# Agent Instructions: `exec_mcp`

> **Scope:** These instructions apply to files under `exec_mcp/`.

> **Maintenance rule:** NOT a changelog; if something stops being true, overwrite it.

## Purpose

exec_mcp

## What it is

HTTP → MCP bridge. A small FastAPI server that lets non-MCP callers (a shell script,
curl, browser JS) invoke tools on any local MCP server over plain HTTP, without speaking
the MCP protocol themselves. Standalone — not part of any MCP server, has its own venv.

## Files

- mcp_tool_executor.py — the FastAPI server (entrypoint).
- run.sh               — .venv + pip + python mcp_tool_executor.py (cd-safe).
- requirements.txt     — fastapi, pydantic, fastmcp, uvicorn.
- tests/               — test_mcp_tool_executor.py (manual CLI; see note below).

## Api

Listens on http://localhost:8122 (GET / = info, /docs = OpenAPI).
- POST /execTool  body {"tool": "...", "params": {...}} → {"success": bool, "result": str}

## Tool-name routing (the key trick)

The tool name encodes the TARGET MCP server's port:
```text
mcp__<port>_<anything>__<real_tool_name>
```

The parser extracts <port>, builds URI http://localhost:<port>/mcp, and forwards
<real_tool_name> (the part after `__`) to that server. So one executor fans out to many
MCP servers. This is the same mcp__server__tool convention Claude Code uses; here the
"server" segment starts with the port (e.g. `mcp__8124_dom__get_current_date` → target
server on port 8124, tool `get_current_date`). mcp_user's GUI JS emits exactly these names.

## Behavior / decisions

- Connection pool: one _ClientState per target URI in `_clients`. fastmcp Client sessions
  are opened once and REUSED (no handshake per call). Locks: open_lock (serializes open),
  call_lock (serializes calls on one client), _clients_lock (guards the dict).
- Retry-once: if call_tool raises, the client is replaced (_replace_client: close → new
  Client → open) and the call retried once — handles a stale/broken session.
- Result extraction: returns result.content[0].text (first text block only).
- Heavy timing instrumentation: nearly every step is timed in ns and logged as
  `[MCP][<rid>] STEP ... ms` (OPEN_SESSION, PARSE, GET_STATE HIT/MISS, CALL, EXTRACT,
  SUCCESS total). Added mainly for latency diagnosis of MCP calls.
- Shutdown closes all pooled clients (@app.on_event("shutdown")).

## Gotchas

- 8122 is the executor's OWN listen port; the port inside a tool name is the TARGET
  server's port — these are different, not a mismatch.
- Note: tests/test_mcp_tool_executor.py is NOT a client of this executor — it connects
  directly to an MCP server (port 8124) via fastmcp, bypassing /execTool. Confusingly named.
- Code comments/messages are Polish (existing code); the file wasn't translated on the move.
