yt-dlp-mcp/test-real-video.mjs
kevinwatt 26b2137751 chore: release v0.7.0 - MCP Best Practices & Quality Improvements
Major release with comprehensive MCP best practices implementation:

 Added:
- Tool name prefixes (ytdlp_) for all 8 tools to avoid naming conflicts
- Zod schema validation with runtime input validation
- Tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- Response format options (JSON/Markdown) for search tools
- Pagination support with offset parameter
- Character limits (25K standard, 50K for transcripts) with smart truncation
- Actionable error messages with platform-specific guidance

🔧 Improved:
- Comprehensive tool descriptions with usage examples
- Enhanced configuration system with limits
- Better TypeScript type safety
- Professional README with badges and tables

🐛 Fixed:
- JSON parsing issue in metadata truncation
- Maintained valid JSON structure when truncated

🧪 Tested:
-  YouTube platform (Rick Astley video)
-  Bilibili platform (Chinese content)
-  Multi-language support verified
-  All 8 tools tested with real API calls

📖 Documentation:
- Created comprehensive CHANGELOG.md
- Redesigned README.md with professional formatting
- Added migration guide for v0.6.x users

🌍 Platform Support:
- Verified: YouTube, Bilibili
- Theory: 1000+ platforms via yt-dlp
2025-10-19 01:52:22 +08:00

216 lines
6.6 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Real-world MCP server test with actual YouTube video
* Tests multiple tools with https://www.youtube.com/watch?v=dQw4w9WgXcQ
*/
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const serverPath = join(__dirname, 'lib', 'index.mjs');
const TEST_VIDEO = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
console.log('🎬 Testing yt-dlp MCP Server with Real Video\n');
console.log('Video:', TEST_VIDEO);
console.log('Starting server from:', serverPath, '\n');
const server = spawn('node', [serverPath]);
let testsPassed = 0;
let testsFailed = 0;
let responseBuffer = '';
let requestId = 0;
let currentTest = '';
// Timeout to ensure tests complete
const timeout = setTimeout(() => {
console.log('\n⏱ Test timeout - killing server');
server.kill();
printResults();
}, 60000); // 60 seconds for real API calls
function printResults() {
clearTimeout(timeout);
console.log(`\n${'='.repeat(60)}`);
console.log(`📊 Final Test Results:`);
console.log(` ✅ Passed: ${testsPassed}`);
console.log(` ❌ Failed: ${testsFailed}`);
console.log(`${'='.repeat(60)}`);
process.exit(testsFailed > 0 ? 1 : 0);
}
server.stdout.on('data', (data) => {
responseBuffer += data.toString();
// Try to parse JSON-RPC responses
const lines = responseBuffer.split('\n');
responseBuffer = lines.pop() || '';
lines.forEach(line => {
if (line.trim()) {
try {
const response = JSON.parse(line);
if (response.error) {
console.log(`${currentTest} - ERROR`);
console.log(' Error:', response.error.message);
testsFailed++;
} else if (response.result) {
handleTestResult(response);
}
} catch (e) {
// Not JSON, might be regular output
}
}
});
});
server.stderr.on('data', (data) => {
const output = data.toString().trim();
if (output && !output.includes('ExperimentalWarning')) {
console.log('🔧 Server:', output);
}
});
server.on('close', (code) => {
printResults();
});
function handleTestResult(response) {
const content = response.result.content?.[0]?.text || JSON.stringify(response.result);
if (currentTest === 'Initialize') {
console.log('✅ Initialize - PASSED');
console.log(` Protocol: ${response.result.protocolVersion}`);
console.log(` Server: ${response.result.serverInfo.name} v${response.result.serverInfo.version}\n`);
testsPassed++;
}
else if (currentTest === 'Get Metadata Summary') {
if (content.includes('Rick Astley') || content.includes('Never Gonna Give You Up')) {
console.log('✅ Get Metadata Summary - PASSED');
console.log(' Response preview:');
const lines = content.split('\n').slice(0, 5);
lines.forEach(line => console.log(` ${line}`));
console.log(' ...\n');
testsPassed++;
} else {
console.log('❌ Get Metadata Summary - FAILED');
console.log(' Expected Rick Astley content, got:', content.substring(0, 100));
testsFailed++;
}
}
else if (currentTest === 'List Subtitle Languages') {
if (content.includes('en') || content.includes('English')) {
console.log('✅ List Subtitle Languages - PASSED');
console.log(' Found subtitle languages\n');
testsPassed++;
} else {
console.log('❌ List Subtitle Languages - FAILED');
console.log(' Response:', content.substring(0, 200));
testsFailed++;
}
}
else if (currentTest === 'Get Metadata (Filtered)') {
try {
const metadata = JSON.parse(content);
if (metadata.title && metadata.channel) {
console.log('✅ Get Metadata (Filtered) - PASSED');
console.log(` Title: ${metadata.title}`);
console.log(` Channel: ${metadata.channel}`);
console.log(` Duration: ${metadata.duration || 'N/A'}\n`);
testsPassed++;
} else {
console.log('❌ Get Metadata (Filtered) - FAILED');
console.log(' Missing expected fields');
testsFailed++;
}
} catch (e) {
console.log('❌ Get Metadata (Filtered) - FAILED');
console.log(' Invalid JSON response');
testsFailed++;
}
}
else if (currentTest === 'Download Transcript (first 500 chars)') {
if (content.length > 100) {
console.log('✅ Download Transcript - PASSED');
console.log(' Transcript length:', content.length, 'characters');
console.log(' Preview:', content.substring(0, 150).replace(/\n/g, ' ') + '...\n');
testsPassed++;
} else {
console.log('❌ Download Transcript - FAILED');
console.log(' Response too short:', content.substring(0, 100));
testsFailed++;
}
}
}
function sendRequest(method, params, testName) {
requestId++;
currentTest = testName;
console.log(`🔍 Test ${requestId}: ${testName}`);
const request = {
jsonrpc: '2.0',
id: requestId,
method: method,
params: params
};
server.stdin.write(JSON.stringify(request) + '\n');
}
// Run tests sequentially with delays
setTimeout(() => {
// Test 1: Initialize
sendRequest('initialize', {
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: { name: 'test-client', version: '1.0.0' }
}, 'Initialize');
setTimeout(() => {
// Test 2: Get video metadata summary
sendRequest('tools/call', {
name: 'ytdlp_get_video_metadata_summary',
arguments: { url: TEST_VIDEO }
}, 'Get Metadata Summary');
setTimeout(() => {
// Test 3: List subtitle languages
sendRequest('tools/call', {
name: 'ytdlp_list_subtitle_languages',
arguments: { url: TEST_VIDEO }
}, 'List Subtitle Languages');
setTimeout(() => {
// Test 4: Get specific metadata fields
sendRequest('tools/call', {
name: 'ytdlp_get_video_metadata',
arguments: {
url: TEST_VIDEO,
fields: ['id', 'title', 'channel', 'duration', 'view_count']
}
}, 'Get Metadata (Filtered)');
setTimeout(() => {
// Test 5: Download transcript (might take longer)
console.log(' (This may take 10-20 seconds...)\n');
sendRequest('tools/call', {
name: 'ytdlp_download_transcript',
arguments: { url: TEST_VIDEO, language: 'en' }
}, 'Download Transcript (first 500 chars)');
setTimeout(() => {
console.log('\n✅ All tests completed!');
server.kill();
}, 25000); // Wait 25 seconds for transcript
}, 3000);
}, 5000);
}, 5000);
}, 2000);
}, 1000);