Skip to main content

py2mcp

Quick MCP (Model Context Protocol) server creation from Python functions.

For AI agents

py2mcp publishes its documentation in forms made for coding agents. If you are one, start here.

The documentation, machine-readable: llms.txt indexes every page; py2mcp.md is the whole documentation in one file; every page has a .md twin; objects.inv maps symbols to URLs.

If you identify as a dinosaur, the rest of this README is written for you, starting at Installation.

Installation

pip install py2mcp

Quick Start

from py2mcp import mk_mcp_server


def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b


def greet(name: str = "world") -> str:
    """Greet someone"""
    return f"Hello, {name}!"


# Create and run MCP server
mcp = mk_mcp_server([add, greet])

if __name__ == "__main__":
    mcp.run()

That's it! Your functions are now available as MCP tools.

Features

  • Simple: Just pass functions to mk_mcp_server()
  • Flexible: Supports input/output transformations
  • Pythonic: Clean, decorator-free function definitions
  • Powerful: Built on FastMCP for production-ready servers

Input Transformations

Transform inputs before they reach your functions:

from py2mcp import mk_mcp_server, mk_input_trans
import numpy as np


def add_arrays(a, b):
    """Add two numpy arrays"""
    return (a + b).tolist()


# Convert list inputs to numpy arrays
input_trans = mk_input_trans({"a": np.array, "b": np.array})
mcp = mk_mcp_server([add_arrays], input_trans=input_trans)

From Stores (MutableMapping)

Automatically expose CRUD operations from any mapping:

from py2mcp import mk_mcp_from_store

projects = {"proj1": {"name": "Project 1"}, "proj2": {"name": "Project 2"}}
mcp = mk_mcp_from_store(projects, name="project")

# Automatically creates: list_projects, get_project, set_project, delete_project

Serving: local (stdio) and remote (HTTP + OAuth)

mk_mcp_* build a server object; py2mcp also gives you two ways to run one.

Local (stdio) — for a one-click bundle (e.g. a Claude Desktop .mcpb):

from py2mcp import serve_stdio

serve_stdio(["mypkg.tools:summarize", "mypkg.tools:translate"], name="My Tools")
# or:  python -m py2mcp --config py2mcp_config.json

Remote (Streamable HTTP + OAuth 2.1) — for a hosted MCP server reached from a vendor's cloud (e.g. a claude.ai custom connector). The server is an OAuth 2.1 resource server: it validates a managed IdP's JWTs (audience-bound per RFC 8707) and never issues tokens itself.

from py2mcp.http import mk_http_app

AUTH = {
    "type": "jwt",  # resource-server: validate the IdP's JWTs
    "jwks_uri": "https://idp.example.com/.well-known/jwks.json",
    "issuer": "https://idp.example.com",
    "audience": "https://my-connector.example.com/mcp",  # THIS server (RFC 8707)
    "authorization_servers": ["https://idp.example.com"],
    "base_url": "https://my-connector.example.com",
    "required_scopes": ["mcp:read"],
}

# An ASGI app you run under any ASGI server (uvicorn, gunicorn, serverless):
app = mk_http_app(["mypkg.tools:summarize"], name="My Connector", auth=AUTH)
#   uvicorn server.app:app --host 0.0.0.0 --port 8000   (behind TLS)

serve_http(...) builds and runs it in-process (FastMCP/uvicorn). Both wrap FastMCP's native transports/OAuth — py2mcp does not reinvent them.

Middleware (metering, logging, rate-limiting)

Every builder accepts middleware= — a single FastMCP middleware or an iterable of them — attached at construction, exactly as auth= is. It's the one clean seam for cross-cutting concerns that must wrap every tool call (usage metering, cost logging, audit trails, rate limiting), so you don't decorate each function individually — and can't forget one (a missed decorator on a paid tool means untracked cost):

from fastmcp.server.middleware import Middleware


class UsageMeter(Middleware):
    async def on_call_tool(self, context, call_next):
        result = await call_next(context)  # the tool runs here
        record(context.message.name)  # ... then meter it
        return result


mcp = mk_mcp_server([render, estimate], middleware=[UsageMeter()])
# same on mk_mcp_from_refs(...), mk_mcp_from_store(...), mk_http_app(...),
#         serve_http(...), serve_stdio(...)

On the remote path auth= (transport-level) runs first, so a middleware can read the authenticated caller via fastmcp.server.dependencies.get_access_token(). Middleware is a programmatic hook — it takes Python objects, so it isn't wired through the python -m py2mcp CLI / JSON-config path (unlike refs/name/auth).

Instructions (the server's model-facing description)

Every builder also accepts instructions= — a natural-language string surfaced to the connecting client/model as the server's instructions, attached at construction exactly like auth=/middleware=. It's the place to say what the tools are for and the intended workflow, so a model can orient itself without calling a tool:

mcp = mk_mcp_server(
    [render, estimate],
    instructions="Turn source docs into narrated audio. Always estimate_cost before a render.",
)
# same keyword on mk_mcp_from_refs(...), mk_mcp_from_store(...), mk_http_app(...),
#                 serve_http(...), serve_stdio(...)

Like middleware=, it's a programmatic argument (not yet wired through the python -m py2mcp CLI / JSON-config path).

Prompts and resources

Every builder also accepts prompts= and resources=, so a server that ships MCP prompts and resources alongside its tools can be built declaratively in one call, instead of reaching past the builder to register them by hand on the returned FastMCP object:

def summarize_request(topic: str) -> str:
    return f"Summarize the latest on {topic}."


def schema() -> dict:
    return {"type": "object"}


mcp = mk_mcp_server(
    [render, estimate],
    prompts=summarize_request,  # a callable, or an iterable of them
    resources={"schema://analysis": schema},  # {uri: callable}
)
# same keywords on mk_mcp_from_refs(...), mk_mcp_from_store(...), mk_http_app(...),
#                  serve_http(...), serve_stdio(...)

prompts accepts a single callable or an iterable, normalized the same way funcs is for tools. resources is a {uri: callable} mapping — each callable is invoked to produce that resource's content when a client reads its URI.

Once a server is hosted, the last mile is getting a human to add it. There's no true one-click install for an unlisted connector (listing requires Anthropic review), but a prefilled link opens the add-connector modal with the name and URL already filled in, so the user only has to confirm:

from py2mcp import claude_install_link, markdown_install_badge

claude_install_link("snout", "https://example.com/api/snout_mcp/mcp")
# 'https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=snout&...'

markdown_install_badge("snout", "https://example.com/api/snout_mcp/mcp")
# '[Add snout to Claude](https://claude.ai/customize/connectors?...)'  <- paste into a README

claude_install_link("snout", "...", admin=True)  # org-wide page, not per-user

Both are pure string functions (stdlib only, no server needed). Three caveats the link itself can't express:

  • Custom connectors are a paid-plan feature, so the link goes nowhere for a Free-plan user.
  • admin=True targets the org-wide install page — the right one when an admin is rolling a connector out to a workspace, the wrong one for a personal install.
  • A link is not an access grant. If the server is an OAuth resource server with an allowlist (see above), someone not on it can follow the link, complete the flow, and still be refused. Hand out the link together with whatever adds them to the allowlist.

License

MIT

Release files for py2mcp 0.1.15

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py2mcp 0.1.15
File Size Uploaded
py2mcp-0.1.15.tar.gz 46.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for py2mcp 0.1.15
File Interpreter ABI Platform
py2mcp-0.1.15-py3-none-any.whl Python 3 none any Details

Total release size: 74.1 kB

Release files / py2mcp-0.1.15.tar.gz

Download URL py2mcp-0.1.15.tar.gz
Size 46.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7b136e62ddbf01e755cf73e895e63251c7d5f9b1cf6072b4a31a9fa5bce9e048
BLAKE2b-256 checksum
How to use checksums
6928e4d7253a4217069f313b2208f8aa83f73855f8f6174923f3e17d9dfcd815
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / py2mcp-0.1.15-py3-none-any.whl

Download URL py2mcp-0.1.15-py3-none-any.whl
Size 27.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2f16b875cbc0772b89d4a0807d59a9cab1a7934ba66d0189075376d09d66f698
BLAKE2b-256 checksum
How to use checksums
768c7dc05e8e12bfa21ec5dd2031ff62b9bfc330f998409cb186a3af4a6c9b7e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.15 This release

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release 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