Files
tai/taicpp/docs/mcp.md
T
retoorandClaude Opus 4.8 79ae6f27da cpp-safety: safe-C++ playbook, fix a second UB, and a sanitizer net
Response to incident WA-2026-10-27 (the std::vector max_size() crash). Deep
research archived under research/safe-cpp-practices (8 Tier 1, 37 Tier 2, 2
conclusions) on production-safe C++ that keeps development speed: OpenSSF
compiler hardening, the C++ Core Guidelines bounds profile, integer-overflow
practice, libFuzzer/sanitizer CI, the sanitizer overhead ladder, clang-tidy
ratchets.

The research prescribed an ASan+UBSan+_GLIBCXX_ASSERTIONS build of the parsers.
Standing it up immediately caught a second, independent undefined-behavior bug
that two manual reviews had missed: a protobuf varint loop that shifted by up
to 70 bits (shift >= 64 is UB) on malformed input. Fixed and guarded by a
deterministic test and the sanitizer net.

- src/whatsapp/wa_proto.cpp: guard the wire-0 varint shift (the UB).
- tests/whatsapp_test.cpp: add an overlong-varint hostile case.
- tools/san-whatsapp.sh: the standing sanitizer net for detz::wa parsers.
- WaClient frame capture: a frame that fails to decode/dispatch has its exact
  bytes recorded to <home>/whatsapp/last-error.log (bounded), so the still
  unreproduced live <iq> failure becomes a one-paste deterministic fix. Wired
  from the wizard and the whatsapp_send tool.
- docs/cpp-safety.md: the project-wide playbook and design decisions (four
  layers: overflow-safe checked-reader parsing, cheap release hardening, the
  sanitizer test net, capture). docs/whatsapp.md section 13.6 records the
  follow-up and the honest open item. AGENTS.md makes it a standing rule;
  cap.md and mcp.md inherit it for their wire parsers.

Verified: build, whatsapp_test (incl. hostile+fuzz), make lint, and
tools/san-whatsapp.sh all green; research audit PASS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-27 19:03:32 +01:00

67 lines
3.4 KiB
Markdown

# mcp (taicpp)
Port of the MCP section of `../tai.py`, normative text in `../mcp.md`.
Namespace `detz::mcp`, sources in `src/mcp/`. Reuses the `detz::wire` layer
from `src/cap/` (see `docs/cap.md`).
**Wire parsing is untrusted input**: every byte here comes off a socket, so the parsers follow [cpp-safety.md](cpp-safety.md) (overflow-safe size checks, unsigned range-checked lengths, guarded shifts, no exception escaping the read loop, hostile-input tests under sanitizers). We do not accept regressions in that.
## Spec delta (this milestone)
The placeholder `serve_list`/`serve_call` is replaced by the real client,
OAuth, bridge, vault tables, tool backends and server.
## Parity table
| Python | C++ |
|---|---|
| `MCP_PROTOCOL_VERSION` `MCP_CLIENT_NAME` `MCP_TOOL_PREFIX` `MCP_RESOLVER_TIMEOUT` `MCP_RESOLVER_STEPS` `MCP_OAUTH_TIMEOUT` `MCP_RESOLVER_BRIEF` `MCP_CONNECT_TIMEOUT` `MCP_CALL_TIMEOUT` `MCP_RESULT_MAX` `MCP_HTTP_MAX_BODY` `MCP_MAX_SERVERS` `MCP_MAX_TOOLS` `MCP_SESSION_IDLE` `MCP_CHAT_TIMEOUT` `MCP_DEFAULT_PORT` `MCP_SERVER_RE` `MCP_VAR_RE` `MCP_GITHUB_RE` | `mcp_constants.h` |
| `McpError` `McpJsonRpcError` `McpHttpError` | `mcp_error.h` |
| `mcp_sanitize_name` `mcp_github_target` `mcp_expand_vars` `mcp_rewrite_placeholders` `mcp_entry_from_mapping` `mcp_normalize` `mcp_tool_hash` `mcp_bridge_name` | `McpNormalizer` |
| `McpStdioTransport` `McpHttpTransport` `McpClient` | same names, `McpTransport` interface |
| `McpOAuthCallback` `McpOAuth` | same names |
| `McpBridge` | `McpBridge` over `McpHost` |
| `Store.mcp_*` | `McpStore` interface, `McpSqliteStore` (four tables) |
| `Tools.run_mcp_*`, `mcp_resolve_github`, `mcp_resolve_agent`, `mcp_schemas` | `McpTools` |
| `McpServeSession` `McpServeContext` `mcp_serve_tools` `mcp_serve_handler` `mcp_serve_main` | `McpServeSession` `McpServeContext` `McpServeHandler` `McpServeMain` |
## Wire subset
Methods: `initialize`, `notifications/initialized`, `ping`, `tools/list`,
`tools/call`, `notifications/cancelled`. Errors `-32601`, `-32602`,
`-32603`. Protocol `2025-11-25`.
## Caps
`MCP_CONNECT_TIMEOUT` 20, `MCP_CALL_TIMEOUT` 60, `MCP_RESULT_MAX` 65536,
`MCP_HTTP_MAX_BODY` 10485760, `MCP_MAX_SERVERS` 32, `MCP_MAX_TOOLS` 100,
`MCP_SESSION_IDLE` 3600, `MCP_CHAT_TIMEOUT` 600, `MCP_RESOLVER_TIMEOUT` 300,
`MCP_RESOLVER_STEPS` 12, `MCP_OAUTH_TIMEOUT` 300.
## Vault tables
`mcp_servers`, `mcp_server_profiles`, `mcp_tool_pins`, `mcp_server_tools`
(`McpSqliteStore::ensureSchema`).
## Agent tools
`mcp_add`, `mcp_list`, `mcp_enable`, `mcp_disable`, `mcp_remove`,
`mcp_tools`, `mcp_call`, `mcp_login` (`McpTools::dispatch`). Served extras:
`chat`, `completions`.
## Differences from Python (declared)
- `mcp_login` browser: opens `xdg-open` only when `DISPLAY`/`WAYLAND_DISPLAY`
is set; Python's `webbrowser` has a longer fallback list.
- Transport error texts come from libcurl mapped to urllib wording
(`[Errno 111] Connection refused`, `timed out`); exotic failures differ.
- HTTP `Server:` header omits the Python version suffix.
- `mcp_store_tools` runs in one transaction (Python leaves partial writes
pending on a duplicate tool name).
- stdio stderr is a pipe nobody drains, exactly as in tai.py (the doc says
drained, the code does not).
## Verification
`tests/capmcp_unit.cpp` (vault tables, normalizer, served endpoint in process). No differential run against tai.py yet for the client, OAuth, bridge and server (see docs/wiring/capmcp.md, Known gaps).