# v0.4 MCP tool-handler audit — Python ↔ Edge Function

**Purpose**: surface drift between the two MCP server implementations before
extracting both into `_shared/mcp-tools/`. Tactical doc; not for long-term
preservation (delete once v0.4 ships and `_shared/mcp-tools/` is the single
source of truth).

## Tool inventory

| # | Tool | Python `mcp_server.py` | EF `cerefox-mcp/tools/` | Drift? |
|---|------|---|---|---|
| 1 | `cerefox_search` | `_handle_search` (line 533) | `search.ts` | none |
| 2 | `cerefox_ingest` | `_handle_ingest` (line 613) | `ingest.ts` | none |
| 3 | `cerefox_get_document` | `_handle_get_document` (line 743) | `get-document.ts` | none |
| 4 | `cerefox_list_versions` | `_handle_list_versions` (line 766) | `list-versions.ts` | none |
| 5 | `cerefox_list_metadata_keys` | `_handle_list_metadata_keys` (line 786) | `metadata.ts` | none |
| 6 | `cerefox_get_audit_log` | `_handle_get_audit_log` (line 797) | `audit-log.ts` | none |
| 7 | `cerefox_list_projects` | `_handle_list_projects` (line 833) | `list-projects.ts` | none |
| 8 | `cerefox_metadata_search` | `_handle_metadata_search` (line 848) | `metadata-search.ts` | none |
| 9 | `cerefox_set_document_projects` | `_handle_set_document_projects` (line 681) | `set-document-projects.ts` | none |
| 10 (NEW) | `cerefox_get_help` | — (added in this iter) | `get-help.ts` (NEW) | n/a |

Schemas are parameter-shape-compatible across the two implementations (same
parameter names, same required vs optional, same JSON Schema types). Prose in
descriptions varies cosmetically but is semantically equivalent.

## What we standardise on

**The EF shapes are the conformance target for `_shared/mcp-tools/`.** They
were already TS, already shipped in production, and the response-text
formatting has been validated against agents in the wild.

The Python implementations stay as the **legacy fallback** for the
`cerefox mcp` soft wrapper (22E) — they continue to use Python-idiomatic
patterns and their existing response text. They do *not* import from
`_shared/`; that's a v0.5+ consolidation opportunity if anyone cares
(probably nobody — the Python path is just a fallback).

## Shape of each `_shared/mcp-tools/<name>.ts` module

Each exports a `ToolDefinition` object with:

```ts
interface ToolDefinition {
  name: string;           // e.g. "cerefox_search"
  description: string;    // one-paragraph description (from EF's TOOLS array)
  inputSchema: JsonSchema; // JSON Schema (from EF's TOOLS array)
  handler: (
    client: CerefoxDbClient,
    args: Record<string, unknown>,
    ctx: ToolContext,
  ) => Promise<string>;   // MCP TextContent body
}

interface ToolContext {
  openaiApiKey?: string;  // only search + ingest need this
  accessPath: "remote-mcp" | "local-mcp";  // for usage logging attribution
}
```

The Edge Function (`cerefox-mcp/index.ts`) imports `ALL_TOOLS` from
`_shared/mcp-tools/index.ts` and passes `accessPath: "remote-mcp"` to every
handler. The new local TS MCP server (`packages/memory/src/server.ts`) does
the same with `accessPath: "local-mcp"`. The Python legacy fallback continues
to use its own response formatting and passes `access_path: "local-mcp"` to
`cerefox_log_usage` (unchanged from today).

## Response-format conformance

Both the EF and the Python handlers produce free-form markdown text. The
text contents are functionally equivalent — same fields surfaced, same
formatting style — but not byte-identical. **For the extraction we adopt
the EF's exact text formatting** since that's what most agents have been
trained on (the remote MCP path is the default for hosted-MCP setups).

22D.4 captures a pre-refactor snapshot of EF tool outputs against the live
maintainer instance; the post-refactor EF (using `_shared/mcp-tools/`) must
match it byte-for-byte. The new local TS MCP server (22C) inherits the same
formatting automatically since it uses the same modules.

## Dependencies the handlers need

| Surface | Modules | Status |
|---|---|---|
| Supabase client (table reads, RPC calls) | `_shared/db-client/` | exists (v0.3.0); needs to grow per 22A.6 |
| Embedder for search/ingest | new `_shared/embeddings/` module wrapping `OpenAI`/`fetch` | NEW in v0.4 |
| Byte-budget helper for `applyByteBudget` (search) | move from `cerefox-mcp/shared.ts` to `_shared/mcp-tools/utils.ts` | move |
| Usage-log helper (`logUsage`) | move from `cerefox-mcp/shared.ts` to `_shared/db-client/usage.ts` | move |

## What's deliberately NOT extracted

- **Identity-enforcement check** (`require_requestor_identity` config): lives
  in the EF's `handleToolsCall` (the per-request dispatch wrapper), not in
  individual tool handlers. The new local TS MCP server replicates that
  wrapper independently. Both call `cerefox_get_config` RPC. Single source of
  truth would require pulling the wrapper into `_shared/mcp-server/` — out of
  scope for v0.4 (the wrapper logic is ~20 lines, duplication tolerable for
  one release).
- **JSON-RPC framing** (Streamable HTTP vs stdio): EF handles HTTP framing;
  local server handles stdio framing. Different transports; each owns its
  own framing layer.
- **OpenAI key resolution**: EF reads from `Deno.env.get("OPENAI_API_KEY")`;
  local server reads from the `Settings` object resolved by
  `_shared/config/`. Passed in via `ToolContext.openaiApiKey`.
