Skip to content

Upgrade to fastmcp 4.0.5 (fixes COR2-475 path traversal at the source) - #16

Merged
ashiramin merged 3 commits into
masterfrom
ashir/fastmcp-4-upgrade
Sep 24, 2026
Merged

ashiramin merged 3 commits into
masterfrom
ashir/fastmcp-4-upgrade

Conversation

@ashiramin

@ashiramin ashiramin commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Why

fastmcp 2.12.3 interpolates OpenAPI path parameters into the backend URL with a plain str.replace, unescaped. httpx then resolves the dot-segments per RFC 3986 while building the request, so a tool argument of ../../internal/users for tagOrId produced a real call to /api/internal/users — carrying the caller's bearer token. That is the path traversal Cimpress reported (COR2-475, under the COR2-349 umbrella).

fastmcp 4 percent-encodes path parameters, so the value stays inside a single URL segment and the traversal cannot be expressed at all. This fixes the cause rather than guarding the symptom, which is why it supersedes #15.

What changed

Import moves only — the OpenAPI types were relocated in 4.x, not redesigned:

  • MCPType, OpenAPITool, OpenAPIResource, OpenAPIResourceTemplate → fastmcp.server.providers.openapi
  • HTTPRoute → fastmcp.utilities.openapi.models

Both callback signatures are unchanged (RouteMapFn = Callable[[HTTPRoute, MCPType], MCPType | None]), so custom_route_mapper and customize_components keep their bodies verbatim.

fastmcp 4 builds on httpx2; the dependency is swapped accordingly. Applications call httpx2.alias_httpx() once at startup so existing import httpx code shares the httpx2 classes — see cortexapps/cortex-remote-mcp#17.

Testing

  • Suite: 26 passed
  • Generated tool surface is byte-identical to 2.12.3 — 29 tools, same names and descriptions, diffed directly. Schema changes are additive (4.x preserves example values from the spec). One semantic change worth a look: a single oneOf + discriminator became anyOf.
  • Traversal verified fixed: tagOrId='../../internal/users' now resolves to /api/v1/catalog/%2E%2E%2F%2E%2E%2Finternal%2Fusers/openapi instead of /api/internal/users.

Merge order

This PR must merge before cortexapps/cortex-remote-mcp#17. That repo depends on cortex-mcp @ master, and while this pins fastmcp==2.12.3 its install cannot resolve against fastmcp>=4.0.5:

× No solution found when resolving dependencies:
  cortex-mcp depends on fastmcp==2.12.3
  cortex-mcp-private depends on fastmcp and fastmcp>=4.0.5

Note

The removed pin comment claimed 2.12.3 was needed to fix a "PublicIdentifier schema bug." The tool-schema diff came back clean, but whoever hit that bug should confirm before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LZtC15RMphgMhtsoDh4i4u

ashiramin and others added 3 commits September 23, 2026 08:29
fastmcp 4 URL-encodes OpenAPI path parameters, which removes the
path-traversal vector at the source (COR2-475) rather than guarding
against it.

The migration is import moves only -- the OpenAPI types were relocated,
not changed: MCPType and the OpenAPI component classes now live in
fastmcp.server.providers.openapi, HTTPRoute in
fastmcp.utilities.openapi.models. Both callback signatures are
unchanged, so custom_route_mapper and customize_components keep their
bodies.

fastmcp 4 moves to httpx2; applications call httpx2.alias_httpx() once
at startup so existing `import httpx` code shares the httpx2 classes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZtC15RMphgMhtsoDh4i4u
The previous commit swapped the httpx dependency for httpx2 but left
`import httpx` in src/clients/cortex.py, working only because the
downstream application called httpx2.alias_httpx() first. That broke
cortex-mcp as a standalone package -- its own console script and
Dockerfile entry point import that module directly:

    ModuleNotFoundError: No module named 'httpx'

The tests did not catch it because none of them import src.clients.cortex.

httpx2's alias_httpx() is documented for applications, not libraries, and
cortex-mcp is imported as a library by cortex-remote-mcp. Import httpx2
under the httpx name in this module instead, so the package is correct on
its own and no longer depends on import ordering elsewhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZtC15RMphgMhtsoDh4i4u
The import-path migration left a line ruff's isort rule wants split; CI
runs `ruff check src tests` and failed on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZtC15RMphgMhtsoDh4i4u

@brucesw brucesw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Seems pretty straightforward. Thanks for taking this on.

@ashiramin
ashiramin merged commit c2dffd1 into master Sep 24, 2026
8 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.

2 participants