mcp-orchestrator
Version:
MCP Orchestrator - Discover and install MCPs with automatic OAuth support. Uses Claude CLI for OAuth MCPs (Canva, Asana, etc). 34 trusted MCPs from Claude Partners.
182 lines (181 loc) âĸ 6.79 kB
JavaScript
/**
* Official MCP Crawler
* Fetches high-quality MCPs from official sources
*/
import * as https from 'https';
import * as fs from 'fs';
export class OfficialMCPCrawler {
mcps = [];
REGISTRY_API_URL = 'https://registry.modelcontextprotocol.io/v0/servers';
/**
* Fetch data from the registry API with pagination
*/
async fetchFromRegistryAPI(limit = 100) {
const allServers = [];
let cursor = undefined;
while (allServers.length < limit) {
const url = cursor
? `${this.REGISTRY_API_URL}?limit=100&cursor=${cursor}`
: `${this.REGISTRY_API_URL}?limit=100`;
console.log(`đĄ Fetching from: ${url}`);
const data = await this.httpsGet(url);
const response = JSON.parse(data);
// Extract server info from entries
const serverInfos = response.servers.map(entry => entry.server);
allServers.push(...serverInfos);
console.log(` â Got ${serverInfos.length} servers (total: ${allServers.length})`);
if (!response.metadata?.nextCursor || allServers.length >= limit) {
break;
}
cursor = response.metadata.nextCursor;
}
return allServers.slice(0, limit);
}
/**
* Helper to make HTTPS GET requests
*/
httpsGet(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(data));
}).on('error', reject);
});
}
/**
* Extract keywords from description
*/
extractKeywords(description) {
const stopWords = new Set(['the', 'and', 'for', 'with', 'this', 'that', 'from', 'your', 'can', 'use']);
const words = description.toLowerCase()
.replace(/[^\w\s]/g, ' ')
.split(/\s+/)
.filter(w => w.length > 3 && !stopWords.has(w));
return [...new Set(words)].slice(0, 10); // Max 10 unique keywords
}
/**
* Generate ID from server name (use the full qualified name)
*/
generateId(serverName) {
// Use the server name as-is since it's already a unique identifier like "ai.mcpcap/mcpcap"
return serverName
.replace(/\//g, '-') // Replace / with -
.toLowerCase();
}
/**
* Transform registry server to our CrawledMCP format
*/
transformServer(server) {
// Determine how to connect/install this MCP
let packageName = '';
let runtime = 'unknown';
let command;
// Check for installable packages (npm, pypi)
if (server.packages && server.packages.length > 0) {
const pkg = server.packages.find(p => p.registryType === 'npm' || p.registryType === 'pypi');
if (pkg) {
packageName = pkg.identifier;
runtime = pkg.registryType === 'npm' ? 'node' : 'python';
}
else if (server.packages[0]) {
// Use OCI or other package type
packageName = server.packages[0].identifier;
runtime = 'unknown';
}
}
// Check for remote endpoints
if (server.remotes && server.remotes.length > 0) {
const remote = server.remotes[0];
// For remotes, use the URL as the "package" identifier
if (!packageName) {
packageName = remote.url;
command = `remote:${remote.type}:${remote.url}`;
}
}
// Skip if we have no way to connect
if (!packageName) {
return null;
}
return {
id: this.generateId(server.name), // Use server name for unique ID
name: server.name,
description: server.description || '',
packageName,
command,
keywords: this.extractKeywords(server.description || server.name),
source: 'official', // From official registry
metadata: {
runtime,
repository: server.repository?.url,
author: server.author?.name,
installTime: runtime === 'node' ? '20s' : runtime === 'python' ? '30s' : '5s' // Remotes are instant
}
};
}
/**
* Fetch official MCPs from the registry API
*/
async crawlOfficialRegistry() {
console.log('đ Crawling official MCP registry API...');
try {
const servers = await this.fetchFromRegistryAPI(200); // Fetch 200 MCPs
console.log(`đĻ Fetched ${servers.length} servers from registry`);
const transformed = servers
.map(s => this.transformServer(s))
.filter((mcp) => mcp !== null);
// Deduplicate by ID (keep first occurrence)
const seenIds = new Set();
const deduplicated = transformed.filter(mcp => {
if (seenIds.has(mcp.id)) {
return false;
}
seenIds.add(mcp.id);
return true;
});
this.mcps = deduplicated;
console.log(`â
Successfully transformed ${this.mcps.length} unique MCPs (${transformed.length - deduplicated.length} duplicates removed)`);
return this.mcps;
}
catch (error) {
console.error('â Failed to fetch from registry:', error);
throw error;
}
}
/**
* Fetch community MCPs (optional - registry already has community MCPs)
*/
async crawlCommunityMCPs() {
console.log('âšī¸ Skipping separate community crawl - registry includes community MCPs');
return []; // Registry API already includes community servers
}
/**
* Get all crawled MCPs
*/
getAllMCPs() {
return this.mcps;
}
/**
* Save crawled MCPs to JSON
*/
async saveToFile(filepath) {
const data = {
timestamp: new Date().toISOString(),
totalMCPs: this.mcps.length,
mcps: this.mcps
};
fs.writeFileSync(filepath, JSON.stringify(data, null, 2));
console.log(`đž Saved ${this.mcps.length} MCPs to ${filepath}`);
}
}
// Run crawler if executed directly
// Check if this is the main module using import.meta.url
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
if (isMainModule) {
const crawler = new OfficialMCPCrawler();
(async () => {
await crawler.crawlOfficialRegistry();
await crawler.crawlCommunityMCPs();
await crawler.saveToFile('./data/crawled-mcps.json');
})();
}