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
7 changes: 5 additions & 2 deletions .codex/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ cwd = "."
enabled_tools = [
"ask_fpf",
"query_fpf_spec",
"read_fpf_doc",
"trace_fpf_path",
"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
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ doc_build
output.txt
*.db
*.db-*
.mastra-project.json
package-lock.json
.DS_Store
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

Use the `fpf_memory` MCP server whenever the task requires grounded answers, exact FPF IDs, canonical generated docs, or deterministic retrieval provenance from the local `FPF-spec.md` runtime.

Prefer these tools:
Public tools (deployed MCP surface):

- `ask_fpf` for markdown-first answers
- `query_fpf_spec` for structured answer envelopes
- `get_fpf_index_status` for runtime freshness checks

Expert tools (local stdio only, via `bun run mcp`):

- `read_fpf_doc` for exact generated markdown pages
- `trace_fpf_path` for retrieval evidence and provenance
- `get_fpf_index_status` or `refresh_fpf_index` for runtime freshness checks
- `inspect_fpf_node`, `inspect_fpf_anchor`, `expand_fpf_citations` for deep inspection
- `refresh_fpf_index` to rebuild the local artifact set
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,13 @@ cwd = "/absolute/path/to/fpf-memory"
enabled_tools = [
"ask_fpf",
"query_fpf_spec",
"read_fpf_doc",
"trace_fpf_path",
"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
Expand All @@ -161,9 +164,13 @@ bun run mcp
Recommended Codex tasks:

- answer a question: `Use only the fpf_memory MCP server. Call ask_fpf with question: "What is U.PromiseContent?"`
- structured query: `Use only the fpf_memory MCP server. Call query_fpf_spec with question: "What is an FPF pattern?"`
- check runtime freshness: `Use only the fpf_memory MCP server. Call get_fpf_index_status`

Expert tasks (local stdio only):

- read a generated page: `Use only the fpf_memory MCP server. Call read_fpf_doc with selector: "A.1.1"`
- inspect retrieval evidence: `Use only the fpf_memory MCP server. Call trace_fpf_path with question: "How do U.RoleAssignment and U.BoundedContext connect?"`
- check runtime freshness: `Use only the fpf_memory MCP server. Call get_fpf_index_status`
- rebuild the local index: `Use only the fpf_memory MCP server. Call refresh_fpf_index`

Smoke-test the same runtime surface locally before wiring it into Codex:
Expand Down Expand Up @@ -247,17 +254,22 @@ Call trace_fpf_path with:

## MCP tool roles

- `refresh_fpf_index`: rebuild the local artifact set
- `get_fpf_index_status`: inspect runtime freshness, artifact presence, and runtime configuration
- `query_fpf_spec`: return the answer envelope with IDs, citations, constraints, and freshness metadata
### Public tools (deployed HTTP 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
- `get_fpf_index_status`: inspect runtime freshness, artifact presence, and runtime configuration

### Expert tools (local stdio only)

- `refresh_fpf_index`: rebuild the local artifact set
- `trace_fpf_path`: return deterministic retrieval evidence only
- `inspect_fpf_node`: expand one node into its anchors, neighboring relations, and stable doc references
- `read_fpf_doc`: return the canonical generated markdown page plus stable markdown/static paths
- `inspect_fpf_anchor`: expand one anchor into raw anchor text plus owning node context
- `expand_fpf_citations`: expand multiple citations into raw anchor text plus owning node context

Only `query_fpf_spec` and `ask_fpf` can use the optional LM Studio synthesizer. All other MCP tools stay deterministic.
Only `query_fpf_spec` and `ask_fpf` can use the optional synthesizer. All other MCP tools stay deterministic. Set `FPF_MCP_SURFACE=public` on the deployed server to restrict to public tools only.

## Runtime behavior

Expand Down
649 changes: 640 additions & 9 deletions bun.lock

Large diffs are not rendered by default.

54 changes: 28 additions & 26 deletions docs/mcp-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ The runtime itself is compiler-backed and local:

## Transport

- transport: stdio
- 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`
- server name: `fpf_memory`
- protocol version: `2024-11-05`
- Codex entry point: `node --import tsx src/mcp-stdio.ts`
- local dev shortcut: `bun run mcp`

Hosted/server surfaces still use the Mastra Hono adapter under `src/mastra.ts` and `src/server.ts`.
The deployed HTTP surface exposes public tools only. The stdio transport exposes all tools.

## Codex Setup

Expand All @@ -46,60 +46,62 @@ cwd = "/absolute/path/to/fpf-memory"
enabled_tools = [
"ask_fpf",
"query_fpf_spec",
"read_fpf_doc",
"trace_fpf_path",
"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 the default answer, doc-read, evidence, status, and refresh flows without exposing every debug tool by default.
`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

### `refresh_fpf_index`
### Public tools (deployed HTTP surface)

Build or rebuild the local vectorless index from `FPF-spec.md` and persist the runtime artifact set under `.runtime/fpf-index/`.

### `get_fpf_index_status`
#### `ask_fpf`

Report whether the local index exists, whether it is fresh against the current source hash, and which artifacts are present.
Return a markdown-first grounded answer envelope over the same runtime.

### `query_fpf_spec`
#### `query_fpf_spec`

Answer a question with deterministic grounding, citations, constraints, and freshness metadata.

### `ask_fpf`
#### `get_fpf_index_status`

Return a markdown-first grounded answer envelope over the same runtime.
Report whether the local index exists, whether it is fresh against the current source hash, and which artifacts are present.

### `trace_fpf_path`
### Expert tools (local stdio only)

#### `refresh_fpf_index`

Build or rebuild the local vectorless index from `FPF-spec.md` and persist the runtime artifact set under `.runtime/fpf-index/`.

#### `trace_fpf_path`

Return the retrieval trace used for a question, including candidate scores, graph expansion, and selected anchors.

### `inspect_fpf_node`
#### `inspect_fpf_node`

Inspect one pattern, route, or lexeme and return anchors, neighboring relations, and stable document references.

### `read_fpf_doc`

Resolve a pattern, route, or lexeme to the canonical generated markdown page and return:
#### `read_fpf_doc`

- the selected document node
- the stable markdown path
- the stable static path
- the exact generated markdown text
Resolve a pattern, route, or lexeme to the canonical generated markdown page and return the selected document node, stable paths, and exact generated markdown text.

### `inspect_fpf_anchor`
#### `inspect_fpf_anchor`

Inspect one exact anchor ID and return raw anchor text plus owning-node context.

### `expand_fpf_citations`
#### `expand_fpf_citations`

Expand multiple citation IDs into raw anchor text plus owning-node context without adding new semantics.

Expand Down
34 changes: 21 additions & 13 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
{
"name": "fpf_memory",
"version": "1.0.0",
"description": "Local vectorless FPF-spec runtime with MCP tools for answers, docs, evidence, status, and refresh.",
"tools": [
"refresh_fpf_index",
"get_fpf_index_status",
"query_fpf_spec",
"ask_fpf",
"trace_fpf_path",
"inspect_fpf_node",
"read_fpf_doc",
"inspect_fpf_anchor",
"expand_fpf_citations"
],
"transport": "stdio",
"description": "Local vectorless FPF-spec runtime with MCP tools for answers, structured queries, and status.",
"tools": {
"public": [
"ask_fpf",
"query_fpf_spec",
"get_fpf_index_status"
],
"expert": [
"refresh_fpf_index",
"trace_fpf_path",
"inspect_fpf_node",
"read_fpf_doc",
"inspect_fpf_anchor",
"expand_fpf_citations"
]
},
"transport": ["stdio", "http"],
"runtime": {
"bun": "bun src/mcp-stdio.ts",
"node": "node --import tsx src/mcp-stdio.ts"
},
"http": {
"path": "/api/mcp/fpf_memory/mcp",
"sse_path": "/api/mcp/fpf_memory/sse"
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@rstest/core": "^0.9.7",
"@types/node": "^25.6.0",
"bun-types": "^1.3.12",
"mastra": "^1.5.0",
"tsx": "^4.21.0",
"typescript": "^6.0.2"
}
Expand Down
18 changes: 14 additions & 4 deletions src/mastra.ts → src/mastra/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ 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 } from './mcp/server.js';
import { getRuntimeObservability } from './observability/runtime-observability.js';
import { getRuntimeLogger } from '../logging/runtime-logger.js';
import { fpfMcpServer, fpfPublicMcpServer } 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({
logger,
observability: observability.observability,
mcpServers: {
fpf_memory: fpfMcpServer,
fpf_memory: mcpServer,
},
server: {
mcpOptions: { serverless: true },
},
});

Expand All @@ -24,6 +28,12 @@ export function createMastraRuntime(env: NodeJS.ProcessEnv = process.env) {
};
}

/**
* 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;
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 Defer Mastra runtime creation until it is needed

Creating mastra at module load (createMastraRuntime()) eagerly initializes the logger and observability stack even when callers only import createHonoMastraRuntime. In this codebase those initializers call resolveLogPath, which performs mkdirSync/openSync; in read-only build/deploy environments this can throw during import before the server starts, and it also bypasses any env object a caller intended to pass later. Keeping this export lazy (or isolating it to a deploy-only entrypoint) avoids import-time side effects and environment drift.

Useful? React with 👍 / 👎.


export async function createHonoMastraRuntime(env: NodeJS.ProcessEnv = process.env) {
const runtime = createMastraRuntime(env);
const app = new Hono<{
Expand Down
19 changes: 14 additions & 5 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import { MCPServer } from '@mastra/mcp';

import { fpfMcpTools } from './tools.js';
import { fpfMcpTools, fpfPublicTools } from './tools.js';

export function createFpfMcpServer(): MCPServer {
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:
'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: fpfMcpTools,
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,
});
}

export const fpfMcpServer = createFpfMcpServer();
/** Deployed MCP — public tools only. */
export const fpfPublicMcpServer = createFpfMcpServer('public');

/** Local MCP — all tools. */
export const fpfMcpServer = createFpfMcpServer('full');

export async function startStdioMcpServer(): Promise<void> {
await fpfMcpServer.startStdio();
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/tool-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const frontierOriginSchema = z.enum([
'session_context',
]);
export const expandedCitationStatusSchema = z.enum(['ok', 'not_found']);
export const lmStudioApiStyleSchema = z.enum(['responses', 'lmstudio_chat']);
export const lmStudioApiStyleSchema = z.enum(['responses', 'lmstudio_chat', 'chat_completions']);

export const relationEdgeSchema = z
.object({
Expand Down
19 changes: 15 additions & 4 deletions src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,27 @@ export const traceFpfPathTool = createTool({
runtime.trace(question, mode ?? 'compact', forceRefresh ?? false, sessionId),
});

export const fpfMcpTools = {
refresh_fpf_index: refreshFpfIndexTool,
get_fpf_index_status: getFpfIndexStatusTool,
/** Public tools — safe for deployed MCP surface. */
export const fpfPublicTools = {
ask_fpf: askFpfTool,
query_fpf_spec: queryFpfSpecTool,
get_fpf_index_status: getFpfIndexStatusTool,
} as const;

/** Expert/debug tools — local use only. */
export const fpfExpertTools = {
refresh_fpf_index: refreshFpfIndexTool,
trace_fpf_path: traceFpfPathTool,
inspect_fpf_node: inspectFpfNodeTool,
read_fpf_doc: readFpfDocTool,
inspect_fpf_anchor: inspectFpfAnchorTool,
expand_fpf_citations: expandFpfCitationsTool,
ask_fpf: askFpfTool,
} as const;

/** All tools — used by local stdio and Hono server. */
export const fpfMcpTools = {
...fpfPublicTools,
...fpfExpertTools,
} as const;

export function resolveDefaultQueryMode(env: NodeJS.ProcessEnv = process.env): AnswerMode {
Expand Down
Loading