require('dotenv').config();
const fetch = require('node-fetch');
const MCP_URL = process.env.MCP_URL || 'http://127.0.0.1:3007/mcp';
async function callTool(name, args = {}) {
const body = {
jsonrpc: '2.0',
id: Math.floor(Math.random() * 100000),
method: 'tools/call',
params: { name, arguments: args }
};
const res = await fetch(MCP_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'x-api-key': 'test-key'
},
body: JSON.stringify(body)
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
return await res.json();
}
function parseToolResponse(resp, toolName) {
if (resp.error) {
throw new Error(`Tool ${toolName} returned error: ${resp.error.message}`);
}
const text = resp.result?.content?.[0]?.text;
if (!text) {
throw new Error(`Tool ${toolName} returned no content`);
}
// Check if it's a YouTube API error by looking for common error patterns
if (text.includes('YouTube API error') || text.includes('403 Forbidden')) {
const errorObj = JSON.parse(text);
throw new Error(`YouTube API key issue: ${errorObj.message}. Please check your YOUTUBE_API_KEY in .env file.`);
}
try {
return JSON.parse(text);
} catch (parseError) {
throw new Error(`Tool ${toolName} returned invalid JSON: ${text.substring(0, 100)}...`);
}
}
describe('YouTube MCP tools', () => {
beforeAll(async () => {
if (!process.env.BYPASS_AUTH_FOR_TESTS) {
throw new Error('BYPASS_AUTH_FOR_TESTS is not set. Add it to your .env to run tests.');
}
// ping root health (assume server on 3002; only fail if unreachable)
const health = await fetch(MCP_URL.replace('/mcp', ''));
if (!health.ok) throw new Error('Dev server not responding on expected port.');
}, 15000);
test('get_youtube_transcript returns timestamped segments', async () => {
const resp = await callTool('get_youtube_transcript', { videoId: 'dQw4w9WgXcQ' });
const arr = parseToolResponse(resp, 'get_youtube_transcript');
expect(Array.isArray(arr)).toBe(true);
expect(arr.length).toBeGreaterThan(0);
// Check first segment has text and timing info
const seg = arr[0];
expect(typeof seg.text).toBe('string');
const hasTs = (typeof seg.startMs === 'number') || (typeof seg.startSeconds === 'number') || (typeof seg.start === 'string');
expect(hasTs).toBe(true);
});
test('get_youtube_transcript backwards compatibility', async () => {
const resp = await callTool('get_youtube_transcript', { videoId: 'dQw4w9WgXcQ' });
const arr = parseToolResponse(resp, 'get_youtube_transcript');
expect(Array.isArray(arr)).toBe(true);
// Find first segment with any timestamp info
const seg = arr.find(s => typeof s.startMs === 'number' || typeof s.startSeconds === 'number' || typeof s.start === 'string');
if (seg) {
expect(typeof seg.text).toBe('string');
const hasTs = (typeof seg.startMs === 'number') || (typeof seg.startSeconds === 'number') || (typeof seg.start === 'string');
expect(hasTs).toBe(true);
}
});
test('get_video_info returns expected id', async () => {
const resp = await callTool('get_video_info', { videoId: 'dQw4w9WgXcQ' });
const obj = parseToolResponse(resp, 'get_video_info');
expect(obj.videoId === 'dQw4w9WgXcQ' || obj.id === 'dQw4w9WgXcQ').toBe(true);
});
});