YouTube MCP Integration
by spolepaka
Verified
// Comprehensive test for all YouTube MCP tools
import fetch from 'node-fetch';
import EventSource from 'eventsource';
const BASE_URL = 'http://localhost:3000';
const sessionId = 'test-session-' + Math.random().toString(36).substring(2, 9);
console.log(`Using session ID: ${sessionId}`);
// Test results tracking
const testResults = {
connection: false,
initialization: false,
tools: {}
};
// Helper to make API calls
async function callAPI(endpoint, method = 'GET', body = null) {
const options = {
method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
if (body) {
options.body = JSON.stringify(body);
}
try {
console.log(`Making ${method} request to ${endpoint}`);
const response = await fetch(`${BASE_URL}${endpoint}`, options);
console.log(`Response status: ${response.status}`);
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
try {
const data = await response.json();
console.log('Response data:', JSON.stringify(data).substring(0, 500) + '...');
return data;
} catch (jsonError) {
console.log('Failed to parse JSON response:', jsonError.message);
return null;
}
} else {
// For non-JSON responses, just get the text
const text = await response.text();
console.log('Response text:', text.substring(0, 200) + (text.length > 200 ? '...' : ''));
// Try to parse as JSON anyway in case the content-type is wrong
try {
return JSON.parse(text);
} catch (e) {
return text;
}
}
} catch (error) {
console.error(`Error calling ${endpoint}:`, error);
return null;
}
}
// Check server status before connecting
async function checkStatus() {
console.log('Checking server status...');
const status = await callAPI('/status');
return status;
}
// Connect to SSE endpoint
function connectSSE() {
console.log(`\n=======================`);
console.log(`Connecting to SSE endpoint with session ID: ${sessionId}`);
console.log(`=======================\n`);
const url = `${BASE_URL}/sse?sessionId=${sessionId}`;
// Create event source
const eventSourceOptions = { headers: { Accept: 'text/event-stream' } };
const eventSource = new EventSource(url, eventSourceOptions);
// Track connection state
let connectionEstablished = false;
eventSource.onopen = () => {
connectionEstablished = true;
testResults.connection = true;
console.log(`\n[SSE] Connection opened successfully`);
// Send initialization message after connection is established
setTimeout(() => {
sendInitMessage();
}, 2000);
};
eventSource.onmessage = (event) => {
try {
console.log(`\n[SSE] Received message:`, event.data);
// Try to parse the data as JSON
try {
const jsonData = JSON.parse(event.data);
console.log('[SSE] Parsed JSON:', jsonData);
} catch (jsonError) {
console.log('[SSE] Message is not valid JSON:', event.data);
}
} catch (error) {
console.error('[SSE] Error handling message:', error);
}
};
eventSource.onerror = (error) => {
console.error(`\n[SSE] Connection error:`, error);
// Log readyState
const states = ['CONNECTING', 'OPEN', 'CLOSED'];
console.log(`[SSE] Ready state: ${states[eventSource.readyState] || eventSource.readyState}`);
// Only retry a few times to avoid infinite loop
if (!connectionEstablished) {
console.log('[SSE] Connection failed to establish initially.');
testResults.connection = false;
}
};
return eventSource;
}
// Send initialization message to the server
async function sendInitMessage() {
console.log('\n=======================');
console.log('Sending init message...');
console.log('=======================\n');
const initMessage = {
method: "initialize",
params: {
protocolVersion: "2024-11-05",
capabilities: {
tools: true,
prompts: false,
resources: true,
logging: false,
roots: {
listChanged: false
}
},
clientInfo: {
name: "test-client",
version: "1.0.0"
}
},
jsonrpc: "2.0",
id: 0
};
const result = await callAPI(`/messages?sessionId=${sessionId}`, 'POST', initMessage);
if (result && !result.error) {
console.log('✅ Initialization successful');
testResults.initialization = true;
// Start testing all the tools
await testAllTools();
} else {
console.log('❌ Initialization failed');
testResults.initialization = false;
}
}
// Test the echo tool
async function testEchoTool() {
console.log('\n=======================');
console.log('Testing echo tool...');
console.log('=======================\n');
const echoMessage = {
method: "call_tool",
params: {
name: "echo",
arguments: {
message: "Hello, MCP!"
}
},
jsonrpc: "2.0",
id: 1
};
const result = await callAPI(`/messages?sessionId=${sessionId}`, 'POST', echoMessage);
if (result && !result.error) {
console.log('✅ Echo tool called successfully');
console.log('Result:', result.result?.content?.[0]?.text || JSON.stringify(result));
testResults.tools.echo = {
success: true,
response: result.result?.content?.[0]?.text || "Invalid response format"
};
return true;
} else {
console.log('❌ Echo tool call failed');
testResults.tools.echo = {
success: false,
error: result?.error?.message || "Unknown error"
};
return false;
}
}
// Test YouTube Search
async function testYouTubeSearch() {
console.log('\n=======================');
console.log('Testing YouTube Search tool...');
console.log('=======================\n');
const searchMessage = {
method: "call_tool",
params: {
name: "youtube_search",
arguments: {
query: "AI programming guide",
limit: 3
}
},
jsonrpc: "2.0",
id: 2
};
const result = await callAPI(`/messages?sessionId=${sessionId}`, 'POST', searchMessage);
if (result && !result.error) {
console.log('✅ YouTube Search tool called successfully');
try {
const content = result.result?.content?.[0]?.text;
const searchResults = JSON.parse(content);
if (Array.isArray(searchResults) && searchResults.length > 0) {
console.log(`Found ${searchResults.length} videos`);
console.log('First result:', searchResults[0].title);
testResults.tools.youtube_search = {
success: true,
resultCount: searchResults.length,
firstVideoId: searchResults[0].videoId
};
// Save the first video ID for the next tests
return searchResults[0].videoId;
} else {
console.log('❌ Search returned empty or invalid results');
testResults.tools.youtube_search = {
success: false,
error: "Empty or invalid results"
};
return null;
}
} catch (error) {
console.log('❌ Failed to parse search results:', error.message);
testResults.tools.youtube_search = {
success: false,
error: `Parse error: ${error.message}`
};
return null;
}
} else {
console.log('❌ YouTube Search tool call failed');
testResults.tools.youtube_search = {
success: false,
error: result?.error?.message || "Unknown error"
};
return null;
}
}
// Test YouTube Video Info
async function testYouTubeVideoInfo(videoId) {
console.log('\n=======================');
console.log('Testing YouTube Video Info tool...');
console.log('=======================\n');
// Use provided videoId or fallback to a known one
const testVideoId = videoId || 'dQw4w9WgXcQ'; // Fallback to Rick Roll
const infoMessage = {
method: "call_tool",
params: {
name: "youtube_get_video_info",
arguments: {
input: testVideoId
}
},
jsonrpc: "2.0",
id: 3
};
const result = await callAPI(`/messages?sessionId=${sessionId}`, 'POST', infoMessage);
if (result && !result.error) {
console.log('✅ YouTube Video Info tool called successfully');
try {
const content = result.result?.content?.[0]?.text;
const videoInfo = JSON.parse(content);
if (videoInfo && videoInfo.title) {
console.log('Video Title:', videoInfo.title);
console.log('Channel:', videoInfo.channel?.name);
testResults.tools.youtube_get_video_info = {
success: true,
videoTitle: videoInfo.title,
channelName: videoInfo.channel?.name
};
return true;
} else {
console.log('❌ Video info returned invalid data');
testResults.tools.youtube_get_video_info = {
success: false,
error: "Invalid video info data"
};
return false;
}
} catch (error) {
console.log('❌ Failed to parse video info:', error.message);
testResults.tools.youtube_get_video_info = {
success: false,
error: `Parse error: ${error.message}`
};
return false;
}
} else {
console.log('❌ YouTube Video Info tool call failed');
testResults.tools.youtube_get_video_info = {
success: false,
error: result?.error?.message || "Unknown error"
};
return false;
}
}
// Test YouTube Transcript
async function testYouTubeTranscript(videoId) {
console.log('\n=======================');
console.log('Testing YouTube Transcript tool...');
console.log('=======================\n');
// Use a known video ID that has a transcript available
const testVideoId = 'BFiBZI3nqhQ'; // Video confirmed to have transcript
console.log(`Using video ID: ${testVideoId} for transcript test`);
const transcriptMessage = {
method: "call_tool",
params: {
name: "youtube_get_transcript",
arguments: {
input: testVideoId
}
},
jsonrpc: "2.0",
id: 4
};
const result = await callAPI(`/messages?sessionId=${sessionId}`, 'POST', transcriptMessage);
if (result && !result.error) {
console.log('✅ YouTube Transcript tool called successfully');
try {
const content = result.result?.content?.[0]?.text;
const transcriptData = JSON.parse(content);
if (transcriptData && transcriptData.transcript && Array.isArray(transcriptData.transcript)) {
console.log(`Transcript has ${transcriptData.transcript.length} entries`);
if (transcriptData.transcript.length > 0) {
console.log('First transcript entry:', transcriptData.transcript[0]);
}
testResults.tools.youtube_get_transcript = {
success: true,
entryCount: transcriptData.transcript.length
};
return true;
} else {
console.log('❌ Transcript returned invalid data');
testResults.tools.youtube_get_transcript = {
success: false,
error: "Invalid transcript data"
};
return false;
}
} catch (error) {
console.log('❌ Failed to parse transcript:', error.message);
testResults.tools.youtube_get_transcript = {
success: false,
error: `Parse error: ${error.message}`
};
return false;
}
} else {
console.log('❌ YouTube Transcript tool call failed');
testResults.tools.youtube_get_transcript = {
success: false,
error: result?.error?.message || "Unknown error"
};
return false;
}
}
// Test all tools in sequence
async function testAllTools() {
// Remove the echo tool test
// const echoSuccess = await testEchoTool();
// console.log(`Echo tool test ${echoSuccess ? 'passed' : 'failed'}`);
// Next, test YouTube search
const videoId = await testYouTubeSearch();
console.log(`YouTube Search test ${videoId ? 'passed' : 'failed'}`);
// If search worked, use that videoId for subsequent tests
if (videoId) {
// Test video info
const infoSuccess = await testYouTubeVideoInfo(videoId);
console.log(`YouTube Video Info test ${infoSuccess ? 'passed' : 'failed'}`);
// Test transcript
const transcriptSuccess = await testYouTubeTranscript(videoId);
console.log(`YouTube Transcript test ${transcriptSuccess ? 'passed' : 'failed'}`);
} else {
// If search failed, still test with the fallback videoId
const infoSuccess = await testYouTubeVideoInfo();
console.log(`YouTube Video Info test ${infoSuccess ? 'passed' : 'failed'}`);
const transcriptSuccess = await testYouTubeTranscript();
console.log(`YouTube Transcript test ${transcriptSuccess ? 'passed' : 'failed'}`);
}
// Print test summary
printTestSummary();
}
// Print test summary
function printTestSummary() {
console.log('\n=======================');
console.log('🔍 TEST SUMMARY');
console.log('=======================');
console.log(`SSE Connection: ${testResults.connection ? '✅ PASSED' : '❌ FAILED'}`);
console.log(`Initialization: ${testResults.initialization ? '✅ PASSED' : '❌ FAILED'}`);
console.log('Tools:');
Object.entries(testResults.tools).forEach(([tool, result]) => {
console.log(` - ${tool}: ${result.success ? '✅ PASSED' : '❌ FAILED'}`);
if (result.success) {
if (tool === 'echo') {
console.log(` Response: ${result.response}`);
} else if (tool === 'youtube_search') {
console.log(` Found ${result.resultCount} videos`);
} else if (tool === 'youtube_get_video_info') {
console.log(` Title: ${result.videoTitle}`);
console.log(` Channel: ${result.channelName}`);
} else if (tool === 'youtube_get_transcript') {
console.log(` Transcript entries: ${result.entryCount}`);
}
} else {
console.log(` Error: ${result.error}`);
}
});
console.log('=======================');
// Check overall success
const allPassed =
testResults.connection &&
testResults.initialization &&
Object.values(testResults.tools).every(tool => tool.success);
console.log(`\nOverall Test Status: ${allPassed ? '✅ ALL TESTS PASSED' : '❌ SOME TESTS FAILED'}`);
}
// Main test function
async function runTest() {
console.log('\n=======================');
console.log('Starting YouTube MCP Tools Test');
console.log('=======================\n');
const status = await checkStatus();
if (status && status.status === 'online') {
console.log('✅ Server is online with', status.tools?.length || 0, 'tools available');
// Make sure all required tools are available
const requiredTools = ['youtube_search', 'youtube_get_video_info', 'youtube_get_transcript']; // Removed echo
const availableTools = status.tools?.map(tool => tool.name) || [];
const missingTools = requiredTools.filter(tool => !availableTools.includes(tool));
if (missingTools.length > 0) {
console.error('❌ Some required tools are missing:', missingTools.join(', '));
process.exit(1);
}
console.log('✅ All required tools are available');
const eventSource = connectSSE();
// Clean up after 60 seconds
setTimeout(() => {
console.log('\n=======================');
console.log('Test complete, closing SSE connection');
console.log('=======================\n');
eventSource.close();
// Final test summary
printTestSummary();
// Exit with appropriate code
const allPassed =
testResults.connection &&
testResults.initialization &&
Object.values(testResults.tools).every(tool => tool.success);
process.exit(allPassed ? 0 : 1);
}, 60000);
} else {
console.error('❌ Server is not online, aborting test');
process.exit(1);
}
}
runTest();