scan
Discover and test MCP server configurations by performing JSON-RPC handshake connections. Identify issues and verify connectivity for your MCP setup.
Instructions
Discover all MCP server configs and test their connections via JSON-RPC handshake
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/server.ts:14-46 (registration)The MCP tool 'scan' is registered on the MCP server using server.tool(). It discovers all MCP server configs via scanConfigs() and tests their connections via testServer().
server.tool( "scan", "Discover all MCP server configs and test their connections via JSON-RPC handshake", {}, async () => { const servers = await scanConfigs(); if (servers.length === 0) { return { content: [{ type: "text", text: "No MCP servers found in any configuration." }], }; } const results: ScanResult[] = []; for (const s of servers) { results.push(await testServer(s)); } const lines = results.map((r) => { const status = r.status === "ok" ? "OK" : r.status === "timeout" ? "TIMEOUT" : "ERROR"; const latency = r.latencyMs ? `${r.latencyMs}ms` : "—"; const tools = r.tools !== undefined ? `${r.tools} tools` : "—"; const detail = r.error ?? ""; return `${r.server.name} (${r.server.source}) — ${status} | ${latency} | ${tools} ${detail}`.trim(); }); const ok = results.filter((r) => r.status === "ok").length; const summary = `\n${ok}/${results.length} servers responding`; return { content: [{ type: "text", text: lines.join("\n") + summary }], }; } ); - src/server.ts:14-46 (handler)The handler function for the 'scan' tool: calls scanConfigs() to discover servers, iterates through each calling testServer(), and returns formatted results with status, latency, tool count, and a summary.
server.tool( "scan", "Discover all MCP server configs and test their connections via JSON-RPC handshake", {}, async () => { const servers = await scanConfigs(); if (servers.length === 0) { return { content: [{ type: "text", text: "No MCP servers found in any configuration." }], }; } const results: ScanResult[] = []; for (const s of servers) { results.push(await testServer(s)); } const lines = results.map((r) => { const status = r.status === "ok" ? "OK" : r.status === "timeout" ? "TIMEOUT" : "ERROR"; const latency = r.latencyMs ? `${r.latencyMs}ms` : "—"; const tools = r.tools !== undefined ? `${r.tools} tools` : "—"; const detail = r.error ?? ""; return `${r.server.name} (${r.server.source}) — ${status} | ${latency} | ${tools} ${detail}`.trim(); }); const ok = results.filter((r) => r.status === "ok").length; const summary = `\n${ok}/${results.length} servers responding`; return { content: [{ type: "text", text: lines.join("\n") + summary }], }; } ); - src/scanner.ts:26-60 (helper)scanConfigs() helper that discovers MCP server configurations from multiple sources (Claude Code, Cursor, VS Code, Windsurf, Claude Desktop config files).
export async function scanConfigs(): Promise<McpServer[]> { const sources = getConfigSources(); const servers: McpServer[] = []; for (const source of sources) { if (!existsSync(source.path)) continue; try { const raw = await readFile(source.path, "utf-8"); const json = JSON.parse(raw); const serverMap = json[source.key]; if (!serverMap || typeof serverMap !== "object") continue; for (const [name, config] of Object.entries(serverMap)) { const cfg = config as Record<string, unknown>; const server: McpServer = { name, source: source.name, configPath: source.path, type: cfg.command ? "stdio" : cfg.url ? "sse" : "unknown", command: cfg.command as string | undefined, args: cfg.args as string[] | undefined, url: cfg.url as string | undefined, env: cfg.env as Record<string, string> | undefined, }; servers.push(server); } } catch { // Skip unparseable configs } } return servers; } - src/tester.ts:27-128 (helper)testServer() helper that spawns an MCP server process, sends a JSON-RPC initialize request, measures latency, then requests tools/list to count available tools.
export async function testServer(server: McpServer): Promise<ScanResult> { if (server.type === "sse" || server.type === "unknown") { return { server, status: "error", error: `Unsupported transport: ${server.type}` }; } if (!server.command) { return { server, status: "error", error: "No command specified" }; } return new Promise((resolve) => { const start = performance.now(); let settled = false; let stdout = ""; const env = { ...process.env, ...server.env }; const cmd = server.command!; const args = server.args ?? []; // On Windows, use shell to resolve commands; on Unix, spawn directly const useShell = process.platform === "win32"; const child = spawn(cmd, args, { env, stdio: ["pipe", "pipe", "pipe"], ...(useShell ? { shell: true } : {}), }); const timer = setTimeout(() => { if (!settled) { settled = true; child.kill(); resolve({ server, status: "timeout", error: `No response within ${TIMEOUT_MS}ms` }); } }, TIMEOUT_MS); child.stdout.on("data", (chunk: Buffer) => { stdout += chunk.toString(); const response = parseJsonRpcResponse(stdout) as Record<string, unknown> | null; if (response && response.result) { if (!settled) { settled = true; clearTimeout(timer); const latencyMs = Math.round(performance.now() - start); // Send initialized notification then tools/list child.stdin.write( createJsonRpcRequest("notifications/initialized", 0) ); child.stdin.write( createJsonRpcRequest("tools/list", 2) ); // Give it a moment to respond with tools let toolsData = ""; const toolsTimer = setTimeout(() => { child.kill(); resolve({ server, status: "ok", latencyMs, tools: undefined }); }, 3000); child.stdout.on("data", (toolChunk: Buffer) => { toolsData += toolChunk.toString(); const toolsResponse = parseJsonRpcResponse(toolsData) as Record<string, unknown> | null; if (toolsResponse && toolsResponse.result) { clearTimeout(toolsTimer); child.kill(); const result = toolsResponse.result as { tools?: unknown[] }; resolve({ server, status: "ok", latencyMs, tools: result.tools?.length, }); } }); } } }); child.on("error", (err: Error) => { if (!settled) { settled = true; clearTimeout(timer); resolve({ server, status: "error", error: err.message }); } }); child.on("exit", (code: number | null) => { if (!settled) { settled = true; clearTimeout(timer); resolve({ server, status: "error", error: `Process exited with code ${code}` }); } }); // Send initialize request const initRequest = createJsonRpcRequest("initialize", 1, { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "mcp-doctor", version: "0.1.0" }, }); child.stdin.write(initRequest); }); } - src/types.ts:12-18 (schema)ScanResult interface defining the shape of scan output: server info, status (ok/error/timeout), latencyMs, tool count, and error message.
export interface ScanResult { server: McpServer; status: "ok" | "error" | "timeout"; latencyMs?: number; tools?: number; error?: string; }