WORLDBOOK

Worldbooks | WebMCP | Search | Submit WebMCP

youtube WebMCP

Browser tool configuration for youtube

URL Pattern: ^https?://(www\.)?youtube\.com(/.*)?$
Allowed Extra Domains: youtube.com

Tools (7)

youtube_channel()

Get YouTube channel info and recent videos

Parameters

id string - Channel ID (UCxxxx) or handle (@name). Defaults to current page channel.
max string - Max recent videos to return (default: 10)

JavaScript Handler

(params) => {
  const run = async function(args) {

      const cfg = window.ytcfg?.data_ || {};
      const apiKey = cfg.INNERTUBE_API_KEY;
      const context = cfg.INNERTUBE_CONTEXT;
      if (!apiKey || !context) return {error: 'YouTube config not found', hint: 'Make sure you are on youtube.com'};

      const max = Math.min(parseInt(args.max) || 10, 30);
      let browseId = args.id || '';

      // Detect from current page
      if (!browseId) {
        const match = location.href.match(/youtube\.com\/(channel\/|c\/|@)([^/?]+)/);
        if (match) {
          browseId = match[1] === 'channel/' ? match[2] : '@' + match[2].replace(/^@/, '');
        }
      }
      if (!browseId) return {error: 'No channel ID or handle', hint: 'Provide a channel ID (UCxxxx) or handle (@name)'};

      // If it's a handle, need to resolve it
      let resolvedBrowseId = browseId;
      if (browseId.startsWith('@')) {
        const resolveResp = await fetch('/youtubei/v1/navigation/resolve_url?key=' + apiKey + '&prettyPrint=false', {
          method: 'POST',
          credentials: 'include',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify({context, url: 'https://www.youtube.com/' + browseId})
        });
        if (resolveResp.ok) {
          const resolveData = await resolveResp.json();
          resolvedBrowseId = resolveData.endpoint?.browseEndpoint?.browseId || browseId;
        }
      }

      // Fetch channel data
      const resp = await fetch('/youtubei/v1/browse?key=' + apiKey + '&prettyPrint=false', {
        method: 'POST',
        credentials: 'include',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({context, browseId: resolvedBrowseId})
      });

      if (!resp.ok) return {error: 'Channel API returned HTTP ' + resp.status, hint: resp.status === 404 ? 'Channel not found' : 'API error'};
      const data = await resp.json();

      // Channel metadata
      const metadata = data.metadata?.channelMetadataRenderer || {};
      const header = data.header?.pageHeaderRenderer || data.header?.c4TabbedHeaderRenderer || {};

      // Try to get subscriber count from header
      let subscriberCount = '';
      if (header.content?.pageHeaderViewModel?.metadata?.contentMetadataViewModel?.metadataRows) {
        const rows = header.content.pageHeaderViewModel.metadata.contentMetadataViewModel.metadataRows;
        for (const row of rows) {
          for (const part of (row.metadataParts || [])) {
            const text = part.text?.content || '';
            if (text.includes('subscriber')) subscriberCount = text;
          }
        }
      }

      // Get tabs to find Videos tab
      const tabs = data.contents?.twoColumnBrowseResultsRenderer?.tabs || [];
      const tabNames = tabs.map(t => t.tabRenderer?.title || t.expandableTabRenderer?.title).filter(Boolean);

      // Extract recent videos from the Home or Videos tab
      let recentVideos = [];

      // Try Home tab first
      const homeTab = tabs.find(t => t.tabRenderer?.selected);
      if (homeTab) {
        const sections = homeTab.tabRenderer?.content?.sectionListRenderer?.contents || [];
        for (const section of sections) {
          const shelfItems = section.itemSectionRenderer?.contents || [];
          for (const shelf of shelfItems) {
            const items = shelf.shelfRenderer?.content?.horizontalListRenderer?.items || [];
            for (const item of items) {
              const lvm = item.lockupViewModel;
              if (lvm && lvm.contentType === 'LOCKUP_CONTENT_TYPE_VIDEO' && recentVideos.length < max) {
                const meta = lvm.metadata?.lockupMetadataViewModel;
                const rows = meta?.metadata?.contentMetadataViewModel?.metadataRows || [];
                let viewsAndTime = (rows[0]?.metadataParts || []).map(p => p.text?.content).filter(Boolean).join(' | ');
                const overlays = lvm.contentImage?.thumbnailViewModel?.overlays || [];
                let duration = '';
                for (const ov of overlays) {
                  for (const b of (ov.thumbnailBottomOverlayViewModel?.badges || [])) {
                    if (b.thumbnailBadgeViewModel?.text) duration = b.thumbnailBadgeViewModel.text;
                  }
                }
                recentVideos.push({
                  videoId: lvm.contentId,
                  title: meta?.title?.content || '',
                  duration,
                  viewsAndTime,
                  url: 'https://www.youtube.com/watch?v=' + lvm.contentId
                });
              }
              // Also handle gridVideoRenderer (older format)
              if (item.gridVideoRenderer && recentVideos.length < max) {
                const v = item.gridVideoRenderer;
                recentVideos.push({
                  videoId: v.videoId,
                  title: v.title?.runs?.[0]?.text || v.title?.simpleText || '',
                  duration: v.thumbnailOverlays?.[0]?.thumbnailOverlayTimeStatusRenderer?.text?.simpleText || '',
                  viewsAndTime: (v.shortViewCountText?.simpleText || '') + (v.publishedTimeText?.simpleText ? ' | ' + v.publishedTimeText.simpleText : ''),
                  url: 'https://www.youtube.com/watch?v=' + v.videoId
                });
              }
            }
          }
        }
      }

      return {
        channelId: metadata.externalId || resolvedBrowseId,
        name: metadata.title || '',
        handle: metadata.vanityChannelUrl?.split('/').pop() || '',
        description: (metadata.description || '').substring(0, 500),
        subscriberCount,
        channelUrl: metadata.channelUrl || 'https://www.youtube.com/channel/' + resolvedBrowseId,
        keywords: metadata.keywords || '',
        isFamilySafe: metadata.isFamilySafe,
        tabs: tabNames,
        recentVideoCount: recentVideos.length,
        recentVideos
      };
  };
  return run(params || {});
}

youtube_comments()

Get comments for a YouTube video

Parameters

id string - Video ID (defaults to current page video)
max string - Max comments to return (default: 20)

JavaScript Handler

(params) => {
  const run = async function(args) {

      const currentUrl = location.href;
      let videoId = args.id;
      const max = Math.min(parseInt(args.max) || 20, 100);

      if (!videoId) {
        const match = currentUrl.match(/[?&]v=([a-zA-Z0-9_-]{11})/);
        if (match) videoId = match[1];
      }
      if (!videoId) return {error: 'No video ID', hint: 'Provide a video ID or navigate to a YouTube video page'};

      const cfg = window.ytcfg?.data_ || {};
      const apiKey = cfg.INNERTUBE_API_KEY;
      const context = cfg.INNERTUBE_CONTEXT;
      if (!apiKey || !context) return {error: 'YouTube config not found', hint: 'Make sure you are on youtube.com'};

      // Step 1: Get comment continuation token
      let continuationToken = null;

      // Try from current page ytInitialData first
      if (currentUrl.includes('watch?v=' + videoId) && window.ytInitialData) {
        const results = window.ytInitialData.contents?.twoColumnWatchNextResults?.results?.results?.contents || [];
        const commentSection = results.find(i => i.itemSectionRenderer?.targetId === 'comments-section');
        continuationToken = commentSection?.itemSectionRenderer?.contents?.[0]?.continuationItemRenderer?.continuationEndpoint?.continuationCommand?.token;
      }

      // If not on the page, fetch via next API
      if (!continuationToken) {
        const nextResp = await fetch('/youtubei/v1/next?key=' + apiKey + '&prettyPrint=false', {
          method: 'POST',
          credentials: 'include',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify({context, videoId})
        });
        if (!nextResp.ok) return {error: 'Failed to get video data: HTTP ' + nextResp.status};
        const nextData = await nextResp.json();
        const results = nextData.contents?.twoColumnWatchNextResults?.results?.results?.contents || [];
        const commentSection = results.find(i => i.itemSectionRenderer?.targetId === 'comments-section');
        continuationToken = commentSection?.itemSectionRenderer?.contents?.[0]?.continuationItemRenderer?.continuationEndpoint?.continuationCommand?.token;
      }

      if (!continuationToken) return {error: 'No comment section found', hint: 'Comments may be disabled for this video'};

      // Step 2: Fetch comments using the continuation token
      const commentResp = await fetch('/youtubei/v1/next?key=' + apiKey + '&prettyPrint=false', {
        method: 'POST',
        credentials: 'include',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({context, continuation: continuationToken})
      });

      if (!commentResp.ok) return {error: 'Failed to fetch comments: HTTP ' + commentResp.status};
      const commentData = await commentResp.json();

      // Parse comments from frameworkUpdates (new ViewModel format)
      const mutations = commentData.frameworkUpdates?.entityBatchUpdate?.mutations || [];
      const commentEntities = mutations.filter(m => m.payload?.commentEntityPayload);

      let headerInfo = null;
      const actions = commentData.onResponseReceivedEndpoints || [];
      for (const action of actions) {
        const items = action.reloadContinuationItemsCommand?.continuationItems || [];
        for (const item of items) {
          if (item.commentsHeaderRenderer) {
            headerInfo = item.commentsHeaderRenderer.countText?.runs?.map(r => r.text).join('') || '';
          }
        }
      }

      const comments = commentEntities.slice(0, max).map((m, i) => {
        const p = m.payload.commentEntityPayload;
        const props = p.properties || {};
        const author = p.author || {};
        const toolbar = p.toolbar || {};
        return {
          rank: i + 1,
          author: author.displayName || '',
          authorChannelId: author.channelId || '',
          text: (props.content?.content || '').substring(0, 500),
          publishedTime: props.publishedTime || '',
          likes: toolbar.likeCountNotliked || '0',
          replyCount: toolbar.replyCount || '0',
          isPinned: !!(p.pinnedText)
        };
      });

      return {
        videoId,
        commentCountText: headerInfo || '',
        fetchedCount: comments.length,
        comments
      };
  };
  return run(params || {});
}

youtube_get_video_info()

Get current video information

Parameters

No parameters

JavaScript Handler

(params) => {
  const title = document.querySelector('h1.ytd-video-primary-info-renderer')?.textContent?.trim();
  const channel = document.querySelector('#channel-name a')?.textContent?.trim();
  const views = document.querySelector('#info-strings yt-formatted-string')?.textContent;
  const video = document.querySelector('video');
  return {
    success: true,
    video: {
      title,
      channel,
      views,
      duration: video ? video.duration : null,
      currentTime: video ? video.currentTime : null
    }
  };
}

youtube_play_pause()

Play/pause the current video

Parameters

No parameters

JavaScript Handler

(params) => {
  const video = document.querySelector('video');
  if (video) {
    if (video.paused) {
      video.play();
      return { success: true, message: 'Video playing' };
    } else {
      video.pause();
      return { success: true, message: 'Video paused' };
    }
  }
  return { success: false, message: 'Video not found' };
}

youtube_search()

Search for videos on YouTube

Parameters

query string required - Search keywords

JavaScript Handler

youtube_transcript()

Get video transcript/captions (must be on the video page)

Parameters

lang string - Language code (default: first available, e.g. 'en', 'ja')

JavaScript Handler

(params) => {
  const run = async function(args) {

      const currentUrl = location.href;
      const match = currentUrl.match(/[?&]v=([a-zA-Z0-9_-]{11})/);
      if (!match) return {error: 'Not on a video page', hint: 'Navigate to a YouTube video page first (youtube.com/watch?v=...)'};

      const videoId = match[1];

      // Get available tracks from ytInitialPlayerResponse
      const playerResp = window.ytInitialPlayerResponse;
      const trackList = playerResp?.captions?.playerCaptionsTracklistRenderer;
      const tracks = trackList?.captionTracks || [];
      const availableTracks = tracks.map(t => ({lang: t.languageCode, name: t.name?.simpleText, kind: t.kind}));

      // Find the transcript engagement panel
      const panel = document.querySelector('ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-searchable-transcript"]');
      if (!panel) return {
        error: 'No transcript panel found',
        hint: 'This video may not have captions/subtitles available.',
        videoId,
        availableTracks
      };

      // If a specific language is requested, try to select it
      if (args.lang && tracks.length > 1) {
        const langTrack = tracks.find(t => t.languageCode === args.lang);
        if (!langTrack) return {
          error: 'Language "' + args.lang + '" not found',
          hint: 'Available: ' + availableTracks.map(t => t.lang + ' (' + (t.name || t.kind || '') + ')').join(', '),
          videoId
        };
      }

      // Expand the transcript panel if hidden
      const wasHidden = panel.getAttribute('visibility') !== 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED';
      if (wasHidden) {
        panel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED');
        await new Promise(r => setTimeout(r, 2000));
      }

      // If language selection needed, click the language dropdown
      if (args.lang && tracks.length > 1) {
        const menuBtn = panel.querySelector('yt-sort-filter-sub-menu-renderer button, #menu button');
        if (menuBtn) {
          menuBtn.click();
          await new Promise(r => setTimeout(r, 500));
          const menuItems = panel.querySelectorAll('tp-yt-paper-listbox tp-yt-paper-item, yt-dropdown-menu tp-yt-paper-item');
          for (const item of menuItems) {
            const txt = item.textContent?.trim().toLowerCase();
            const trackMatch = tracks.find(t => t.languageCode === args.lang);
            if (trackMatch && txt.includes(trackMatch.name?.simpleText?.toLowerCase() || args.lang)) {
              item.click();
              await new Promise(r => setTimeout(r, 2000));
              break;
            }
          }
        }
      }

      // Extract transcript segments from DOM
      const segmentEls = panel.querySelectorAll('ytd-transcript-segment-renderer');

      if (!segmentEls.length) {
        // Restore panel state
        if (wasHidden) panel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN');
        return {
          error: 'No transcript segments loaded',
          hint: 'The transcript panel is present but empty. Try refreshing the page.',
          videoId,
          availableTracks
        };
      }

      const segments = Array.from(segmentEls).map(seg => {
        const timeText = seg.querySelector('.segment-timestamp')?.textContent?.trim() || '';
        const text = seg.querySelector('.segment-text')?.textContent?.trim() || '';

        // Parse time string (e.g. "1:23" or "1:02:34") to seconds
        const parts = timeText.split(':').map(Number);
        let startSec = 0;
        if (parts.length === 3) startSec = parts[0] * 3600 + parts[1] * 60 + parts[2];
        else if (parts.length === 2) startSec = parts[0] * 60 + parts[1];
        else if (parts.length === 1) startSec = parts[0];

        return {
          start: startSec,
          startFormatted: timeText,
          text
        };
      }).filter(s => s.text);

      // Restore panel state if we opened it
      if (wasHidden) {
        panel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN');
      }

      // Determine language from available info
      let language = tracks[0]?.languageCode || 'unknown';
      let languageName = tracks[0]?.name?.simpleText || '';
      let kind = tracks[0]?.kind || 'manual';
      if (args.lang) {
        const langTrack = tracks.find(t => t.languageCode === args.lang);
        if (langTrack) {
          language = langTrack.languageCode;
          languageName = langTrack.name?.simpleText || '';
          kind = langTrack.kind || 'manual';
        }
      }

      // Build full text version
      const fullText = segments.map(s => s.text).join(' ');

      // Estimate total duration from last segment
      const lastSeg = segments[segments.length - 1];
      const totalDuration = lastSeg ? lastSeg.start + 10 : 0;

      return {
        videoId,
        language,
        languageName,
        kind,
        segmentCount: segments.length,
        totalDuration,
        availableTracks,
        segments,
        fullText: fullText.substring(0, 5000)
      };
  };
  return run(params || {});
}

youtube_video()

Get detailed info for a YouTube video (from current page or by video ID)

Parameters

id string - Video ID (defaults to current page video)

JavaScript Handler

(params) => {
  const run = async function(args) {

      const currentUrl = location.href;
      let videoId = args.id;

      // Auto-detect from current page
      if (!videoId) {
        const match = currentUrl.match(/[?&]v=([a-zA-Z0-9_-]{11})/);
        if (match) videoId = match[1];
      }
      if (!videoId) return {error: 'No video ID', hint: 'Provide a video ID or navigate to a YouTube video page'};

      const onVideoPage = currentUrl.includes('watch?v=' + videoId);

      // If we're on the video page, use pre-rendered data
      if (onVideoPage && window.ytInitialPlayerResponse && window.ytInitialData) {
        const p = window.ytInitialPlayerResponse;
        const d = window.ytInitialData;

        const vd = p.videoDetails || {};
        const mf = p.microformat?.playerMicroformatRenderer || {};

        // Extract engagement data from ytInitialData
        const results = d.contents?.twoColumnWatchNextResults?.results?.results?.contents || [];
        const primary = results.find(i => i.videoPrimaryInfoRenderer)?.videoPrimaryInfoRenderer;

        let likeCount = '';
        const menuRenderer = primary?.videoActions?.menuRenderer;
        if (menuRenderer?.topLevelButtons) {
          for (const btn of menuRenderer.topLevelButtons) {
            const seg = btn.segmentedLikeDislikeButtonViewModel;
            if (seg) {
              likeCount = seg.likeButtonViewModel?.likeButtonViewModel?.toggleButtonViewModel?.toggleButtonViewModel?.defaultButtonViewModel?.buttonViewModel?.title || '';
            }
          }
        }

        // Channel info
        const secondary = results.find(i => i.videoSecondaryInfoRenderer)?.videoSecondaryInfoRenderer;
        const owner = secondary?.owner?.videoOwnerRenderer;

        // Comment count from section
        const commentSection = results.find(i => i.itemSectionRenderer?.targetId === 'comments-section');
        const commentToken = commentSection?.itemSectionRenderer?.contents?.[0]?.continuationItemRenderer?.continuationEndpoint?.continuationCommand?.token;

        // Chapters
        const chapters = [];
        const chapterPanel = d.engagementPanels?.find(p => p.engagementPanelSectionListRenderer?.panelIdentifier === 'engagement-panel-macro-markers-description-chapters');

        return {
          videoId: vd.videoId,
          title: vd.title,
          channel: vd.author,
          channelId: vd.channelId,
          channelUrl: owner?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl ? 'https://www.youtube.com' + owner.navigationEndpoint.browseEndpoint.canonicalBaseUrl : '',
          subscriberCount: owner?.subscriberCountText?.simpleText || '',
          description: (vd.shortDescription || '').substring(0, 1000),
          duration: parseInt(vd.lengthSeconds) || 0,
          durationFormatted: (() => {
            const s = parseInt(vd.lengthSeconds) || 0;
            const h = Math.floor(s / 3600);
            const m = Math.floor((s % 3600) / 60);
            const sec = s % 60;
            return h > 0 ? h + ':' + String(m).padStart(2,'0') + ':' + String(sec).padStart(2,'0') : m + ':' + String(sec).padStart(2,'0');
          })(),
          viewCount: parseInt(vd.viewCount) || 0,
          viewCountFormatted: primary?.viewCount?.videoViewCountRenderer?.viewCount?.simpleText || '',
          likes: likeCount,
          publishDate: mf.publishDate || primary?.dateText?.simpleText || '',
          category: mf.category || '',
          isLive: vd.isLiveContent || false,
          keywords: (vd.keywords || []).slice(0, 20),
          captionLanguages: (p.captions?.playerCaptionsTracklistRenderer?.captionTracks || []).map(t => ({lang: t.languageCode, name: t.name?.simpleText})),
          url: 'https://www.youtube.com/watch?v=' + vd.videoId,
          _commentContinuationToken: commentToken || null
        };
      }

      // If not on the video page, use innertube next API for basic info
      const cfg = window.ytcfg?.data_ || {};
      const apiKey = cfg.INNERTUBE_API_KEY;
      const context = cfg.INNERTUBE_CONTEXT;
      if (!apiKey || !context) return {error: 'YouTube config not found', hint: 'Make sure you are on youtube.com'};

      const resp = await fetch('/youtubei/v1/next?key=' + apiKey + '&prettyPrint=false', {
        method: 'POST',
        credentials: 'include',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({context, videoId})
      });

      if (!resp.ok) return {error: 'API returned HTTP ' + resp.status};
      const data = await resp.json();

      const results = data.contents?.twoColumnWatchNextResults?.results?.results?.contents || [];
      const primary = results.find(i => i.videoPrimaryInfoRenderer)?.videoPrimaryInfoRenderer;
      const secondary = results.find(i => i.videoSecondaryInfoRenderer)?.videoSecondaryInfoRenderer;
      const owner = secondary?.owner?.videoOwnerRenderer;

      let likeCount = '';
      const menuRenderer = primary?.videoActions?.menuRenderer;
      if (menuRenderer?.topLevelButtons) {
        for (const btn of menuRenderer.topLevelButtons) {
          const seg = btn.segmentedLikeDislikeButtonViewModel;
          if (seg) {
            likeCount = seg.likeButtonViewModel?.likeButtonViewModel?.toggleButtonViewModel?.toggleButtonViewModel?.defaultButtonViewModel?.buttonViewModel?.title || '';
          }
        }
      }

      return {
        videoId,
        title: primary?.title?.runs?.[0]?.text || '',
        channel: owner?.title?.runs?.[0]?.text || '',
        channelId: owner?.navigationEndpoint?.browseEndpoint?.browseId || '',
        subscriberCount: owner?.subscriberCountText?.simpleText || '',
        viewCountFormatted: primary?.viewCount?.videoViewCountRenderer?.viewCount?.simpleText || '',
        likes: likeCount,
        publishDate: primary?.dateText?.simpleText || '',
        url: 'https://www.youtube.com/watch?v=' + videoId
      };
  };
  return run(params || {});
}

🔌 Chrome MCP Server Extension

Use these tools with Claude, ChatGPT, and other AI assistants.

Get Extension →

How to Use WebMCP

WebMCP tools are designed for browser extensions or automation frameworks. The browser extension matches the current URL against the pattern and executes the JavaScript handler when the tool is invoked.

API Endpoint:

GET /api/webmcp/match?url=https://www.youtube.com/...