Skip to main content

foxxe-mcp

Shared MCP plumbing for the FoxxeLabs server fleet. One SDK pin, one bootstrap, thin re-export of the SDK server class — so a spec revision is one commit here, not eleven latent crash-loops.

See docs/PRD-foxxe-mcp.md for why. See docs/migration.md for how to convert a server.

Status: v0.1.0 — library built, fleet unmigrated.

Two shapes

The fleet splits evenly, and the library supports both as first-class.

1. The MCP app is the app (ainm, git-mcp) — use serve() / build_app().

2. MCP is mounted inside a parent app (tomhas's FastAPI dashboard, mnemos's Starlette API) — use mounted_app(), keep your own lifespan and /health:

from foxxe_mcp import MCPServer, mounted_app, version_payload

mcp = MCPServer("tomhas")

@asynccontextmanager
async def lifespan(app):
    async with mcp.session_manager.run():   # same name on both SDK majors
        yield

app = FastAPI(lifespan=lifespan)
app.get("/version")(lambda: version_payload("tomhas"))
app.mount("/", mounted_app(mcp, allowed_host=EXTERNAL_HOST))   # last: routes above win

mounted_app() carries the fix for Starlette stripping the leading / under Mount("/") — plumbing mnemos and tomhas had each written by hand.

The shape

from foxxe_mcp import MCPServer, serve

mcp = MCPServer("tomhas")

@mcp.tool()
def status() -> str:
    """Report the current run state."""
    ...

if __name__ == "__main__":
    serve(mcp)

MCPServer is the SDK's own class, re-exported unwrapped. The tool decorator is the SDK's, reached through the instance — foxxe-mcp never wraps the registration API (PRD NG2), so every SDK feature is available the day it ships.

serve() owns what eleven servers currently hand-roll: transport, bind address, allowed host, /health, /version, logging, graceful shutdown.

Why the name is MCPServer on an SDK that calls it FastMCP

v0.1 pins mcp>=1.29.1,<2, where the class is FastMCP. The export is named MCPServer anyway — that is the mcp 2.x name, and exporting it now means the eventual pin flip changes this library and no server code at all.

That is a claim, so it is tested: scripts/verify_sdk2.py runs the same library against mcp==2.1.1 and checks that MCPServer resolves to the renamed class, that a tool still registers, and that /health, /version and /mcp all still answer. Both majors, one codebase.

$ python scripts/verify_sdk2.py
foxxe-mcp 0.1.0 against mcp 2.1.1
  PASS  SDK major detected as 2 — mcp.server.mcpserver
  PASS  MCPServer resolves to the renamed class
  ...
All checks passed — the v1.0 pin flip is a one-line change.

The bound is enforced twice

Once at install time, in pyproject.toml, and again at boot, in check_sdk(). The second one is not redundant. PRD finding B: rialu declares no mcp at all, only an unbounded fastmcp, and fastmcp 3.x pulls mcp>=2 transitively — a declaration that binds only at install time cannot catch what a resolver did to a transitive dependency. serve() calls check_sdk() before the first tool call, so a wrong resolution fails at boot, next to its cause.

mcp out of range is fatal. fastmcp out of range warns, and is fatal under FOXXE_MCP_STRICT_SDK=1.

Install

foxxe-mcp==0.5.0

Add the fastmcp extra only if the server actually uses the third-party fastmcp package: foxxe-mcp[fastmcp]==0.5.0. Never declare mcp or fastmcp directly in a server again — that is the whole point.

Published to PyPI rather than installed as a git dependency, which is a deliberate amendment to PRD §7. The git-by-tag plan died on contact with the first real deploy: this repo is private, so pip install git+https://... inside a Docker build has no credentials and fails with exit 128. The alternatives were a build-time PAT threaded through eleven Dockerfiles, or making the repo public — which would publish fleet.toml and the internal audit along with it. PyPI keeps the repo private, publishes only the package, and makes every Dockerfile a plain pip install again. NG3 rules out a private index; this is the public one.

Fleet operations

foxxe-herd status              # poll every server's /version — who is on what
foxxe-herd redeploy            # dry run
foxxe-herd redeploy --yes      # fly deploy every migrated server

fleet.toml maps server name to Fly app name (verified against flyer apps_list, not guessed — the -foxxelabs suffix is inconsistent) and records which servers are migrated and which are still unbounded.

Environment

Variable Default Meaning
PORT 8080 Port to bind
BIND_HOST 0.0.0.0 Bind address (Fly requires all interfaces)
ALLOWED_HOST $FLY_APP_NAME.fly.dev Public hostname for DNS-rebinding protection — not the bind address
MCP_TRANSPORT http http or stdio
LOG_LEVEL INFO Root log level
LOG_FORMAT json text for human-readable
MCP_BEARER_TOKENS unset Comma-separated; presence enables bearer auth
FOXXE_MCP_STRICT_SDK unset 1 makes an out-of-range fastmcp fatal
FOXXE_MCP_DNS_REBINDING unset 1 re-enables Host/Origin checking (off by default — the edge pins the Host)
TAISCE_SERVICE_TOKEN unset Service token for taisce_fetch()
SENTINEL_URL unset Presence enables sentinel_push()

Development

python -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/python -m pytest

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

foxxe_mcp-0.5.4.tar.gz (29.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

foxxe_mcp-0.5.4-py3-none-any.whl (29.2 kB view details)

Uploaded Python 3

File details

Details for the file foxxe_mcp-0.5.4.tar.gz.

File metadata

  • Download URL: foxxe_mcp-0.5.4.tar.gz
  • Upload date:
  • Size: 29.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for foxxe_mcp-0.5.4.tar.gz
Algorithm Hash digest
SHA256 79c5439f31a0b4dd1e10b650b1a93a3336608d5213279d2b377803bd43ec623d
MD5 a8ab90d816cbb27e6c8b94e24245dae0
BLAKE2b-256 f3a19bbb2e5c2977d199a2e5b7d8ecc8a370c117a0d652d4319c758d007410e6

See more details on using hashes here.

File details

Details for the file foxxe_mcp-0.5.4-py3-none-any.whl.

File metadata

  • Download URL: foxxe_mcp-0.5.4-py3-none-any.whl
  • Upload date:
  • Size: 29.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for foxxe_mcp-0.5.4-py3-none-any.whl
Algorithm Hash digest
SHA256 b3424d5f7adc311de75a27b671ddbf43d2f370d0ae76c40d458c3139c5c9cc15
MD5 1b3ed5ae40e9d7adfabb9e0d5946f9eb
BLAKE2b-256 80c391c43c8bcafe0caafff42074900b1788e9f9193a462a57a49e133c1cf2bd

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.4 This release

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.1

2 files

0.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page