youtube WebMCP
Browser tool configuration for youtube
Tools (7)
youtube_channel()
Get YouTube channel info and recent videos
Parameters
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
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
JavaScript Handler
(params) => {
const searchBox = document.querySelector('input#search');
if (searchBox) {
searchBox.value = params.query;
searchBox.dispatchEvent(new Event('input', { bubbles: true }));
const searchBtn = document.querySelector('#search-icon-legacy');
if (searchBtn) searchBtn.click();
return { success: true, message: 'Searched: ' + params.query };
}
return { success: false, message: 'Search box not found' };
}
youtube_transcript()
Get video transcript/captions (must be on the video page)
Parameters
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
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.
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/...