Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -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
36 changes: 12 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section says verify-runtime.sh checks the “direct stdio launcher” using bun src/mastra/stdio.ts, but the script currently still starts the direct launcher via node --import tsx src/mcp-stdio.ts. Update either the script or this documentation so they match; otherwise readers will follow a verification path that the script doesn’t actually cover.

Suggested change
bun src/mastra/stdio.ts
node --import tsx src/mcp-stdio.ts

Copilot uses AI. Check for mistakes.
```

This starts a long-running stdio server; for a manual smoke check, stop it with `Ctrl+C` after startup confirmation.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
59 changes: 45 additions & 14 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/drr/DRR-0001-mcp-first-class-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 9 additions & 24 deletions docs/mcp-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand All @@ -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`

Expand Down Expand Up @@ -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
```
6 changes: 2 additions & 4 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"files": [
"dist",
"FPF-spec.md"
],
Comment on lines 6 to +11
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bin points at dist/stdio.js, but the current source entry (src/mastra/stdio.ts) doesn’t include a shebang. Many package managers invoke bin files directly (symlink), so dist/stdio.js should include #!/usr/bin/env node (or an explicit Bun shebang) to be executable; chmod +x alone won’t make it runnable as a script.

Copilot uses AI. Check for mistakes.
"scripts": {
"dev": "bun --watch src/server.ts",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ensure the default build emits the declared CLI bin

package.json now declares "bin": "dist/stdio.js", but the build script only compiles src/cli.ts and src/server.ts, so the standard build path leaves the published bin missing unless build:mcp is run separately. In environments that run only bun run build before packing/publishing (as suggested by the repo workflow), installs will produce a package whose CLI entrypoint does not exist.

Useful? React with 👍 / 👎.

"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",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build declared bin artifact before packaging

The package is now publishable ("private": false) and declares "bin": "dist/stdio.js", but the main build script no longer produces that file. On a clean checkout, bun run build emits only dist/cli.js and dist/server.js, so a normal pack/publish flow can ship a package whose executable entrypoint is missing. This makes the installed CLI unusable unless maintainers remember to run build:mcp manually; adding the stdio build to the default packaging path (e.g., build/prepack) would prevent broken releases.

Useful? React with 👍 / 👎.

"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",
Expand Down Expand Up @@ -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"
}
Expand Down
4 changes: 2 additions & 2 deletions scripts/verify-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions server.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
37 changes: 5 additions & 32 deletions src/mastra/index.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -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();
17 changes: 17 additions & 0 deletions src/mastra/mcp/server.ts
Original file line number Diff line number Diff line change
@@ -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,
});
Comment on lines +5 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The MCPServer instances are missing the description field. Providing a clear description is a best practice for MCP servers as it helps clients (like Claude Desktop or Codex) and users understand the server's purpose and capabilities. The previous implementation had detailed descriptions that should be restored.

Suggested change
export const fpfMemory = new MCPServer({
name: 'fpf_memory',
version: '1.0.0',
tools: fpfMcpTools,
});
export const fpfMemoryPublic = new MCPServer({
name: 'fpf_memory',
version: '1.0.0',
tools: fpfPublicTools,
});
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,
});

35 changes: 35 additions & 0 deletions src/mastra/stdio.ts
Original file line number Diff line number Diff line change
@@ -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<void>((resolve) => setImmediate(resolve));
process.exit(1);
});
15 changes: 0 additions & 15 deletions src/mcp-stdio.ts

This file was deleted.

Loading