UNPKG

fastmcp

Version:

A TypeScript framework for building MCP servers.

237 lines (234 loc) 6.84 kB
#!/usr/bin/env node // src/bin/fastmcp.ts import { execa } from "execa"; import { mkdtemp, rm, writeFile } from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; // src/bin/devCommand.ts var DEV_SERVER_NAME = "dev"; var buildDevConfig = (file) => JSON.stringify( { mcpServers: { [DEV_SERVER_NAME]: { args: ["tsx", file], command: "npx" } } }, null, 2 ); var buildDevCommand = (options) => { const { configPath, file, tool, toolArgs, watch = false } = options; if (tool) { if (!configPath) { throw new Error( "configPath is required when calling a tool non-interactively" ); } return [ "npx", "@wong2/mcp-cli", "-c", configPath, "call-tool", `${DEV_SERVER_NAME}:${tool}`, ...toolArgs === void 0 ? [] : ["--args", toolArgs] ]; } return [ "npx", "@wong2/mcp-cli", "npx", "tsx", ...watch ? ["--watch"] : [], file ]; }; // src/bin/fastmcp.ts await yargs(hideBin(process.argv)).scriptName("fastmcp").command( "dev <file>", "Start a development server", (yargs2) => { return yargs2.positional("file", { demandOption: true, describe: "The path to the server file", type: "string" }).option("args", { describe: `JSON arguments passed to the tool (requires --tool), e.g. '{"a":1}'`, type: "string" }).option("tool", { alias: "t", describe: "Call a tool non-interactively instead of launching the inspector", type: "string" }).option("watch", { alias: "w", default: false, describe: "Watch for file changes and restart server", type: "boolean" }).option("verbose", { alias: "v", default: false, describe: "Enable verbose logging", type: "boolean" }); }, async (argv) => { let configDir; try { if (argv.args !== void 0 && !argv.tool) { console.error("[FastMCP Error] --args requires --tool"); process.exitCode = 1; return; } if (argv.tool && argv.watch) { console.warn( "[FastMCP] --watch is ignored when calling a tool with --tool: the server runs for a single call." ); } let configPath; if (argv.tool) { configDir = await mkdtemp(join(tmpdir(), "fastmcp-dev-")); configPath = join(configDir, "mcp-cli.json"); await writeFile(configPath, buildDevConfig(argv.file), "utf8"); } const [command, ...commandArgs] = buildDevCommand({ configPath, file: argv.file, tool: argv.tool, toolArgs: argv.args, watch: argv.watch }); if (argv.verbose) { console.log( `[FastMCP] Starting server: ${[command, ...commandArgs].join(" ")}` ); console.log(`[FastMCP] File: ${argv.file}`); console.log( `[FastMCP] Watch mode: ${argv.watch ? "enabled" : "disabled"}` ); if (argv.tool) { console.log(`[FastMCP] Tool: ${argv.tool}`); } } await execa(command, commandArgs, { stderr: "inherit", stdin: "inherit", stdout: "inherit" }); } catch (error) { console.error( "[FastMCP Error] Failed to start development server:", error instanceof Error ? error.message : String(error) ); if (argv.verbose && error instanceof Error && error.stack) { console.error("[FastMCP Debug] Stack trace:", error.stack); } process.exitCode = 1; } finally { if (configDir) { await rm(configDir, { force: true, recursive: true }); } } } ).command( "inspect <file>", "Inspect a server file", (yargs2) => { return yargs2.positional("file", { demandOption: true, describe: "The path to the server file", type: "string" }); }, async (argv) => { try { await execa({ stderr: "inherit", stdout: "inherit" })`npx @modelcontextprotocol/inspector npx tsx ${argv.file}`; } catch (error) { console.error( "[FastMCP Error] Failed to inspect server:", error instanceof Error ? error.message : String(error) ); process.exit(1); } } ).command( "validate <file>", "Validate a FastMCP server file for syntax and basic structure", (yargs2) => { return yargs2.positional("file", { demandOption: true, describe: "The path to the server file", type: "string" }).option("strict", { alias: "s", default: false, describe: "Enable strict validation (type checking)", type: "boolean" }); }, async (argv) => { try { const { existsSync } = await import("fs"); const { resolve } = await import("path"); const filePath = resolve(argv.file); if (!existsSync(filePath)) { console.error(`[FastMCP Error] File not found: ${filePath}`); process.exit(1); } console.log(`[FastMCP] Validating server file: ${filePath}`); const command = argv.strict ? `npx tsc --noEmit --strict ${filePath}` : `npx tsc --noEmit ${filePath}`; try { await execa({ shell: true, stderr: "pipe", stdout: "pipe" })`${command}`; console.log("[FastMCP] \u2713 TypeScript compilation successful"); } catch (tsError) { console.error("[FastMCP] \u2717 TypeScript compilation failed"); if (tsError instanceof Error && "stderr" in tsError) { console.error(tsError.stderr); } process.exit(1); } try { await execa({ shell: true, stderr: "pipe", stdout: "pipe" })`node -e " (async () => { try { const { FastMCP } = await import('fastmcp'); await import('file://${filePath}'); console.log('[FastMCP] ✓ Server structure validation passed'); } catch (error) { console.error('[FastMCP] ✗ Server structure validation failed:', error.message); process.exit(1); } })(); "`; } catch { console.error("[FastMCP] \u2717 Server structure validation failed"); console.error("Make sure the file properly imports and uses FastMCP"); process.exit(1); } console.log( "[FastMCP] \u2713 All validations passed! Server file looks good." ); } catch (error) { console.error( "[FastMCP Error] Validation failed:", error instanceof Error ? error.message : String(error) ); process.exit(1); } } ).help().parseAsync(); //# sourceMappingURL=fastmcp.js.map