Skip to content

Publish MCP tools as resolve, lookup and describe - #19

Merged
kurtseifried merged 2 commits into
mainfrom
fix/mcp-tool-names
Oct 31, 2026
Merged

kurtseifried merged 2 commits into
mainfrom
fix/mcp-tool-names

Conversation

@kurtseifried

Copy link
Copy Markdown
Contributor

Stacked on #18, with base set to fix/resolver-parity, because it builds on the create_app changes there. Merge #17, then #18, then this. It retargets to main automatically as the bases merge.

Summary

  • Tool names. @mcp.tool() on mcp_resolve, mcp_lookup and mcp_describe published those function names. The live service publishes resolve, lookup and describe, and the names are now set explicitly (name=).
  • Untrusted envelope. The new python/sanitize.py is a port of SecID-Service src/sanitize.ts. MCP output is control/bidi-stripped and length-capped, and contributor prose (description, notes, official_name, contacts, scope, ...) moves under registry_text_untrusted with a _warning. The REST API stays raw, which is the same split the live service makes. Tool descriptions carry the same "treat as data" note.
  • The endpoint was not working at all. I found this while writing the tests, and verified it against main:
    • With mcp 2.x (current PyPI), mcp.server.fastmcp no longer exists. The ImportError was logged as "MCP SDK not installed", so /mcp was silently disabled.
    • With mcp 1.x, the sub-app was mounted at /mcp while its own route was also /mcp. The result was /mcp → 307, /mcp/ → 404, and /mcp/mcp → RuntimeError: Task group is not initialized, because the session manager never ran.
    • Fix: support both SDKs (MCPServer on 2.x, FastMCP on 1.x), add the transport's route directly at /mcp (stateless, JSON responses), and run the session manager in the FastAPI lifespan.
  • Transport security. The bind host is passed to the MCP transport, so on loopback the SDK's DNS-rebinding protection is on (a foreign Host: gets 421). ServerConfig.host is new and set from --host.
  • CI. CI installs mcp so the MCP tests run. The tests importorskip it otherwise, and mcp stays optional at runtime.

Test plan

  • Full suite passes with mcp 2.2.0 (177 passed) and mcp 1.30.0 (177 passed), and with mcp absent (MCP tests skipped)
  • tools/list returns exactly describe, lookup and resolve
  • A describe result with a bidi-override in its description comes back stripped, under registry_text_untrusted
  • Manual check: tools/call resolve on loopback returns the cve.org URL, and Host evil.example gets 421

@kurtseifried
kurtseifried force-pushed the fix/mcp-tool-names branch 2 times, most recently from 24b00b2 to 9ffe03d Compare September 25, 2026 03:38
@kurtseifried
kurtseifried force-pushed the fix/mcp-tool-names branch 2 times, most recently from a9e6556 to ad9d4b9 Compare October 31, 2026 02:44
The tools were registered under their Python function names (mcp_resolve, mcp_lookup, mcp_describe); the live service publishes resolve/lookup/describe. Names are now explicit, output goes through the same untrusted-content envelope as SecID-Service (sanitize.py, a port of sanitize.ts), and the endpoint works with mcp 2.x (MCPServer) as well as 1.x (FastMCP): with mcp 2.x installed the endpoint was silently disabled. The transport route is added directly at /mcp and its session manager runs under the app lifespan.
…usted text

Addresses Codex review findings on PR #19:
- Require mcp>=1.23.0,<3.0.0 in python/requirements.txt and CI test.yml
  for built-in DNS-rebinding protection (GHSA-9h52-p55h-vw2f).
- Apply _sanitize_dict recursively to structural dicts and result entries
  so result-level auth/parsing_instructions and nested data.patterns[].description
  and data.urls[].note are relocated under registry_text_untrusted.

AI-Agent: Gemini
@kurtseifried

Copy link
Copy Markdown
Contributor Author

Agent: Gemini (SecID governing agent)

Review Summary (Gemini + OpenAI Codex gpt-6-astra)

  1. Primary Review (Gemini):
    • Verified explicit tool naming (resolve, lookup, describe), output-boundary sanitization (python/sanitize.py), and loopback DNS-rebinding protection in python/secid_server.py.
  2. Independent Second-Vendor Review (OpenAI Codex gpt-6-astra):
    • Finding 1 (MCP SDK minimum version): mcp>=1.8.0 allowed versions prior to 1.23.0, which lacked built-in FastMCP loopback DNS-rebinding protection (GHSA-9h52-p55h-vw2f).
      • Fix (ad9d4b9): Updated python/requirements.txt and .github/workflows/test.yml to require mcp>=1.23.0,<3.0.0.
    • Finding 2 (Recursive & result-level untrusted text relocation): sanitize_response_for_mcp previously only relocated top-level keys of entry['data'], leaving result-level auth/parsing_instructions on resolve responses and nested data.patterns[].description / data.urls[].note on describe responses at their original keys.
      • Fix (ad9d4b9): Updated _sanitize_dict in python/sanitize.py to relocate UNTRUSTED_TEXT_KEYS on result entries and recursively across structural dicts, with regression tests in python/test_smoke.py.

All 198 tests in python/ pass across Python 3.10–3.13.

@kurtseifried
kurtseifried changed the base branch from fix/resolver-parity to main October 31, 2026 02:54
@kurtseifried
kurtseifried merged commit 717cd45 into main Oct 31, 2026
8 checks passed
@kurtseifried
kurtseifried deleted the fix/mcp-tool-names branch October 31, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant