closedmcp
A small Python wrapper for Log in with ClosedMCP. It protects Streamable HTTP requests made to the official SDK’s Server or built-in FastMCP, or standalone FastMCP 2, verifies a signed pass on every call, and serves MCP protected-resource metadata.
Add to an existing server
Keep your existing official mcp.server.fastmcp.FastMCP or standalone fastmcp.FastMCP 2 instance. Replace its HTTP serving call with:
import os
from closedmcp import current_user, serve_with_closedmcp
serve_with_closedmcp(app, resource=os.environ["MCP_URL"], allowed_domains=os.environ["ALLOWED_DOMAINS"].split(","))
In any tool, use current_user().email or current_user().org. For an existing ASGI host, serve closedmcp_app(app, resource=..., allowed_domains=...) instead. Install with pip install closedmcp for the official SDK or pip install 'closedmcp[fastmcp]' for standalone FastMCP 2.
Hello
from mcp.server.fastmcp import FastMCP
from closedmcp import current_user, serve_with_closedmcp
app = FastMCP("hello")
@app.tool()
def hello() -> str:
return f"Hello {current_user().email}"
serve_with_closedmcp(
app,
resource="https://example.com/mcp",
allowed_domains=["example.com"])
Replace the first import with from fastmcp import FastMCP to use standalone FastMCP 2. Install with pip install closedmcp for the official SDK or pip install 'closedmcp[fastmcp]' for both. This repository also has a runnable template: install the package locally with pip install -e ., then run MCP_URL=https://example.com/mcp ALLOWED_DOMAINS=example.com python template/server.py. Set PORT and HOST for the listening socket. CLOSEDMCP_ORIGIN and CLOSEDMCP_JWKS_URL are available for local integration tests.
resource is the exact public MCP URL and JWT audience. allowed_domains must contain at least one verified company domain. By default, the wrapper accepts passes from https://closedmcp.com and the legacy https://closedmcp.canpai.io, using each issuer's JWKS. An explicit issuer accepts only that issuer. The wrapper checks the EdDSA signature, issuer, exact audience, expiry, five-minute maximum lifetime, required identity claims, and company domain. Unknown key IDs trigger at most one JWKS refresh per 30 seconds. current_user() returns a Person with id, email, company, org, and optional aiApp; it raises outside a verified request. aiApp is a client supplied display value.
For /mcp, metadata is at /.well-known/oauth-protected-resource/mcp and the generic metadata path. A missing or invalid pass receives 401 with WWW-Authenticate: Bearer resource_metadata="<metadata URL>". Authenticated non-POST requests receive 405. To use another ASGI server, call closedmcp_app(app, resource=..., allowed_domains=...) and serve the returned ASGI app. Its lifespan must be run by the ASGI server.
See the ClosedMCP documentation.
Metadata
Release files for closedmcp 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| closedmcp-0.1.1.tar.gz | 12.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| closedmcp-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.8 kB
Release files / closedmcp-0.1.1.tar.gz
| Download URL | closedmcp-0.1.1.tar.gz |
|---|---|
| Size | 12.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e0dc6a6e09c7ff4507a38ae3faee19d8a1873b2d20f9ee203ecd31b7bcba23cd
|
|
BLAKE2b-256 checksum How to use checksums |
100326cf3a59b9e29a5669a65dec9d4d3fc09ad798e365cc2ad0bd64060c811e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / closedmcp-0.1.1-py3-none-any.whl
| Download URL | closedmcp-0.1.1-py3-none-any.whl |
|---|---|
| Size | 10.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf7116aaf3ece1cfc7f579538002510e480091d07561e079da0a76899ce2d5c2
|
|
BLAKE2b-256 checksum How to use checksums |
add73858ca429e4540257ef5edf89e595005c3735e4c4203c676fb8b89544c16
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|