Skip to content

Flavors

Section 7 · From docs/MCP.md on GitHub

--flavor (and build_server(layer, flavor)) chooses which implementation speaks MCP. The layer, the policy and the results are identical across flavors; what differs is the wire code and which serve options apply.

Flavor Implementation Needs Pick it when
auto sdk if import mcp.server.lowlevel works, else native You do not care
native cloudg.mcp.native, standard library only nothing Minimal installs, air-gapped hosts, or when you want every option below (paging, CORS, /healthz)
sdk the official SDK's low-level Server, uvicorn for HTTP cloudg[mcp] You want the reference implementation on the wire, or the SDK's OAuth support when you build the app yourself
fastmcp fastmcp.FastMCP with cloudg components fastmcp You want fastmcp's middleware, auth providers or mount

Which serve options each flavor honors:

Option native sdk fastmcp
--transport stdio / http / sse yes yes yes
--host, --port, --path yes yes yes (--path is not used for sse)
--auth-token yes yes (ASGI middleware) yes (ASGI middleware)
--allowed-origin, --allowed-host yes yes (cloudg's OriginHostGuard in front of the SDK app) yes (same guard)
--cors-origin yes refused at start-up refused at start-up
--json-response yes yes yes for http; refused with sse
--stateless yes yes (affects handshake-era sessions only) yes for http; refused with sse
--page-size yes no (one page) no
/healthz yes no no
Session id bound to the principal that opened it (another principal presenting it gets 404) yes yes (streamable HTTP; ids issued over the legacy SSE transport are not bound) yes (same)
Per-principal session cap, connection cap, header and body timeouts (HTTPConfig) yes no (uvicorn's own limits) no (uvicorn's own limits)

The flavors share one DNS-rebinding check, cloudg.mcp.native.http.OriginHostGuard. The native server runs it itself; for the SDK and fastmcp flavors it sits in TokenAuthASGIMiddleware in front of the framework's app, and the frameworks' own checks are switched off so requests are not filtered twice. The rules, identical everywhere:

  • Origin is checked on every bind. A request without Origin (non-browser clients) passes; otherwise the origin must match --allowed-origin (default: loopback origins on any port) or be same-origin, meaning its host:port equals the Host header. Same-origin counts only when the Host header itself was validated (a loopback bind, or --allowed-host): on a public bind without --allowed-host a DNS-rebinding attacker controls both headers. Failure: 403.
  • Host is checked against --allowed-host when given. Without it, a loopback bind accepts only loopback names (and refuses a missing Host), and any other bind does not check it, since the server cannot know which names it is reached by. Failure: 421.

Against the SDK flavor bound to 0.0.0.0 with a token and no --allowed-host, an Origin: https://evil.example got 403 Forbidden with {"jsonrpc": "2.0", "id": null, "error": {"code": -32600, "message": "Forbidden: invalid Origin"}}, Host: whatever.example got 200 OK, and a request without the token got 401. The fastmcp flavor (fastmcp 4.0.11) with --json-response answered initialize with content-type: application/json and refused a bad Origin with 403 and a bad Host with 421. Behind a reverse proxy on a public name, pass --allowed-host with that name.

Versions checked for this release:

Package Version Checked with
mcp 2.3.0 the main dev environment: SDK flavor over stdio and HTTP, MCPServer and low-level Server mounting, both eras
mcp 1.30.0 scratch venv: FastMCP and low-level Server mounting, adapter, native, middleware and CLI tests
fastmcp 4.0.11 (on mcp 2.3.0) scratch venv: mounting, adapter tests, the fastmcp flavor over HTTP
fastmcp 3.4.8 (on mcp 1.30.0) scratch venv: mounting, adapter tests
fastmcp 2.14.7 (on mcp 1.30.0) scratch venv: mounting, adapter tests

The mcp extra is mcp>=1.30,<3, the range those versions cover. How tightly to pin beyond that depends on how you use the SDK:

  • Mounting into the SDK's high-level servers reaches into private attributes: MCPServer._lowlevel_server (mcp 2.x), FastMCP._mcp_server (mcp 1.x, and fastmcp's inner server), and MCPServer._subscriptions for the 2026 notification bus. A minor SDK release can rename them; install then raises a TypeError that says so. For this path, pin the minor version you tested.
  • Mounting into a low-level Server, and the SDK flavor (which builds one), use the request-handler API (get_request_handler / add_request_handler on 2.x, which the SDK marks provisional, and request_handlers on 1.x). The extra's range is enough there. The one private attribute on this path is the 2.x session's _connection, read only to track handshake-era sessions for change notifications.
  • The native flavor depends on no SDK at all.
# pyproject.toml of an app that mounts cloudg into an MCPServer / FastMCP
dependencies = [
  "cloudg[mcp]>=0.6,<0.7",
  "mcp>=2.3,<2.4",          # or "mcp>=1.30,<1.31" on the 1.x line
  # "fastmcp>=4.0.11,<4.1", # only if you mount into fastmcp
]