py2mcp.http#
Serve a py2mcp FastMCP server over Streamable HTTP with optional OAuth 2.1.
The remote counterpart to py2mcp.serve (stdio). A remote MCP server (e.g. a claude.ai custom connector) is reached over public
HTTPS from the vendor’s cloud and authenticates with OAuth 2.1. Per the MCP
authorization spec the MCP server is an OAuth 2.1 resource server — it
validates bearer tokens minted by a managed identity provider (the
authorization server) and never issues tokens itself. Two hard rules fall out
of that and are enforced here by construction:
Audience binding (RFC 8707). The verifier checks the token’s
audequals this server’s resource id, so a token minted for another service cannot be replayed here (the confused-deputy defense).No token passthrough. This layer only verifies the inbound token; it never forwards it upstream. Any upstream call your tools make must use their own credentials.
It wraps FastMCP’s native machinery (no transport/OAuth code is reinvented):
mk_auth_provider()— an auth-config dict → afastmcp.server.auth.RemoteAuthProvider(aJWTVerifierresource server that validates the IdP’s JWTs and publishes the RFC 9728/.well-known/oauth-protected-resourcedocument pointing at the IdP).mk_http_app()— build the server (viapy2mcp.mk_mcp_from_refs()), attach the auth provider, and return a Streamable-HTTP ASGI app to run under any ASGI server (uvicorn, gunicorn, a serverless adapter).serve_http()— build and run it (blocking), for a self-hosted process.
coact’s claude-remote-connector publish target scaffolds a deployable
service around these — coact writes packaging, py2mcp builds and serves the MCP
server (the same division of labour as the stdio .mcpb path).
Building the app performs no network I/O, so it is safe to do at import time:
>>> from py2mcp.http import mk_http_app
>>> app = mk_http_app(['os.path:basename'], name='Paths')
>>> [route.path for route in app.routes]
['/mcp']
Module Attributes
Auth |
|
Default Streamable-HTTP transport (the current remote MCP transport). |
Functions
|
Build a FastMCP resource-server auth provider from an auth-config dict. |
|
Build a Streamable-HTTP ASGI app from |
|
Build and run a Streamable-HTTP MCP server (blocking) via FastMCP/uvicorn. |
- py2mcp.http.DFLT_TRANSPORT = 'streamable-http'#
Default Streamable-HTTP transport (the current remote MCP transport).
- py2mcp.http.SUPPORTED_AUTH_TYPES = ('jwt',)#
Auth
typevaluesmk_auth_provider()understands.'jwt'is the vendor-neutral resource-server pattern (validate a managed IdP’s JWTs); managed-provider shortcuts (auth0/workos/…) can be added as new types.
- py2mcp.http.mk_auth_provider(auth)[source]#
Build a FastMCP resource-server auth provider from an auth-config dict.
authisNone/falsy (no auth) or a dict with atypekey:type='jwt'(default) — validate JWTs issued by a managed IdP. Keys:jwks_uriorpublic_key— where to get the IdP’s signing key(s).issuer— the IdP issuer URL (the token’siss).audience(required) — this server’s resource id (the token’saud). RFC 8707 audience binding is mandatory: it stops a token minted for another service being replayed here (the confused-deputy defense), so this helper refuses to build a verifier that would skip it.authorization_servers(or a singleissuer) — IdP issuer URL(s) advertised in the RFC 9728 protected-resource metadata.base_url— this server’s public base URL.required_scopes(optional) — scopes every request must carry.
Building the provider performs no network I/O (key fetching is lazy, on the first request), so this is safe to call at scaffold/import time.
- Parameters:
auth (
Optional[dict]) – The auth-config dict described above, orNone/{}for no authentication.- Return type:
- Returns:
A
RemoteAuthProvider(a resource server), orNonewhenauthis falsy.- Raises:
ValueError –
authis not a dict, itstypeis not supported, or a required key (jwks_uri/public_key,base_url,audience,authorization_servers/issuer) is missing.
Examples
>>> mk_auth_provider(None) is None True >>> provider = mk_auth_provider({ ... 'type': 'jwt', ... 'jwks_uri': 'https://idp.example.com/.well-known/jwks.json', ... 'issuer': 'https://idp.example.com', ... 'audience': 'https://conn.example.com/mcp', ... 'base_url': 'https://conn.example.com', ... }) >>> type(provider).__name__ 'RemoteAuthProvider' >>> provider.authorization_servers ['https://idp.example.com']
Leaving out the audience is refused rather than silently unchecked:
>>> mk_auth_provider({'type': 'jwt', 'jwks_uri': 'https://idp.example.com/jwks', ... 'base_url': 'https://conn.example.com'}) Traceback (most recent call last): ... ValueError: jwt auth needs 'audience' (this server's resource id). ...
See also
mk_http_app(): where the provider is attached to a server.
- py2mcp.http.mk_http_app(refs, *, name='py2mcp Server', auth=None, input_trans=None, transport='streamable-http', path=None, stateless_http=None, middleware=None, instructions=None, prompts=None, resources=None, icons=None, annotations=None)[source]#
Build a Streamable-HTTP ASGI app from
refs(+ optional OAuth).Returns the ASGI application (a Starlette app), so any ASGI server can run it:
# server/app.py from py2mcp.http import mk_http_app app = mk_http_app(['mypkg.tools:summarize'], name='My Connector', auth=AUTH) # then: uvicorn server.app:app --host 0.0.0.0 --port 8000
Builds the app with no network I/O.
- Parameters:
refs (
Iterable[str]) –'module:function'references, one per tool.name (
str) – Server name.auth (
Optional[dict]) – Resolved bymk_auth_provider()(None→ no auth; a remote connector should always set it).input_trans (
Optional[Callable[[dict],dict]]) – Forwarded topy2mcp.mk_mcp_from_refs().transport (
str) – The FastMCP HTTP transport;DFLT_TRANSPORTis Streamable HTTP.path (
Optional[str]) – URL path the MCP endpoint is mounted at;Nonekeeps FastMCP’s default (/mcp).stateless_http (
Optional[bool]) –Trueis recommended behind a load balancer (MCP sessions are stateful, so default in-memory sessions break across replicas — go stateless or externalize session state).Nonekeeps FastMCP’s default.middleware (
Optional[Any]) – A single FastMCP middleware or a list, attached for cross-cutting concerns — metering, logging, rate limiting. Becauseauthruns first, it can read the authenticated caller viafastmcp.server.dependencies.get_access_token().instructions (
Optional[str]) – The server’s model-facing description (surfaced to the connecting client/model).prompts (
Union[Callable,Iterable[Callable],None]) – Forwarded topy2mcp.mk_mcp_from_refs().resources (
Optional[Mapping[str,Callable]]) – Forwarded topy2mcp.mk_mcp_from_refs().icons (
Optional[Any]) – Forwarded topy2mcp.mk_mcp_from_refs().annotations (
Optional[Any]) – Forwarded topy2mcp.mk_mcp_from_refs()(per-tool MCP hints).
- Return type:
- Returns:
The Starlette ASGI application.
- Raises:
ValueError –
authis malformed (seemk_auth_provider()) or a reference cannot be parsed (seepy2mcp.mk_mcp_from_refs()).
Examples
>>> app = mk_http_app(['os.path:basename'], name='Paths') >>> callable(app) True >>> [route.path for route in app.routes] ['/mcp']
With OAuth and a custom mount path, the RFC 9728 protected-resource metadata route is added next to the endpoint:
>>> AUTH = { ... 'type': 'jwt', ... 'jwks_uri': 'https://idp.example.com/.well-known/jwks.json', ... 'issuer': 'https://idp.example.com', ... 'audience': 'https://conn.example.com/mcp', ... 'base_url': 'https://conn.example.com', ... } >>> app = mk_http_app(['os.path:basename'], name='Paths', auth=AUTH, path='/api/mcp') >>> [route.path for route in app.routes] ['/.well-known/oauth-protected-resource/api/mcp', '/api/mcp']
See also
serve_http(): build and run in-process instead of returning the app.py2mcp.serve.serve_stdio(): the local stdio counterpart.
- py2mcp.http.serve_http(refs, *, name='py2mcp Server', host='127.0.0.1', port=8000, auth=None, input_trans=None, transport='streamable-http', stateless_http=None, middleware=None, instructions=None, prompts=None, resources=None, icons=None, annotations=None)[source]#
Build and run a Streamable-HTTP MCP server (blocking) via FastMCP/uvicorn.
For a self-hosted process. Binds
127.0.0.1by default — expose a public interface only behind a TLS-terminating reverse proxy (a remote connector must be reachable over public HTTPS, and binding locally is the spec’s DNS-rebinding-safe default).authis resolved bymk_auth_provider();middleware(a single FastMCP middleware or a list) is attached as inmk_http_app();instructionssets the server’s model-facing description.prompts/resourcesare forwarded topy2mcp.mk_mcp_from_refs().- Return type: