Skip to content

feat(mcp): migrate server to FastMCP with native tool-search#28

Merged
Ooscaar merged 1 commit into
developfrom
feat/mcp-tool-search
Jul 10, 2026
Merged

feat(mcp): migrate server to FastMCP with native tool-search#28
Ooscaar merged 1 commit into
developfrom
feat/mcp-tool-search

Conversation

@Ooscaar

@Ooscaar Ooscaar commented Jul 10, 2026

Copy link
Copy Markdown
Member

Why

Connecting the MCP server to an AI client surfaces the full generated tool catalog (480+ tools) at once. Large tool lists inflate context and some MCP clients cap or truncate them, so not every tool reliably shows up. This keeps a curated, always-visible core of the tools central to profile work and makes the rest of the catalog reachable on demand through search.

What

  • Migrate the MCP server from the bundled mcp.server.fastmcp to the standalone FastMCP framework.
  • Add FastMCP's native BM25 tool-search transform with a 49-tool always-visible core (51 with search_tools/call_tool, out of 481 registered): the 20 hand-written ergonomic tools plus the generated tools central to profile work — posting, scheduling queue, pre-publish validation, account health, cross-platform analytics basics, and post engagement. The messaging/ads/connect suites (~430 tools) sit behind search_tools/call_tool. Every underlying tool stays directly callable, so nothing is lost.
  • Move authentication to FastMCP's resource-server model: a custom token verifier validates the bearer against the Zernio API (both API keys and OAuth tokens work), and the RFC 9728 protected-resource metadata plus the 401 WWW-Authenticate challenge are served automatically. The pre-FastMCP discovery path (/.well-known/oauth-protected-resource) 308-redirects to the canonical path-inserted document, so clients holding the old URL keep working. Verified against the zernio.com authorization-server code that the changed resource identifier (…/mcp) is a no-op for token issuance and validation.
  • Serve Streamable HTTP via FastMCP's stateless ASGI app as the primary transport, with Origin-based DNS-rebinding protection. The legacy SSE transport (GET /sse + POST /messages/) is deprecated but kept for backwards compatibility — production still receives SSE connections; its routes are grafted from the same FastMCP instance so tools and auth are identical.
  • Remove the superseded custom auth/routes module; update docs/MCP.md and docs/HTTP_DEPLOYMENT.md to the new transport, auth model, and tool-search behaviour; document MCP_PUBLIC_URL and MCP_ALLOWED_ORIGINS.

Verification

  • In-process: auth parity probes (401 challenge, origin guard ordering, discovery docs), all 49 pins resolve, 51 tools visible; full SSE session (handshake → initialize → tools/list → tool call) with the real MCP SSE client.
  • Live on Railway dev (mcp-server-dev-5f19.up.railway.app): health, 401/403 gates on /mcp and /sse, all three discovery docs, legacy 308 redirect; real client session exercised pinned tools, search_tools, and call_tool end-to-end.
  • 176 tests pass; branch rebased on develop with a minimal uv.lock delta (fastmcp + transitive deps only).

Follow-ups

  • Fix the pre-existing Platform enum gap (sms/phone accounts crash accounts_list/_resolve_account — surfaced during live testing, present in production today).
  • Optional: one live OAuth connector run against dev as final confirmation; tune tool descriptions to sharpen search relevance.

🤖 Generated with Claude Code

@Ooscaar
Ooscaar force-pushed the feat/mcp-tool-search branch 3 times, most recently from 392b575 to b2a483c Compare July 10, 2026 15:04
Migrate the MCP server from the bundled mcp.server.fastmcp to the standalone
FastMCP package and collapse the generated tool catalog behind FastMCP's BM25
tool-search transform, so clients see a small always-visible core plus
search_tools/call_tool instead of the full catalog.

- server.py: FastMCP + BM25SearchTransform(always_visible=core tools);
  _get_client() reads the request AccessToken (get_access_token) with
  ContextVar/env fallback for STDIO.
- Pin a 49-tool always-visible core (51 with search_tools/call_tool, out of
  481 registered): the 20 hand-written ergonomic tools plus the generated
  tools central to profile work — posting, scheduling queue, pre-publish
  validation, account health, cross-platform analytics basics, and post
  engagement. The messaging/ads/connect suites (~430 tools) stay behind
  search.
- auth.py: ZernioTokenVerifier(TokenVerifier) + RemoteAuthProvider — bearer
  validated against the Zernio API (accepts API keys and OAuth tokens), with
  RFC 9728 protected-resource metadata and the 401 challenge served
  automatically.
- http_server.py: mcp.http_app(stateless_http=True) + Origin-guard ASGI
  middleware. Streamable HTTP is the primary transport; the legacy SSE
  transport (GET /sse + POST /messages/) is deprecated but kept for
  backwards compatibility — its routes are grafted from the same FastMCP
  instance (http_app(transport="sse")), so tools and auth are identical.
- Remove routes.py (superseded by FastMCP-native auth/routes).
- Keep the pre-FastMCP discovery path working: /.well-known/oauth-protected-
  resource now 308-redirects to the canonical path-inserted document FastMCP
  serves at /.well-known/oauth-protected-resource/mcp.
- Update docs (MCP.md, HTTP_DEPLOYMENT.md) to the new transport, auth model,
  and tool-search behaviour; document MCP_PUBLIC_URL and MCP_ALLOWED_ORIGINS.
- Remove code superseded by FastMCP (extract_late_api_key,
  WWW_AUTHENTICATE_BEARER) and fix stale comments/docstrings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Ooscaar
Ooscaar force-pushed the feat/mcp-tool-search branch from b2a483c to e1cdc1e Compare July 10, 2026 16:00
@Ooscaar
Ooscaar merged commit 252e395 into develop Jul 10, 2026
4 checks passed
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