diff --git a/.codex/config.toml b/.codex/config.toml index 6df36c1..723d4cb 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -1,20 +1,9 @@ #:schema https://developers.openai.com/codex/config-schema.json [mcp_servers.fpf_memory] -command = "node" -args = ["--import", "tsx", "src/mcp-stdio.ts"] +command = "bun" +args = ["src/mastra/stdio.ts"] cwd = "." -enabled_tools = [ - "ask_fpf", - "query_fpf_spec", - "get_fpf_index_status", - "refresh_fpf_index", - "read_fpf_doc", - "trace_fpf_path", - "inspect_fpf_node", - "inspect_fpf_anchor", - "expand_fpf_citations", -] required = false startup_timeout_sec = 15 tool_timeout_sec = 60 diff --git a/README.md b/README.md index 2aefce1..f744f13 100644 --- a/README.md +++ b/README.md @@ -122,32 +122,19 @@ Decision record for this interface choice: - [DRR-0001: MCP As The First-Class Codex Interface](docs/drr/DRR-0001-mcp-first-class-interface.md) -For Codex registration, use the direct Node stdio entry point instead of the Bun script wrapper: +For Codex registration: -- Command: `node` -- Arguments: `--import`, `tsx`, `src/mcp-stdio.ts` +- Command: `bun` +- Arguments: `src/mastra/stdio.ts` - Working directory: your local `fpf-memory` repo root -The desktop app fields map directly to those values. - Equivalent `~/.codex/config.toml` entry: ```toml [mcp_servers.fpf_memory] -command = "node" -args = ["--import", "tsx", "src/mcp-stdio.ts"] +command = "bun" +args = ["src/mastra/stdio.ts"] cwd = "/absolute/path/to/fpf-memory" -enabled_tools = [ - "ask_fpf", - "query_fpf_spec", - "get_fpf_index_status", - "refresh_fpf_index", - "read_fpf_doc", - "trace_fpf_path", - "inspect_fpf_node", - "inspect_fpf_anchor", - "expand_fpf_citations", -] required = false startup_timeout_sec = 15 tool_timeout_sec = 60 @@ -191,10 +178,10 @@ Run the end-to-end verification script for the real CLI, MCP stdio, and hosted H ./scripts/verify-runtime.sh ``` -The verification script also checks the direct Codex launcher: +The verification script also checks the direct stdio launcher (same entry as `bun run mcp`): ```bash -node --import tsx src/mcp-stdio.ts +bun src/mastra/stdio.ts ``` This starts a long-running stdio server; for a manual smoke check, stop it with `Ctrl+C` after startup confirmation. @@ -239,9 +226,10 @@ Call trace_fpf_path with: - `src/mcp/tool-contracts.ts`: Zod-authored MCP input and output contracts - `src/mcp/tools.ts`: canonical snake_case MCP tools and `ask_fpf` -- `src/mcp/server.ts`: Mastra MCP server boundary for stdio transport -- `src/mastra.ts`: Mastra runtime registration plus Hono server initialization -- `src/server.ts`: hosted Hono bootstrap for Bun +- `src/mastra/mcp/server.ts`: MCPServer definitions (public and full surfaces) +- `src/mastra/index.ts`: Mastra instance registration +- `src/mastra/stdio.ts`: stdio entry point for MCP clients +- `src/server.ts`: Hono HTTP server bootstrap for Bun - `src/runtime/`: compiler, retrieval, trace, inspect, and synthesis logic - `src/logging/runtime-logger.ts`: Mastra-backed structured runtime/MCP log writer - `src/observability/runtime-observability.ts`: Mastra-backed observability wrapper for local synthesis @@ -254,7 +242,7 @@ Call trace_fpf_path with: ## MCP tool roles -### Public tools (deployed HTTP surface) +### Public tools (default surface) - `ask_fpf`: return the grounded answer as markdown with IDs, citations, constraints, gaps, and snapshot metadata - `query_fpf_spec`: return the answer envelope with IDs, citations, constraints, and freshness metadata diff --git a/bun.lock b/bun.lock index 393b193..5ddc5a1 100644 --- a/bun.lock +++ b/bun.lock @@ -20,6 +20,7 @@ "@types/node": "^25.6.0", "bun-types": "^1.3.12", "mastra": "^1.5.0", + "tsup": "^8.5.1", "tsx": "^4.21.0", "typescript": "^6.0.2", }, @@ -468,6 +469,8 @@ "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + "arch": ["arch@2.2.0", "", {}, "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="], "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], @@ -528,8 +531,12 @@ "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + "bytes": ["bytes@3.0.0", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], @@ -554,6 +561,8 @@ "chat": ["chat@4.25.0", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" } }, "sha512-QM8ex4Gpn8zYIPyQXh41Who6R9Wq3WcQeOjAy4EuR1m1ha0tASuzHkLQfjaTAGLgrgrThV0Zh5KKoH0S92iwNA=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], "clipboardy": ["clipboardy@3.0.0", "", { "dependencies": { "arch": "^2.2.0", "execa": "^5.1.1", "is-wsl": "^2.2.0" } }, "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg=="], @@ -586,6 +595,8 @@ "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "content-disposition": ["content-disposition@0.5.2", "", {}, "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -614,7 +625,7 @@ "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], - "debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], @@ -750,6 +761,8 @@ "find-workspaces": ["find-workspaces@0.3.1", "", { "dependencies": { "fast-glob": "^3.3.2", "pkg-types": "^1.0.3", "yaml": "^2.3.4" } }, "sha512-UDkGILGJSA1LN5Aa7McxCid4sqW3/e+UYsVwyxki3dDT0F8+ym0rAfnCkEfkL0rO7M+8/mvkim4t/s3IPHmg+w=="], + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + "flexsearch": ["flexsearch@0.8.212", "", {}, "sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], @@ -926,6 +939,12 @@ "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], @@ -1084,6 +1103,8 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], @@ -1158,10 +1179,14 @@ "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + "posthog-node": ["posthog-node@5.17.2", "", { "dependencies": { "@posthog/core": "1.7.1" } }, "sha512-lz3YJOr0Nmiz0yHASaINEDHqoV+0bC3eD8aZAG+Ky292dAnVYul+ga/dMX8KCBXg8hHfKdxw0SztYD5j6dgUqQ=="], "prettier": ["prettier@3.8.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q=="], @@ -1218,6 +1243,8 @@ "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], "recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="], @@ -1366,6 +1393,8 @@ "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -1376,8 +1405,14 @@ "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="], + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], @@ -1390,12 +1425,18 @@ "tokenx": ["tokenx@1.3.0", "", {}, "sha512-NLdXTEZkKiO0gZuLtMoZKjCXTREXeZZt8nnnNeyoXtNZAfG/GKGSbQtLU5STspc0rMSwcA+UJfWZkbNU01iKmQ=="], + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], @@ -1504,8 +1545,6 @@ "@apidevtools/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - "@babel/core/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -1514,7 +1553,7 @@ "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/traverse/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "@expo/devcert/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], @@ -1534,8 +1573,6 @@ "body-parser/bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - "body-parser/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "chalk-template/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "clipboardy/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], @@ -1572,8 +1609,6 @@ "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], @@ -1590,10 +1625,6 @@ "readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], - "rollup-plugin-esbuild/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "router/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -1606,6 +1637,8 @@ "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "type-is/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -1626,8 +1659,6 @@ "@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], - "@modelcontextprotocol/sdk/express/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], "@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], diff --git a/docs/drr/DRR-0001-mcp-first-class-interface.md b/docs/drr/DRR-0001-mcp-first-class-interface.md index 16b37bd..67b2abe 100644 --- a/docs/drr/DRR-0001-mcp-first-class-interface.md +++ b/docs/drr/DRR-0001-mcp-first-class-interface.md @@ -30,7 +30,7 @@ The decision includes these commitments: 2. The CLI remains an operator/debug surface, not the primary semantic boundary for agent use. 3. Hosted HTTP remains a transport/hosting option, not the first interface to optimize for in this repo slice. 4. The Codex registration path is documented and packaged around the stdio entry point: - `node --import tsx src/mcp-stdio.ts` + `bun src/mastra/stdio.ts` 5. This decision is recorded as a DRR outside the normative FPF core, consistent with `E.9`. ## Rationale diff --git a/docs/mcp-interface.md b/docs/mcp-interface.md index c15a15a..254a6c5 100644 --- a/docs/mcp-interface.md +++ b/docs/mcp-interface.md @@ -20,52 +20,38 @@ The runtime itself is compiler-backed and local: ## Transport -- stdio (local): `bun run mcp` or `node --import tsx src/mcp-stdio.ts` -- HTTP (deployed): `https://fpf-memory.server.mastra.cloud/api/mcp/fpf_memory/mcp` -- SSE (deployed): `https://fpf-memory.server.mastra.cloud/api/mcp/fpf_memory/sse` +- stdio (local): `bun run mcp` +- HTTP (local): `http://localhost:4111/api/mcp/fpf_memory/mcp` via `mastra dev` - server name: `fpf_memory` - protocol version: `2024-11-05` -The deployed HTTP surface exposes public tools only. The stdio transport exposes all tools. +Both stdio and HTTP default to the public tool surface (3 tools). Set `FPF_MCP_SURFACE=full` for all 9 tools. ## Codex Setup Codex desktop app fields: -- command: `node` -- arguments: `--import`, `tsx`, `src/mcp-stdio.ts` +- command: `bun` +- arguments: `src/mastra/stdio.ts` - working directory: absolute path to the local repo root Equivalent `~/.codex/config.toml` entry: ```toml [mcp_servers.fpf_memory] -command = "node" -args = ["--import", "tsx", "src/mcp-stdio.ts"] +command = "bun" +args = ["src/mastra/stdio.ts"] cwd = "/absolute/path/to/fpf-memory" -enabled_tools = [ - "ask_fpf", - "query_fpf_spec", - "get_fpf_index_status", - "refresh_fpf_index", - "read_fpf_doc", - "trace_fpf_path", - "inspect_fpf_node", - "inspect_fpf_anchor", - "expand_fpf_citations", -] required = false startup_timeout_sec = 15 tool_timeout_sec = 60 ``` -`enabled_tools` is optional. The list above gives Codex all tools locally. The deployed HTTP surface only exposes the public subset (`ask_fpf`, `query_fpf_spec`, `get_fpf_index_status`). - This repo also ships the same project-scoped configuration at `.codex/config.toml`. Codex will load that file after the project is trusted. ## Tool Catalog -### Public tools (deployed HTTP surface) +### Public tools (default surface) #### `ask_fpf` @@ -79,7 +65,7 @@ Answer a question with deterministic grounding, citations, constraints, and fres Report whether the local index exists, whether it is fresh against the current source hash, and which artifacts are present. -### Expert tools (local stdio only) +### Expert tools (FPF_MCP_SURFACE=full) #### `refresh_fpf_index` @@ -125,5 +111,4 @@ bun run test bun run docs:build bun run cli -- read-doc --selector "A.1.1" bun run mcp -node --import tsx src/mcp-stdio.ts ``` diff --git a/manifest.json b/manifest.json index a72e2cb..4c776da 100644 --- a/manifest.json +++ b/manifest.json @@ -19,11 +19,9 @@ }, "transport": ["stdio", "http"], "runtime": { - "bun": "bun src/mcp-stdio.ts", - "node": "node --import tsx src/mcp-stdio.ts" + "bun": "bun src/mastra/stdio.ts" }, "http": { - "path": "/api/mcp/fpf_memory/mcp", - "sse_path": "/api/mcp/fpf_memory/sse" + "path": "/api/mcp/fpf_memory/mcp" } } diff --git a/package.json b/package.json index 2b040c2..f5623fa 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,22 @@ "name": "fpf-memory", "version": "1.0.0", "description": "Bun-first local vectorless FPF spec runtime exposed through Mastra MCP surfaces with a Hono server engine.", - "private": true, + "private": false, "packageManager": "bun@1.3.5", + "bin": "dist/stdio.js", + "files": [ + "dist", + "FPF-spec.md" + ], "scripts": { "dev": "bun --watch src/server.ts", - "build": "bun build ./src/cli.ts ./src/mcp-stdio.ts ./src/server.ts --outdir dist --target bun", + "build": "bun build ./src/cli.ts ./src/server.ts --outdir dist --target bun", + "build:mcp": "tsup src/mastra/stdio.ts --format esm,cjs --out-dir dist --no-splitting && node -e \"const fs=require('node:fs');const path='dist/stdio.js';const data=fs.readFileSync(path,'utf8');const shebang='#!/usr/bin/env node';if(!data.startsWith(shebang))fs.writeFileSync(path, shebang + '\\\\n' + data);\" && chmod +x dist/stdio.js", "start": "bun src/server.ts", "lint": "rslint --type-check src tests scripts/generate-docs.ts *.config.ts", "check": "tsc --noEmit", "cli": "bun src/cli.ts", - "mcp": "bun src/mcp-stdio.ts", + "mcp": "bun src/mastra/stdio.ts", "test": "rstest run", "docs:generate": "bun scripts/generate-docs.ts", "docs:dev": "bun run docs:generate && rspress", @@ -41,6 +47,7 @@ "@types/node": "^25.6.0", "bun-types": "^1.3.12", "mastra": "^1.5.0", + "tsup": "^8.5.1", "tsx": "^4.21.0", "typescript": "^6.0.2" } diff --git a/scripts/verify-runtime.sh b/scripts/verify-runtime.sh index 050ca1d..2cd3e41 100755 --- a/scripts/verify-runtime.sh +++ b/scripts/verify-runtime.sh @@ -41,13 +41,13 @@ trap - EXIT grep -q '"msg":"MCP stdio server start"' "$MAS_LOG" -printf '==> Starting MCP stdio server via Node/tsx briefly\n' +printf '==> Starting MCP stdio server directly via stdio entry briefly\n' node_before_size="$(wc -c <"$MAS_LOG" | tr -d ' ')" node_fifo="$(mktemp -u "${TMPDIR:-/tmp}/fpf-node-mcp.XXXXXX")" mkfifo "$node_fifo" exec 3<>"$node_fifo" rm -f "$node_fifo" -node --import tsx src/mcp-stdio.ts <&3 >/dev/null 2>&1 & +bun src/mastra/stdio.ts <&3 >/dev/null 2>&1 & node_mcp_pid="$!" trap 'kill "$node_mcp_pid" 2>/dev/null || true; wait "$node_mcp_pid" 2>/dev/null || true' EXIT sleep 2 diff --git a/server.json b/server.json index bc7ddcb..440e517 100644 --- a/server.json +++ b/server.json @@ -1,11 +1,11 @@ { "name": "fpf_memory", "version": "1.0.0", - "description": "Local vectorless FPF-spec runtime for Codex and other MCP clients.", + "description": "Local vectorless FPF-spec runtime for MCP clients.", "transport": "stdio", - "command": "node", + "command": "bun", "cwd": ".", - "args": ["--import", "tsx", "src/mcp-stdio.ts"], + "args": ["src/mastra/stdio.ts"], "env": { "FPF_SPEC_SOURCE_PATH": "FPF-spec.md", "FPF_RUNTIME_ARTIFACT_DIR": ".runtime/fpf-index" diff --git a/src/mastra/index.ts b/src/mastra/index.ts index 6f4aa1f..f8c1f1c 100644 --- a/src/mastra/index.ts +++ b/src/mastra/index.ts @@ -1,16 +1,15 @@ import { Mastra } from '@mastra/core/mastra'; -import { HonoBindings, HonoVariables, MastraServer } from '@mastra/hono'; -import { Hono } from 'hono'; import { getRuntimeLogger } from '../logging/runtime-logger.js'; -import { fpfMcpServer, fpfPublicMcpServer } from '../mcp/server.js'; +import { fpfMemory, fpfMemoryPublic } from './mcp/server.js'; import { getRuntimeObservability } from '../observability/runtime-observability.js'; export function createMastraRuntime(env: NodeJS.ProcessEnv = process.env) { const logger = getRuntimeLogger(env); const observability = getRuntimeObservability(env); - const mcpServer = env.FPF_MCP_SURFACE === 'public' ? fpfPublicMcpServer : fpfMcpServer; - const mastra = new Mastra({ + const mcpServer = env.FPF_MCP_SURFACE === 'full' ? fpfMemory : fpfMemoryPublic; + + return new Mastra({ logger, observability: observability.observability, mcpServers: { @@ -20,36 +19,10 @@ export function createMastraRuntime(env: NodeJS.ProcessEnv = process.env) { mcpOptions: { serverless: true }, }, }); - - return { - logger, - observability, - mastra, - }; } /** * Direct Mastra instance export required by `mastra build` / `mastra deploy`. * Deployed server sets FPF_MCP_SURFACE=public to restrict to 3 public tools. */ -export const mastra = createMastraRuntime().mastra; - -export async function createHonoMastraRuntime(env: NodeJS.ProcessEnv = process.env) { - const runtime = createMastraRuntime(env); - const app = new Hono<{ - Bindings: HonoBindings; - Variables: HonoVariables; - }>(); - const server = new MastraServer({ - app, - mastra: runtime.mastra, - }); - - await server.init(); - - return { - ...runtime, - app, - server, - }; -} +export const mastra = createMastraRuntime(); diff --git a/src/mastra/mcp/server.ts b/src/mastra/mcp/server.ts new file mode 100644 index 0000000..0a24847 --- /dev/null +++ b/src/mastra/mcp/server.ts @@ -0,0 +1,17 @@ +import { MCPServer } from '@mastra/mcp'; + +import { fpfMcpTools, fpfPublicTools } from '../../mcp/tools.js'; + +export const fpfMemory = new MCPServer({ + name: 'fpf_memory', + version: '1.0.0', + description: 'Local vectorless MCP runtime for FPF-spec.md with full tool surface.', + tools: fpfMcpTools, +}); + +export const fpfMemoryPublic = new MCPServer({ + name: 'fpf_memory', + version: '1.0.0', + description: 'FPF-spec query runtime with public tool surface (ask, query, status).', + tools: fpfPublicTools, +}); diff --git a/src/mastra/stdio.ts b/src/mastra/stdio.ts new file mode 100644 index 0000000..b583d4c --- /dev/null +++ b/src/mastra/stdio.ts @@ -0,0 +1,35 @@ +#!/usr/bin/env node +import { getRuntimeLogger } from '../logging/runtime-logger.js'; +import { fpfMemory, fpfMemoryPublic } from './mcp/server.js'; + +const logger = getRuntimeLogger(); +const server = process.env.FPF_MCP_SURFACE === 'full' ? fpfMemory : fpfMemoryPublic; + +logger.info('MCP stdio server start'); + +function normalizeErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + + if (typeof error === 'string') { + return error; + } + + try { + return JSON.stringify(error); + } catch { + return String(error); + } +} + +server + .startStdio() + .catch(async (error) => { + logger.error('MCP stdio server failed', { + error: normalizeErrorMessage(error), + cause: error instanceof Error ? error.stack ?? error : error, + }); + await new Promise((resolve) => setImmediate(resolve)); + process.exit(1); + }); diff --git a/src/mcp-stdio.ts b/src/mcp-stdio.ts deleted file mode 100644 index 6c9621e..0000000 --- a/src/mcp-stdio.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { getRuntimeLogger } from './logging/runtime-logger.js'; -import { startStdioMcpServer } from './mcp/server.js'; - -const logger = getRuntimeLogger(); - -logger.info('MCP stdio server start'); - -try { - await startStdioMcpServer(); -} catch (error) { - logger.error('MCP stdio server failed', { - error: error instanceof Error ? error.message : 'Unknown MCP stdio error', - }); - throw error; -} diff --git a/src/mcp/server.ts b/src/mcp/server.ts deleted file mode 100644 index e66fc9c..0000000 --- a/src/mcp/server.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { MCPServer } from '@mastra/mcp'; - -import { fpfMcpTools, fpfPublicTools } from './tools.js'; - -export type McpToolSurface = 'public' | 'full'; - -export function createFpfMcpServer(surface: McpToolSurface = 'full'): MCPServer { - const tools = surface === 'public' ? fpfPublicTools : fpfMcpTools; - return new MCPServer({ - name: 'fpf_memory', - version: '1.0.0', - description: - surface === 'public' - ? 'FPF-spec query runtime with answer, structured query, and status tools.' - : 'Local vectorless MCP runtime for FPF-spec.md with refresh, query, trace, status, node inspect, direct doc read, anchor inspect, citation expansion, and markdown answer tools.', - tools, - }); -} - -/** Deployed MCP — public tools only. */ -export const fpfPublicMcpServer = createFpfMcpServer('public'); - -/** Local MCP — all tools. */ -export const fpfMcpServer = createFpfMcpServer('full'); - -export async function startStdioMcpServer(): Promise { - await fpfMcpServer.startStdio(); -} diff --git a/src/server.ts b/src/server.ts index 83b0491..b31bc1f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,17 +1,27 @@ +import { HonoBindings, HonoVariables, MastraServer } from '@mastra/hono'; +import { Hono } from 'hono'; + import { getRuntimeLogger } from './logging/runtime-logger.js'; -import { createHonoMastraRuntime } from './mastra/index.js'; +import { createMastraRuntime } from './mastra/index.js'; import { parsePort } from './server-config.js'; const logger = getRuntimeLogger(); const port = parsePort(process.env.PORT); +const mastra = createMastraRuntime(); + +const app = new Hono<{ + Bindings: HonoBindings; + Variables: HonoVariables; +}>(); -const { app } = await createHonoMastraRuntime(); +const server = new MastraServer({ app, mastra }); +await server.init(); -const server = Bun.serve({ +const httpServer = Bun.serve({ fetch: app.fetch, port, }); logger.info('Mastra Hono server start', { - port: server.port, + port: httpServer.port, }); diff --git a/tests/mcp-server.test.ts b/tests/mcp-server.test.ts index ab0db97..ead399c 100644 --- a/tests/mcp-server.test.ts +++ b/tests/mcp-server.test.ts @@ -6,7 +6,7 @@ import readline from 'node:readline'; import { afterEach, describe, expect, it } from '@rstest/core'; -import { createHonoMastraRuntime } from '../src/mastra/index.js'; +import { createMastraRuntime } from '../src/mastra/index.js'; interface JsonRpcResponse { jsonrpc: '2.0'; @@ -130,14 +130,15 @@ describe('Mastra MCP server', () => { await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); - async function startHarness(): Promise { + async function startHarness(surface: 'public' | 'full' = 'full'): Promise { const tempDir = await mkdtemp(resolve(tmpdir(), 'fpf-mcp-stdio-')); tempDirs.push(tempDir); - const child = spawn('bun', ['src/mcp-stdio.ts'], { + const child = spawn('bun', ['src/mastra/stdio.ts'], { cwd: process.cwd(), env: { ...process.env, + ...(surface === 'full' ? { FPF_MCP_SURFACE: 'full' } : {}), FPF_MASTRA_LOG_PATH: resolve(tempDir, 'mastra.log'), FPF_MASTRA_OBSERVABILITY_PATH: resolve(tempDir, 'mastra-observability.json'), }, @@ -269,12 +270,22 @@ describe('Mastra MCP server', () => { expect(askPayload.markdown).toContain('## Grounding'); }); - it('initializes the hosted Mastra runtime on the Hono engine', async () => { - const runtime = await createHonoMastraRuntime(); + it('defaults to public tools when FPF_MCP_SURFACE is unset', async () => { + const harness = await startHarness('public'); + await initializeHarness(harness); + + const toolsList = await harness.request('tools/list'); + const tools = (toolsList.result?.tools ?? []) as Array<{ name: string }>; + expect(tools.map((tool) => tool.name)).toEqual([ + 'ask_fpf', + 'query_fpf_spec', + 'get_fpf_index_status', + ]); + }); - expect(typeof runtime.app.fetch).toBe('function'); - expect(runtime.mastra).toBeDefined(); - expect(runtime.server).toBeDefined(); + it('initializes the Mastra runtime', () => { + const mastra = createMastraRuntime(); + expect(mastra).toBeDefined(); }); });