UNPKG

modelfetch

Version:

CLI for MCP servers built with ModelFetch

164 lines 5.3 kB
import { loadConfig } from "c12"; import { Command } from "commander"; import getPort from "get-port"; import { get as detectRuntime } from "js-runtime"; import { exec as execCommand, spawn } from "node:child_process"; import { access, mkdir, readdir, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { promisify } from "node:util"; import packageJson from "../package.json" with { type: "json" }; const exec = promisify(execCommand); async function detectDefaultServer() { // Check static files const staticFiles = [ "src/server.ts", "src/server.js", "netlify/server.ts", "netlify/server.js", ]; for (const staticFile of staticFiles) { try { await access(path.join(process.cwd(), staticFile)); return `./${staticFile}`; } catch { // Ignore } } // Check for Next.js dynamic routes try { const dirs = await readdir(path.join(process.cwd(), "app")); for (const dir of dirs) { if (dir.startsWith("[[...") && dir.endsWith("]]")) { for (const ext of ["ts", "js"]) { const serverPath = `app/${dir}/server.${ext}`; try { await access(path.join(process.cwd(), serverPath)); return `./${serverPath}`; } catch { // Ignore } } } } } catch { // Ignore } // Check for Supabase functions try { const dirs = await readdir(path.join(process.cwd(), "supabase", "functions")); for (const dir of dirs) { for (const ext of ["ts", "js"]) { const serverPath = `supabase/functions/${dir}/server.${ext}`; try { await access(path.join(process.cwd(), serverPath)); return `./${serverPath}`; } catch { // Ignore } } } } catch { // Ignore } return ""; } function getRunArgs(runtime) { switch (runtime) { case "deno": { return ["deno", "run", "-Aq"]; } default: { return ["bun", "run", "--silent", "--no-deprecation"]; } } } function getHotReloadArgs(runtime) { const runArgs = getRunArgs(runtime); switch (runtime) { case "deno": { return [...runArgs, "--watch-hmr", "--no-clear-screen"]; } default: { return [...runArgs, "--hot", "--no-clear-screen"]; } } } const killSignals = ["SIGINT", "SIGTERM"]; const program = new Command(); program .name(packageJson.name) .description(packageJson.description) .version(packageJson.version); program .command("dev") .description("start the MCP Inspector") .action(async () => { const { config } = await loadConfig({ name: "modelfetch", defaults: { server: await detectDefaultServer() }, }); if (!config.server) throw new Error("config.server is required"); const dotDir = path.join(process.cwd(), ".modelfetch"); await mkdir(dotDir, { recursive: true }); const runtime = detectRuntime(); if (runtime === "node" || runtime === "jest") { try { await exec("bun --version"); } catch { throw new Error("Bun (https://bun.com) is required"); } } const serverPath = path.join(process.cwd(), config.server); const serverPort = await getPort({ port: 3000 }); const mainPath = path.join(dotDir, `main${path.extname(config.server)}`); const mainContent = ` import handle from "modelfetch"; import serverInstance from "${pathToFileURL(serverPath).toString()}"; handle("${runtime}", serverInstance, ${serverPort}); `; await writeFile(mainPath, mainContent, "utf8"); const configPath = path.join(dotDir, "main.json"); const configContent = JSON.stringify({ mcpServers: { "mcp-server": { type: "streamable-http", url: `http://localhost:${serverPort}/mcp`, }, }, }); await writeFile(configPath, configContent, "utf8"); const [hotReloadCommand, ...hotReloadArgs] = getHotReloadArgs(runtime); const server = spawn(hotReloadCommand, [...hotReloadArgs, mainPath], { stdio: ["ignore", "inherit", "inherit"], }); server.once("exit", (code) => { process.exit(code); }); const [runCommand, ...runArgs] = getRunArgs(runtime); const inspector = spawn(runCommand, [ ...runArgs, fileURLToPath(import.meta.resolve("@modelcontextprotocol/inspector/cli/build/cli.js")), "--", "--config", configPath, ], { stdio: "inherit" }); inspector.once("exit", (code) => { process.exit(code); }); for (const killSignal of killSignals) { process.once(killSignal, () => { server.kill(killSignal); inspector.kill(killSignal); }); } }); await program.parseAsync(process.argv); //# sourceMappingURL=cli.js.map