py2mcp
py2mcp: Quick MCP server creation from Python functions.
This package provides a simple, Pythonic way to create Model Context Protocol (MCP) servers from ordinary Python functions. Built on FastMCP, it handles all the protocol complexity while letting you focus on your business logic.
- Basic usage:
>>> from py2mcp import mk_mcp_server >>> >>> def add(a: int, b: int) -> int: ... '''Add two numbers''' ... return a + b >>> >>> mcp = mk_mcp_server([add]) >>> # mcp.run() # Start the server
- py2mcp.import_object(ref: str) Any[source]
Resolve a
'module.path:attr'(preferred) or'module.path.attr'reference.Useful for building MCP servers from configuration strings (e.g. tool references declared in a file), so callers don’t reimplement the
importlibdance.>>> import_object('json:dumps') <function dumps at ...> >>> import_object('os.path.join') <function join at ...>
- py2mcp.load_server_config(path: str | Path) dict[source]
Load a server config JSON of the form
{"name": str, "refs": [str, ...]}.Raises
ValueErrorif the file is not a JSON object carrying arefslist — an actionable error beats a server that starts with no tools.
- py2mcp.mk_auth_provider(auth: dict | None) Any | None[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.
Returns a
RemoteAuthProvider(a resource server), orNonewhenauthis falsy. RaisesValueErroron an unknowntypeor a missing required key. 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.
- py2mcp.mk_http_app(refs: Iterable[str], *, name: str = 'py2mcp Server', auth: dict | None = None, input_trans: Callable[[dict], dict] | None = None, transport: str = 'streamable-http', path: str | None = None, stateless_http: bool | None = None) Any[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
authis resolved bymk_auth_provider()(None→ no auth; a remote connector should always set it).stateless_http=Trueis recommended behind a load balancer (MCP sessions are stateful, so default in-memory sessions break across replicas — go stateless or externalize session state). Builds the app with no network I/O.
- py2mcp.mk_input_trans(name_func_relationships: Mapping | None = None) Callable[[dict], dict][source]
Create an input transformation function from name->func mappings.
>>> def to_int(x): return int(x) >>> trans = mk_input_trans({'x': to_int}) >>> trans({'x': '42', 'y': 'hello'}) {'x': 42, 'y': 'hello'}
- py2mcp.mk_mcp_from_refs(refs: Iterable[str], *, name: str = 'py2mcp Server', input_trans: Callable[[dict], dict] | None = None, auth: Any | None = None) FastMCP[source]
Create an MCP server from
'module:function'reference strings.Resolves each reference to a callable via
py2mcp.util.import_object()and delegates tomk_mcp_server(). One call from config strings to a runnable server — what tools that read tool references from a file (e.g.coact’smcpbackend) need.authis forwarded tomk_mcp_server()(the remote/HTTP path attaches an OAuth provider here).Examples
>>> mcp = mk_mcp_from_refs(['os.path:basename', 'os.path:dirname'], name='Paths') >>> mcp.name 'Paths'
- py2mcp.mk_mcp_from_store(store: MutableMapping[Any, Any], *, name: str = 'item', plural: str = '', server_name: str | None = None) FastMCP[source]
Create an MCP server from a MutableMapping with CRUD operations.
Automatically generates list, get, set, and delete functions for the store.
- Parameters:
store – A MutableMapping to expose via MCP
name – Singular name for items (e.g., ‘project’, ‘user’)
plural – Plural form (defaults to name + ‘s’)
server_name – Name of the MCP server (defaults to “{name} Store”)
- Returns:
A FastMCP server with CRUD operations
Examples
>>> projects = {'p1': {'name': 'Project 1'}, 'p2': {'name': 'Project 2'}} >>> mcp = mk_mcp_from_store(projects, name='project') >>> mcp.name 'project Store'
- py2mcp.mk_mcp_server(funcs: Callable | Iterable[Callable], *, name: str = 'py2mcp Server', input_trans: Callable[[dict], dict] | None = None, auth: Any | None = None) FastMCP[source]
Create an MCP server from Python functions.
This is the main entry point for py2mcp. Pass one or more functions, and get back a FastMCP server ready to run.
- Parameters:
funcs – A function or iterable of functions to expose as MCP tools
name – Name of the MCP server
input_trans – Optional function to transform input kwargs before calling tools
auth – Optional
fastmcp.server.authprovider attached at construction — used by the remote (HTTP) path for OAuth 2.1 (seepy2mcp.http).None(the default) leaves the server unauthenticated, which is correct for the local stdio path.
- Returns:
A FastMCP server instance ready to run
Examples
>>> def add(a: int, b: int) -> int: ... '''Add two numbers''' ... return a + b >>> mcp = mk_mcp_server(add) >>> mcp.name 'py2mcp Server'
>>> def greet(name: str) -> str: ... return f"Hello, {name}!" >>> mcp = mk_mcp_server([add, greet], name="Math & Greetings") >>> mcp.name 'Math & Greetings'
- py2mcp.resolve_server_config(*, config: str | Path | None = None, refs: Iterable[str] = (), name: str | None = None) tuple[list[str], str][source]
Merge a config file and explicit
refs/nameinto(refs, name).Refs from
--refare appended after any from the config file; an explicitnamewins over the config’s. Pure (no I/O beyond readingconfig), so it is unit-testable without standing up a server.>>> resolve_server_config(refs=['os.path:basename'], name='Paths') (['os.path:basename'], 'Paths')
- py2mcp.serve_http(refs: Iterable[str], *, name: str = 'py2mcp Server', host: str = '127.0.0.1', port: int = 8000, auth: dict | None = None, input_trans: Callable[[dict], dict] | None = None, transport: str = 'streamable-http', stateless_http: bool | None = None) 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().
- py2mcp.serve_stdio(refs: Iterable[str], *, name: str = 'py2mcp Server', input_trans: Callable[[dict], dict] | None = None) None[source]
Build an MCP server from
'module:function'refs and run it over stdio.Blocks, serving the MCP protocol on stdin/stdout until the host disconnects. Thin wrapper over
py2mcp.mk_mcp_from_refs()+FastMCP.runso that packaged integrations have one command to launch.