@prefecthq/fastmcp-ts 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -74,11 +74,6 @@ async function main() {
74
74
  return;
75
75
  }
76
76
  await resolved.run();
77
- if (resolved.address) {
78
- const { host, port, path } = resolved.address;
79
- process.stderr.write(`listening on http://${host}:${port}${path}
80
- `);
81
- }
82
77
  }
83
78
  main().catch((err) => {
84
79
  console.error(err);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/entrypoint-runtime.ts"],"sourcesContent":["/**\n * Entrypoint bootstrap — spawned by the CLI (`run`, `inspect`, `list`, `call`,\n * `dev inspector`) instead of the user's server file directly.\n *\n * This mirrors Python FastMCP's `FileSystemSource` entrypoint resolution\n * (`fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py`): import the\n * user's file, resolve a named export (or a factory function that returns a\n * server), and start it. Keeping this in a spawned subprocess (rather than\n * importing the user's file in the CLI's own process) preserves two things the\n * existing CLI already relies on: `tsx` transpilation for TypeScript files, and\n * process isolation so a crashing user server doesn't take down the CLI.\n *\n * Contract (set by the parent CLI process via environment variables):\n * - FASTMCP_ENTRYPOINT_FILE: absolute path to the user's server file (required)\n * - FASTMCP_ENTRYPOINT_EXPORT: export name to resolve. Omitted when the user\n * didn't specify one, in which case a conventional name is auto-detected.\n *\n * Resolution order when FASTMCP_ENTRYPOINT_EXPORT is not set:\n * `default`, `mcp`, `server`, `app` — the first of these that is already a\n * FastMCP instance wins. Auto-detection deliberately does NOT call factory\n * functions (mirroring Python FastMCP's `_find_server_object`, which only\n * resolves factories for an explicit entrypoint) — invoking an arbitrary\n * exported function as a side effect of guessing a name would be surprising,\n * and a name match that isn't a FastMCP instance is skipped in favor of the\n * next candidate rather than committing to it. Factory functions are only\n * resolved when the export name is given explicitly (`file:export` or\n * `--export`).\n *\n * Backwards compatibility: if no explicit export was requested and none of the\n * conventional names resolve to a valid entrypoint, this script assumes the\n * file already started its own server via top-level side effects (the\n * historical fastmcp-ts contract, e.g. `server.run()` at module scope) and\n * exits without error. An explicit export request that can't be resolved is\n * always a hard error.\n */\n\nimport { pathToFileURL } from 'node:url'\n\nconst AUTO_DETECT_NAMES = ['default', 'mcp', 'server', 'app']\n\ninterface RunnableFastMCP {\n run(): Promise<void>\n isRunning: boolean\n address: { host: string; port: number; path: string } | null\n}\n\nfunction isFastMCPInstance(value: unknown): value is RunnableFastMCP {\n if (!value || typeof value !== 'object') return false\n const obj = value as Record<string, unknown>\n return (\n typeof obj['run'] === 'function' &&\n typeof obj['tool'] === 'function' &&\n typeof obj['resource'] === 'function' &&\n typeof obj['prompt'] === 'function' &&\n typeof obj['connect'] === 'function'\n )\n}\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'an array'\n return `a ${typeof value}`\n}\n\nfunction fail(message: string): never {\n console.error(`fastmcp: ${message}`)\n process.exit(1)\n}\n\nasync function resolveFactory(\n value: unknown,\n exportName: string,\n filePath: string,\n): Promise<unknown> {\n if (typeof value !== 'function') return value\n try {\n return await (value as () => unknown)()\n } catch (err) {\n fail(\n `Failed to call entrypoint factory \"${exportName}\" in ${filePath}: ` +\n (err instanceof Error ? err.message : String(err)),\n )\n }\n}\n\nasync function main(): Promise<void> {\n const filePath = process.env['FASTMCP_ENTRYPOINT_FILE']\n if (!filePath) {\n fail('internal error: FASTMCP_ENTRYPOINT_FILE was not set for the entrypoint bootstrap')\n }\n\n const explicitExport = process.env['FASTMCP_ENTRYPOINT_EXPORT']\n\n let mod: Record<string, unknown>\n try {\n mod = (await import(pathToFileURL(filePath).href)) as Record<string, unknown>\n } catch (err) {\n fail(\n `Failed to load entrypoint file \"${filePath}\": ` +\n (err instanceof Error ? (err.stack ?? err.message) : String(err)),\n )\n }\n\n let resolved: RunnableFastMCP | undefined\n\n if (explicitExport) {\n if (!(explicitExport in mod)) {\n const available = Object.keys(mod).filter((k) => k !== '__esModule')\n fail(\n `Entrypoint export \"${explicitExport}\" not found in ${filePath}.` +\n (available.length > 0\n ? ` Available exports: ${available.join(', ')}.`\n : ' The file has no exports.'),\n )\n }\n const target = await resolveFactory(mod[explicitExport], explicitExport, filePath)\n if (!isFastMCPInstance(target)) {\n fail(\n `Entrypoint export \"${explicitExport}\" in ${filePath} is not a FastMCP server ` +\n `(or a function that returns one). Got ${describe(target)}.`,\n )\n }\n resolved = target\n } else {\n for (const name of AUTO_DETECT_NAMES) {\n if (!(name in mod)) continue\n const candidate = mod[name]\n // Only a direct FastMCP instance is auto-detected — a name match that\n // isn't one (including a function, which would require calling it) is\n // skipped in favor of the next conventional name rather than erroring\n // or invoking it speculatively.\n if (isFastMCPInstance(candidate)) {\n resolved = candidate\n break\n }\n }\n if (resolved === undefined) {\n // No conventional export resolved to a FastMCP instance. Assume this is\n // a legacy self-running file (it started its own server via top-level\n // side effects when imported above) and exit quietly.\n return\n }\n }\n\n if (resolved.isRunning) {\n // The file already started this server itself (e.g. a top-level\n // `server.run()` call) before we got a chance to. Don't start it twice.\n return\n }\n\n await resolved.run()\n\n // Mirrors the \"listening on <url>\" line the raw-SDK CLI test fixtures print\n // themselves — FastMCP's own run() doesn't emit a startup banner, but the\n // CLI's `run` command watches stderr for this text to detect a successful\n // HTTP start (see spawnServer()'s \"started\" detection in commands/run.ts).\n if (resolved.address) {\n const { host, port, path } = resolved.address\n process.stderr.write(`listening on http://${host}:${port}${path}\\n`)\n }\n}\n\nmain().catch((err) => {\n console.error(err)\n process.exit(1)\n})\n"],"mappings":";;;;AAoCA,sBAA8B;AAE9B,IAAM,oBAAoB,CAAC,WAAW,OAAO,UAAU,KAAK;AAQ5D,SAAS,kBAAkB,OAA0C;AACnE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,MAAM;AACZ,SACE,OAAO,IAAI,KAAK,MAAM,cACtB,OAAO,IAAI,MAAM,MAAM,cACvB,OAAO,IAAI,UAAU,MAAM,cAC3B,OAAO,IAAI,QAAQ,MAAM,cACzB,OAAO,IAAI,SAAS,MAAM;AAE9B;AAEA,SAAS,SAAS,OAAwB;AACxC,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO,KAAK,OAAO,KAAK;AAC1B;AAEA,SAAS,KAAK,SAAwB;AACpC,UAAQ,MAAM,YAAY,OAAO,EAAE;AACnC,UAAQ,KAAK,CAAC;AAChB;AAEA,eAAe,eACb,OACA,YACA,UACkB;AAClB,MAAI,OAAO,UAAU,WAAY,QAAO;AACxC,MAAI;AACF,WAAO,MAAO,MAAwB;AAAA,EACxC,SAAS,KAAK;AACZ;AAAA,MACE,sCAAsC,UAAU,QAAQ,QAAQ,QAC7D,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACpD;AAAA,EACF;AACF;AAEA,eAAe,OAAsB;AACnC,QAAM,WAAW,QAAQ,IAAI,yBAAyB;AACtD,MAAI,CAAC,UAAU;AACb,SAAK,kFAAkF;AAAA,EACzF;AAEA,QAAM,iBAAiB,QAAQ,IAAI,2BAA2B;AAE9D,MAAI;AACJ,MAAI;AACF,UAAO,MAAM,WAAO,+BAAc,QAAQ,EAAE;AAAA,EAC9C,SAAS,KAAK;AACZ;AAAA,MACE,mCAAmC,QAAQ,SACxC,eAAe,QAAS,IAAI,SAAS,IAAI,UAAW,OAAO,GAAG;AAAA,IACnE;AAAA,EACF;AAEA,MAAI;AAEJ,MAAI,gBAAgB;AAClB,QAAI,EAAE,kBAAkB,MAAM;AAC5B,YAAM,YAAY,OAAO,KAAK,GAAG,EAAE,OAAO,CAAC,MAAM,MAAM,YAAY;AACnE;AAAA,QACE,sBAAsB,cAAc,kBAAkB,QAAQ,OAC3D,UAAU,SAAS,IAChB,uBAAuB,UAAU,KAAK,IAAI,CAAC,MAC3C;AAAA,MACR;AAAA,IACF;AACA,UAAM,SAAS,MAAM,eAAe,IAAI,cAAc,GAAG,gBAAgB,QAAQ;AACjF,QAAI,CAAC,kBAAkB,MAAM,GAAG;AAC9B;AAAA,QACE,sBAAsB,cAAc,QAAQ,QAAQ,kEACT,SAAS,MAAM,CAAC;AAAA,MAC7D;AAAA,IACF;AACA,eAAW;AAAA,EACb,OAAO;AACL,eAAW,QAAQ,mBAAmB;AACpC,UAAI,EAAE,QAAQ,KAAM;AACpB,YAAM,YAAY,IAAI,IAAI;AAK1B,UAAI,kBAAkB,SAAS,GAAG;AAChC,mBAAW;AACX;AAAA,MACF;AAAA,IACF;AACA,QAAI,aAAa,QAAW;AAI1B;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,WAAW;AAGtB;AAAA,EACF;AAEA,QAAM,SAAS,IAAI;AAMnB,MAAI,SAAS,SAAS;AACpB,UAAM,EAAE,MAAM,MAAM,KAAK,IAAI,SAAS;AACtC,YAAQ,OAAO,MAAM,uBAAuB,IAAI,IAAI,IAAI,GAAG,IAAI;AAAA,CAAI;AAAA,EACrE;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,GAAG;AACjB,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/cli/entrypoint-runtime.ts"],"sourcesContent":["/**\n * Entrypoint bootstrap — spawned by the CLI (`run`, `inspect`, `list`, `call`,\n * `dev inspector`) instead of the user's server file directly.\n *\n * This mirrors Python FastMCP's `FileSystemSource` entrypoint resolution\n * (`fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py`): import the\n * user's file, resolve a named export (or a factory function that returns a\n * server), and start it. Keeping this in a spawned subprocess (rather than\n * importing the user's file in the CLI's own process) preserves two things the\n * existing CLI already relies on: `tsx` transpilation for TypeScript files, and\n * process isolation so a crashing user server doesn't take down the CLI.\n *\n * Contract (set by the parent CLI process via environment variables):\n * - FASTMCP_ENTRYPOINT_FILE: absolute path to the user's server file (required)\n * - FASTMCP_ENTRYPOINT_EXPORT: export name to resolve. Omitted when the user\n * didn't specify one, in which case a conventional name is auto-detected.\n *\n * Resolution order when FASTMCP_ENTRYPOINT_EXPORT is not set:\n * `default`, `mcp`, `server`, `app` — the first of these that is already a\n * FastMCP instance wins. Auto-detection deliberately does NOT call factory\n * functions (mirroring Python FastMCP's `_find_server_object`, which only\n * resolves factories for an explicit entrypoint) — invoking an arbitrary\n * exported function as a side effect of guessing a name would be surprising,\n * and a name match that isn't a FastMCP instance is skipped in favor of the\n * next candidate rather than committing to it. Factory functions are only\n * resolved when the export name is given explicitly (`file:export` or\n * `--export`).\n *\n * Backwards compatibility: if no explicit export was requested and none of the\n * conventional names resolve to a valid entrypoint, this script assumes the\n * file already started its own server via top-level side effects (the\n * historical fastmcp-ts contract, e.g. `server.run()` at module scope) and\n * exits without error. An explicit export request that can't be resolved is\n * always a hard error.\n */\n\nimport { pathToFileURL } from 'node:url'\n\nconst AUTO_DETECT_NAMES = ['default', 'mcp', 'server', 'app']\n\ninterface RunnableFastMCP {\n run(): Promise<void>\n isRunning: boolean\n address: { host: string; port: number; path: string } | null\n}\n\nfunction isFastMCPInstance(value: unknown): value is RunnableFastMCP {\n if (!value || typeof value !== 'object') return false\n const obj = value as Record<string, unknown>\n return (\n typeof obj['run'] === 'function' &&\n typeof obj['tool'] === 'function' &&\n typeof obj['resource'] === 'function' &&\n typeof obj['prompt'] === 'function' &&\n typeof obj['connect'] === 'function'\n )\n}\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'an array'\n return `a ${typeof value}`\n}\n\nfunction fail(message: string): never {\n console.error(`fastmcp: ${message}`)\n process.exit(1)\n}\n\nasync function resolveFactory(\n value: unknown,\n exportName: string,\n filePath: string,\n): Promise<unknown> {\n if (typeof value !== 'function') return value\n try {\n return await (value as () => unknown)()\n } catch (err) {\n fail(\n `Failed to call entrypoint factory \"${exportName}\" in ${filePath}: ` +\n (err instanceof Error ? err.message : String(err)),\n )\n }\n}\n\nasync function main(): Promise<void> {\n const filePath = process.env['FASTMCP_ENTRYPOINT_FILE']\n if (!filePath) {\n fail('internal error: FASTMCP_ENTRYPOINT_FILE was not set for the entrypoint bootstrap')\n }\n\n const explicitExport = process.env['FASTMCP_ENTRYPOINT_EXPORT']\n\n let mod: Record<string, unknown>\n try {\n mod = (await import(pathToFileURL(filePath).href)) as Record<string, unknown>\n } catch (err) {\n fail(\n `Failed to load entrypoint file \"${filePath}\": ` +\n (err instanceof Error ? (err.stack ?? err.message) : String(err)),\n )\n }\n\n let resolved: RunnableFastMCP | undefined\n\n if (explicitExport) {\n if (!(explicitExport in mod)) {\n const available = Object.keys(mod).filter((k) => k !== '__esModule')\n fail(\n `Entrypoint export \"${explicitExport}\" not found in ${filePath}.` +\n (available.length > 0\n ? ` Available exports: ${available.join(', ')}.`\n : ' The file has no exports.'),\n )\n }\n const target = await resolveFactory(mod[explicitExport], explicitExport, filePath)\n if (!isFastMCPInstance(target)) {\n fail(\n `Entrypoint export \"${explicitExport}\" in ${filePath} is not a FastMCP server ` +\n `(or a function that returns one). Got ${describe(target)}.`,\n )\n }\n resolved = target\n } else {\n for (const name of AUTO_DETECT_NAMES) {\n if (!(name in mod)) continue\n const candidate = mod[name]\n // Only a direct FastMCP instance is auto-detected — a name match that\n // isn't one (including a function, which would require calling it) is\n // skipped in favor of the next conventional name rather than erroring\n // or invoking it speculatively.\n if (isFastMCPInstance(candidate)) {\n resolved = candidate\n break\n }\n }\n if (resolved === undefined) {\n // No conventional export resolved to a FastMCP instance. Assume this is\n // a legacy self-running file (it started its own server via top-level\n // side effects when imported above) and exit quietly.\n return\n }\n }\n\n if (resolved.isRunning) {\n // The file already started this server itself (e.g. a top-level\n // `server.run()` call) before we got a chance to. Don't start it twice.\n return\n }\n\n await resolved.run()\n\n // FastMCP.run() now logs its own 'listening on <url>' line (framework lifecycle logs), which the CLI's run command sniffs on stderr; no manual line needed here.\n}\n\nmain().catch((err) => {\n console.error(err)\n process.exit(1)\n})\n"],"mappings":";;;;AAoCA,sBAA8B;AAE9B,IAAM,oBAAoB,CAAC,WAAW,OAAO,UAAU,KAAK;AAQ5D,SAAS,kBAAkB,OAA0C;AACnE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,MAAM;AACZ,SACE,OAAO,IAAI,KAAK,MAAM,cACtB,OAAO,IAAI,MAAM,MAAM,cACvB,OAAO,IAAI,UAAU,MAAM,cAC3B,OAAO,IAAI,QAAQ,MAAM,cACzB,OAAO,IAAI,SAAS,MAAM;AAE9B;AAEA,SAAS,SAAS,OAAwB;AACxC,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO,KAAK,OAAO,KAAK;AAC1B;AAEA,SAAS,KAAK,SAAwB;AACpC,UAAQ,MAAM,YAAY,OAAO,EAAE;AACnC,UAAQ,KAAK,CAAC;AAChB;AAEA,eAAe,eACb,OACA,YACA,UACkB;AAClB,MAAI,OAAO,UAAU,WAAY,QAAO;AACxC,MAAI;AACF,WAAO,MAAO,MAAwB;AAAA,EACxC,SAAS,KAAK;AACZ;AAAA,MACE,sCAAsC,UAAU,QAAQ,QAAQ,QAC7D,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACpD;AAAA,EACF;AACF;AAEA,eAAe,OAAsB;AACnC,QAAM,WAAW,QAAQ,IAAI,yBAAyB;AACtD,MAAI,CAAC,UAAU;AACb,SAAK,kFAAkF;AAAA,EACzF;AAEA,QAAM,iBAAiB,QAAQ,IAAI,2BAA2B;AAE9D,MAAI;AACJ,MAAI;AACF,UAAO,MAAM,WAAO,+BAAc,QAAQ,EAAE;AAAA,EAC9C,SAAS,KAAK;AACZ;AAAA,MACE,mCAAmC,QAAQ,SACxC,eAAe,QAAS,IAAI,SAAS,IAAI,UAAW,OAAO,GAAG;AAAA,IACnE;AAAA,EACF;AAEA,MAAI;AAEJ,MAAI,gBAAgB;AAClB,QAAI,EAAE,kBAAkB,MAAM;AAC5B,YAAM,YAAY,OAAO,KAAK,GAAG,EAAE,OAAO,CAAC,MAAM,MAAM,YAAY;AACnE;AAAA,QACE,sBAAsB,cAAc,kBAAkB,QAAQ,OAC3D,UAAU,SAAS,IAChB,uBAAuB,UAAU,KAAK,IAAI,CAAC,MAC3C;AAAA,MACR;AAAA,IACF;AACA,UAAM,SAAS,MAAM,eAAe,IAAI,cAAc,GAAG,gBAAgB,QAAQ;AACjF,QAAI,CAAC,kBAAkB,MAAM,GAAG;AAC9B;AAAA,QACE,sBAAsB,cAAc,QAAQ,QAAQ,kEACT,SAAS,MAAM,CAAC;AAAA,MAC7D;AAAA,IACF;AACA,eAAW;AAAA,EACb,OAAO;AACL,eAAW,QAAQ,mBAAmB;AACpC,UAAI,EAAE,QAAQ,KAAM;AACpB,YAAM,YAAY,IAAI,IAAI;AAK1B,UAAI,kBAAkB,SAAS,GAAG;AAChC,mBAAW;AACX;AAAA,MACF;AAAA,IACF;AACA,QAAI,aAAa,QAAW;AAI1B;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,WAAW;AAGtB;AAAA,EACF;AAEA,QAAM,SAAS,IAAI;AAGrB;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,GAAG;AACjB,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}