# py2mcp

py2mcp: Quick MCP server creation from Python functions.

Pass ordinary Python functions and get back a Model Context Protocol (MCP)
server, built on FastMCP, with each function registered as a tool. The
`mk_mcp_*` builders return a server *object* and leave running it to you:
`mcp.run()` for stdio, or [`py2mcp.serve`](py2mcp.serve.html.md#module-py2mcp.serve) and [`py2mcp.http`](py2mcp.http.html.md#module-py2mcp.http) for a
packaged stdio launcher and a Streamable-HTTP (optionally OAuth 2.1) server.

Main entry points:

- `mk_mcp_server`: functions in, `FastMCP` server out
- `mk_mcp_from_refs`: the same from `'module:function'` strings
- `mk_mcp_from_store`: list/get/set/delete tools over any `MutableMapping`
- `mk_input_trans`: per-argument conversion of tool inputs
- `serve_stdio` and `serve_http`: build from refs and run
- `UsageLogger`: one record per tool call, to a sink (`py2mcp usage` reads it)

```pycon
>>> 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.name
'py2mcp Server'
>>> # mcp.run()  # Start the server over stdio
```

### Functions

| [`mk_mcp_server`](#py2mcp.mk_mcp_server)(funcs, \*[, name, input_trans, ...])   | Create an MCP server from Python functions.                                    |
|-------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|
| [`mk_mcp_from_store`](#py2mcp.mk_mcp_from_store)(store, \*[, name, plural, ...])    | Create an MCP server from a MutableMapping with CRUD operations.               |
| [`mk_mcp_from_refs`](#py2mcp.mk_mcp_from_refs)(refs, \*[, name, ...])              | Create an MCP server from `'module:function'` reference strings.               |
| [`mk_input_trans`](#py2mcp.mk_input_trans)([name_func_relationships])            | Create an input transformation function from name->func mappings.              |
| [`import_object`](#py2mcp.import_object)(ref)                                   | Resolve a `'module.path:attr'` (preferred) or `'module.path.attr'` reference.  |
| [`claude_install_link`](#py2mcp.claude_install_link)(name, mcp_url, \*[, admin])      | Prefilled "Add custom connector" link for claude.ai.                           |
| [`markdown_install_badge`](#py2mcp.markdown_install_badge)(name, mcp_url, \*[, admin])   | Markdown link that installs an MCP server as a claude.ai connector.            |
| [`serve_stdio`](#py2mcp.serve_stdio)(refs, \*[, name, input_trans, ...])      | Build an MCP server from `'module:function'` refs and run it over stdio.       |
| [`resolve_server_config`](#py2mcp.resolve_server_config)(\*[, config, refs, name])      | Merge a config file and explicit `refs`/`name` into `(refs, name)`.            |
| [`load_server_config`](#py2mcp.load_server_config)(path)                             | Load a server config JSON of the form `{"name": str, "refs": [str, ...]}`.     |
| [`mk_http_app`](#py2mcp.mk_http_app)(refs, \*[, name, auth, ...])             | Build a Streamable-HTTP **ASGI app** from `refs` (+ optional OAuth).           |
| [`serve_http`](#py2mcp.serve_http)(refs, \*[, name, host, port, ...])        | Build and **run** a Streamable-HTTP MCP server (blocking) via FastMCP/uvicorn. |
| [`mk_auth_provider`](#py2mcp.mk_auth_provider)(auth)                               | Build a FastMCP **resource-server** auth provider from an auth-config dict.    |
| [`mapping_sink`](#py2mcp.mapping_sink)(store, \*[, key])                       | A sink that does `store[key(record)] = record` — any `MutableMapping`.         |

### Classes

| [`UsageLogger`](#py2mcp.UsageLogger)([sink, name, version, caller, ...])   | FastMCP middleware: one record per tool call (and per handshake) to `sink`.   |
|----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
| [`JsonlSink`](#py2mcp.JsonlSink)([root, retention_days, server, strict]) | Append records to one JSON-lines file per UTC day; prune old days.            |

### *class* py2mcp.JsonlSink(root=None, , retention_days=90, server='server', strict=False)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Append records to one JSON-lines file per UTC day; prune old days.

Files are `<root>/<YYYY-MM-DD>.jsonl`. Files older than `retention_days`
(`0` keeps today only, `None` keeps everything) are pruned at
construction and on the first write of each new day. Each write is one
`O_APPEND` write of one line, so concurrent workers on the same host
interleave whole lines. `root` defaults to `default_usage_dir()`; it
must be **one directory per server**, since the day files carry no server
name and pruning covers the whole directory.

The sink is built to be attached to a live server, so by default it does not
raise: a directory that cannot be created at construction is reported via
`logging` and retried on every write (`strict=True` raises instead), and
a directory removed at runtime is recreated on the next write.

```pycon
>>> import tempfile
>>> sink = JsonlSink(tempfile.mkdtemp(), retention_days=None)
>>> sink({'id': 'a', 'ts': '2026-10-02T10:00:00+00:00', 'event': 'tool_call'})
>>> sorted(p.name for p in sink.root.iterdir())
['2026-10-02.jsonl']
```

#### prune(, today=None)

Delete day files older than `retention_days`; return what was removed.

* **Return type:**
  [`list`](https://docs.python.org/3/builtins/stdtypes.html#list)[[`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path)]

### *class* py2mcp.UsageLogger(sink=None, \*, name='server', version=None, caller=<function token_caller>, outcome=<function default_outcome>, include_args=True, redact=(), max_args_chars=4000, max_error_chars=500, handshakes=True)

Bases: `Middleware`

FastMCP middleware: one record per tool call (and per handshake) to `sink`.

* **Parameters:**
  * **sink** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)], [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]]) – `record -> None` (sync or `async`). Default: a [`JsonlSink`](#py2mcp.JsonlSink)
    under `default_usage_dir()` for `name`. Any list’s `append`
    works for tests; [`mapping_sink()`](#py2mcp.mapping_sink) adapts a store.
  * **name** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – The connector/server name written into every record (and the
    default sink’s directory).
  * **version** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – The connector version written into every record, if known.
  * **caller** ([`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[], [`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]]) – `() -> str | None` resolving the caller for the in-flight
    request. Default `token_caller()`.
  * **outcome** ([`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)], [`Union`](https://docs.python.org/3/library/typing.html#typing.Union)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Mapping`](https://docs.python.org/3/library/typing.html#typing.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]]]) – `(tool_name, result) -> label | mapping` classifying a result
    that did not raise. Default `default_outcome()`. A mapping is
    merged into the record (it must carry `"outcome"`), so a connector
    that knows its own “no match” shape can also set the true
    `result_count`.
  * **include_args** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – Record the tool arguments at all. `True` by default —
    the arguments are the point of the log — but they are the users’
    questions, so a connector that must not keep them sets `False`.
  * **redact** ([`Collection`](https://docs.python.org/3/library/typing.html#typing.Collection)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – Top-level argument names whose values are replaced with
    `REDACTED` (nested keys are not inspected; pre-shape the
    arguments with `input_trans` or drop them with `include_args`).
  * **max_args_chars** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`int`](https://docs.python.org/3/builtins/functions.html#int)]) – Cap on the serialized arguments stored per record
    (`None` for no cap).
  * **max_error_chars** ([`int`](https://docs.python.org/3/builtins/functions.html#int)) – Cap on the error text stored per record.
  * **handshakes** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – Also record `initialize` requests (as `event:
    "initialize"` with the client’s name/version, and `outcome`
    `error` if the handshake failed), so a connector that is *enabled*
    can be told from one that is *used*.

Error text (an exception message, or an `is_error` result’s text) is
recorded only when `include_args` is true and `redact` is empty, because
it routinely echoes the arguments; otherwise only `error_type` is kept.
The logger never raises into the tool call: a failing sink, caller or
classifier is reported via `logging` (logger `py2mcp.usage`) and the
call proceeds. A cancelled call is recorded as `cancelled` with no text;
other 

```
``
```

BaseException\`\`s (shutdown) pass through unrecorded.

```pycon
>>> records = []
>>> logger = UsageLogger(records.append, name='demo', redact=('token',))
>>> logger.name, logger.redact, logger.records_error_text
('demo', frozenset({'token'}), False)
```

#### *property* records_error_text *: [bool](https://docs.python.org/3/builtins/functions.html#bool)*

Whether error messages are stored (only when every argument is).

### py2mcp.claude_install_link(name, mcp_url, , admin=False)

Prefilled “Add custom connector” link for claude.ai.

There is no true one-click install for an unlisted MCP server (listing
requires Anthropic review), but this link opens the add-connector modal with
the name and URL already filled in, so the user only has to confirm — which
beats “go to Settings, find Connectors, paste this long URL”.

* **Parameters:**
  * **name** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Connector name to prefill (what the user will see in their list).
  * **mcp_url** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Full URL of the MCP endpoint, e.g. `https://host/api/x/mcp`.
  * **admin** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – Target the org-wide install page instead of the per-user one.
    Use it when an admin is rolling the connector out to a whole
    workspace; the default (`False`) is the personal install.
* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)
* **Returns:**
  The claude.ai URL, safe to paste into a README or a chat message.

```pycon
>>> claude_install_link('snout', 'https://example.com/api/snout_mcp/mcp')
'https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=snout&connectorUrl=https%3A%2F%2Fexample.com%2Fapi%2Fsnout_mcp%2Fmcp'
```

Names and URLs are percent-encoded, so spaces (and `&`) can’t break the
query string:

```pycon
>>> claude_install_link('my server', 'https://x.io/mcp', admin=True)
'https://claude.ai/admin-settings/connectors?modal=add-custom-connector&connectorName=my%20server&connectorUrl=https%3A%2F%2Fx.io%2Fmcp'
```

Note that the link is a convenience, not an access grant: if the server is an
OAuth resource server with an allowlist, a user who isn’t on it can follow
the link, complete the flow, and still be refused. Custom connectors are also
a paid-plan feature, so the link goes nowhere for a Free-plan user.

### py2mcp.import_object(ref)

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
`importlib` dance. With a colon, everything before it is the module and
everything after it an attribute path; without one, the last dot splits
module from attribute, so `'pkg.mod.Class.method'` cannot be reached in
the dotted form (use `'pkg.mod:Class.method'`).

* **Parameters:**
  **ref** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – The reference string; the `module:attr` form is preferred.
* **Return type:**
  [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)
* **Returns:**
  The object the reference names, after importing its module.
* **Raises:**
  * [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError) – The reference has no module part or no attribute part.
  * [**ImportError**](https://docs.python.org/3/builtins/exceptions.html#ImportError) – The module part does not import (`ModuleNotFoundError`
        when the module does not exist; a plain `ImportError` if it
        exists but fails while importing).
  * [**AttributeError**](https://docs.python.org/3/builtins/exceptions.html#AttributeError) – The attribute path does not exist on the module.

### Examples

```pycon
>>> import_object('json:dumps')
<function dumps at ...>
>>> import_object('os.path.join')
<function join at ...>
>>> import_object('no-separator')
Traceback (most recent call last):
    ...
ValueError: Invalid object reference 'no-separator'; expected 'module:attr' or 'module.path.attr'.
```

#### SEE ALSO
[`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs): build a server from such references.

### py2mcp.load_server_config(path)

Load a server config JSON of the form `{"name": str, "refs": [str, ...]}`.

Only the shape is checked here; the refs are resolved later, when the
server is built. An actionable error beats a server that starts with no
tools.

* **Parameters:**
  **path** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path)) – The JSON file to read.
* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)
* **Returns:**
  The parsed JSON object, untouched, with at least a `refs` list.
* **Raises:**
  * [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError) – The file is not valid JSON, or is not a JSON object carrying
        a `refs` list.
  * [**OSError**](https://docs.python.org/3/builtins/exceptions.html#OSError) – The file cannot be read.

### Examples

```pycon
>>> import json, tempfile
>>> from pathlib import Path
>>> path = Path(tempfile.mkdtemp()) / 'py2mcp_config.json'
>>> _ = path.write_text(json.dumps({'name': 'My Tools', 'refs': ['os.path:basename']}))
>>> load_server_config(path)
{'name': 'My Tools', 'refs': ['os.path:basename']}
>>> _ = path.write_text(json.dumps({'name': 'no refs here'}))
>>> load_server_config(path)
Traceback (most recent call last):
    ...
ValueError: py2mcp server config '...py2mcp_config.json' must be a JSON object with a "refs" list, ...
```

#### SEE ALSO
[`resolve_server_config()`](#py2mcp.resolve_server_config): merge the config with command-line refs.

### py2mcp.mapping_sink(store, \*, key=<function \_default_key>)

A sink that does `store[key(record)] = record` — any `MutableMapping`.

The default key is `"<YYYY-MM-DD>/<id>.json"`, so a `dol` file store lays
records out one per file by day, and “last 30 days” is a key-prefix scan.
The write happens on the event loop, so this is for a local store; a remote
one (S3, a database) belongs behind an `async` sink or a batching one.

```pycon
>>> store = {}
>>> sink = mapping_sink(store)
>>> sink({'id': 'abc', 'ts': '2026-10-02T10:00:00+00:00'})
>>> list(store)
['2026-10-02/abc.json']
```

* **Return type:**
  [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)], [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]

### py2mcp.markdown_install_badge(name, mcp_url, , admin=False)

Markdown link that installs an MCP server as a claude.ai connector.

The dominant use of [`claude_install_link()`](#py2mcp.claude_install_link) is pasting one into a README,
so this saves writing the same link syntax around it. Arguments are those of
[`claude_install_link()`](#py2mcp.claude_install_link).

```pycon
>>> markdown_install_badge('snout', 'https://x.io/mcp')
'[Add snout to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=snout&connectorUrl=https%3A%2F%2Fx.io%2Fmcp)'
```

Brackets and backslashes in the name are escaped in the link text, so a name
like `tools [beta]` can’t end the link text early and break the link (the
URL itself is percent-encoded already):

```pycon
>>> print(markdown_install_badge('tools [beta]', 'https://x.io/mcp').split('](')[0])
[Add tools \[beta\] to Claude
```

* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)

### py2mcp.mk_auth_provider(auth)

Build a FastMCP **resource-server** auth provider from an auth-config dict.

`auth` is `None`/falsy (no auth) or a dict with a `type` key:

`type='jwt'` (default) — validate JWTs issued by a managed IdP. Keys:

- `jwks_uri` *or* `public_key` — where to get the IdP’s signing key(s).
- `issuer` — the IdP issuer URL (the token’s `iss`).
- `audience` (**required**) — **this** server’s resource id (the token’s
  `aud`). 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 single `issuer`) — 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`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)]) – The auth-config dict described above, or `None`/`{}` for no
  authentication.
* **Return type:**
  [`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]
* **Returns:**
  A `RemoteAuthProvider` (a resource server), or `None` when `auth`
  is falsy.
* **Raises:**
  [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError) – `auth` is not a dict, its `type` is not supported, or a
      required key (`jwks_uri`/`public_key`, `base_url`,
      `audience`, `authorization_servers`/`issuer`) is missing.

### Examples

```pycon
>>> 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:

```pycon
>>> 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()`](#py2mcp.mk_http_app): where the provider is attached to a server.

### py2mcp.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)

Build a Streamable-HTTP **ASGI app** from `refs` (+ optional OAuth).

Returns the ASGI application (a Starlette app), so any ASGI server can run it:

```default
# 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`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – `'module:function'` references, one per tool.
  * **name** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Server name.
  * **auth** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)]) – Resolved by [`mk_auth_provider()`](#py2mcp.mk_auth_provider) (`None` → no auth; a remote
    connector should always set it).
  * **input_trans** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)], [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)]]) – Forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs).
  * **transport** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – The FastMCP HTTP transport; `DFLT_TRANSPORT` is
    Streamable HTTP.
  * **path** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – URL path the MCP endpoint is mounted at; `None` keeps
    FastMCP’s default (`/mcp`).
  * **stateless_http** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`bool`](https://docs.python.org/3/builtins/functions.html#bool)]) – `True` is recommended behind a load balancer (MCP
    sessions are stateful, so default in-memory sessions break across
    replicas — go stateless or externalize session state). `None`
    keeps FastMCP’s default.
  * **middleware** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – A single FastMCP middleware or a list, attached for
    cross-cutting concerns — metering, logging, rate limiting. Because
    `auth` runs first, it can read the authenticated caller via
    `fastmcp.server.dependencies.get_access_token()`.
  * **instructions** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – The server’s model-facing description (surfaced to the
    connecting client/model).
  * **prompts** ([`Union`](https://docs.python.org/3/library/typing.html#typing.Union)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable), [`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)], [`None`](https://docs.python.org/3/builtins/constants.html#None)]) – Forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs).
  * **resources** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Mapping`](https://docs.python.org/3/library/typing.html#typing.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)]]) – Forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs).
  * **icons** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs).
  * **annotations** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs) (per-tool MCP hints).
* **Return type:**
  [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)
* **Returns:**
  The Starlette ASGI application.
* **Raises:**
  [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError) – `auth` is malformed (see [`mk_auth_provider()`](#py2mcp.mk_auth_provider)) or a
      reference cannot be parsed (see [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs)).

### Examples

```pycon
>>> 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:

```pycon
>>> 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()`](#py2mcp.serve_http): build and run in-process instead of returning the app.
[`py2mcp.serve.serve_stdio()`](py2mcp.serve.html.md#py2mcp.serve.serve_stdio): the local stdio counterpart.

### py2mcp.mk_input_trans(name_func_relationships=None)

Create an input transformation function from name->func mappings.

The returned callable takes a tool call’s keyword arguments as a dict and
returns a new dict in which each named argument has been passed through its
converter; arguments with no converter are copied through unchanged. Pass it
as `input_trans` to [`py2mcp.mk_mcp_server()`](#py2mcp.mk_mcp_server) and friends.

* **Parameters:**
  **name_func_relationships** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Mapping`](https://docs.python.org/3/library/typing.html#typing.Mapping)]) – Which converter applies to which argument, as
  `{name: func}`, or reversed as `{func: name}` or
  `{func: [name, ...]}` when one converter serves several arguments.
  `None` gives a transformation that only copies the dict.
* **Return type:**
  [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)], [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)]
* **Returns:**
  A function from a kwargs dict to a new kwargs dict.
* **Raises:**
  [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError) – The same argument name is mapped more than once.

### Examples

```pycon
>>> def to_int(x): return int(x)
>>> trans = mk_input_trans({'x': to_int})
>>> trans({'x': '42', 'y': 'hello'})
{'x': 42, 'y': 'hello'}
```

One converter for several arguments, written the reversed way:

```pycon
>>> trans = mk_input_trans({to_int: ['x', 'y']})
>>> trans({'x': '1', 'y': '2', 'z': '3'})
{'x': 1, 'y': 2, 'z': '3'}
```

No mapping means no conversion:

```pycon
>>> mk_input_trans()({'x': '1'})
{'x': '1'}
```

#### SEE ALSO
[`py2mcp.mk_mcp_server()`](#py2mcp.mk_mcp_server): where the returned callable is applied.

### py2mcp.mk_mcp_from_refs(refs, , name='py2mcp Server', input_trans=None, auth=None, middleware=None, instructions=None, prompts=None, resources=None, icons=None, annotations=None)

Create an MCP server from `'module:function'` reference strings.

Resolves each reference to a callable via [`py2mcp.util.import_object()`](py2mcp.util.html.md#py2mcp.util.import_object)
and delegates to [`mk_mcp_server()`](#py2mcp.mk_mcp_server). One call from config strings to a
runnable server — what tools that read tool references from a file (e.g.
`coact`’s `mcp` backend) need.

* **Parameters:**
  * **refs** ([`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – `'module.path:attr'` (or `'module.path.attr'`) strings, one per
    tool. Every module is imported when this is called.
  * **name** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Name of the MCP server.
  * **input_trans** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)], [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)]]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
  * **auth** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server); the remote/HTTP path attaches
    its OAuth provider here.
  * **middleware** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
  * **instructions** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server) as the server’s
    model-facing description.
  * **prompts** ([`Union`](https://docs.python.org/3/library/typing.html#typing.Union)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable), [`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)], [`None`](https://docs.python.org/3/builtins/constants.html#None)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
  * **resources** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Mapping`](https://docs.python.org/3/library/typing.html#typing.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)]]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
  * **icons** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
  * **annotations** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
* **Return type:**
  `FastMCP`
* **Returns:**
  A FastMCP server with one tool per reference, each named after the
  resolved function.
* **Raises:**
  [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError) – A reference has no `module` or `attr` part (see
      [`py2mcp.util.import_object()`](py2mcp.util.html.md#py2mcp.util.import_object), whose import errors propagate too).

### Examples

```pycon
>>> mcp = mk_mcp_from_refs(['os.path:basename', 'os.path:dirname'], name='Paths')
>>> mcp.name
'Paths'
```

The tools are the resolved functions:

```pycon
>>> import asyncio
>>> sorted(tool.name for tool in asyncio.run(mcp.list_tools()))
['basename', 'dirname']
>>> asyncio.run(mcp.call_tool('basename', {'p': '/a/b/c.txt'})).content[0].text
'c.txt'
```

#### SEE ALSO
[`py2mcp.serve.serve_stdio()`](py2mcp.serve.html.md#py2mcp.serve.serve_stdio): build from refs and run over stdio.
[`py2mcp.http.mk_http_app()`](py2mcp.http.html.md#py2mcp.http.mk_http_app): build from refs as an ASGI app.

### py2mcp.mk_mcp_from_store(store, , name='item', plural='', server_name=None, middleware=None, instructions=None, prompts=None, resources=None, icons=None, annotations=None)

Create an MCP server from a MutableMapping with CRUD operations.

Generates four tools over the store, `list_<plural>`, `get_<name>`,
`set_<name>` and `delete_<name>`, so any key-value store (a dict, a
`dol` store, a database wrapper) is one call away from being MCP tools.
The store is used live: a tool call reads or writes the mapping you passed.

* **Parameters:**
  * **store** ([`MutableMapping`](https://docs.python.org/3/library/typing.html#typing.MutableMapping)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any), [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – A MutableMapping to expose via MCP.
  * **name** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Singular name for items (e.g., ‘project’, ‘user’); used in the
    tool names.
  * **plural** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Plural form used by the list tool (defaults to name + ‘s’).
  * **server_name** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – Name of the MCP server (defaults to “{name} Store”).
  * **middleware** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Optional FastMCP middleware (a single middleware or an
    iterable), forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server) — wraps every generated
    CRUD tool call, e.g. to meter or audit store reads and mutations.
  * **instructions** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – Optional natural-language server description, forwarded to
    [`mk_mcp_server()`](#py2mcp.mk_mcp_server) as the server’s model-facing `instructions`.
  * **prompts** ([`Union`](https://docs.python.org/3/library/typing.html#typing.Union)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable), [`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)], [`None`](https://docs.python.org/3/builtins/constants.html#None)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
  * **resources** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Mapping`](https://docs.python.org/3/library/typing.html#typing.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)]]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
  * **icons** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server).
  * **annotations** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Forwarded to [`mk_mcp_server()`](#py2mcp.mk_mcp_server); keys are the generated
    tool names (e.g. `delete_project`).
* **Return type:**
  `FastMCP`
* **Returns:**
  A FastMCP server with the four CRUD tools.

### Examples

```pycon
>>> import asyncio
>>> projects = {'p1': {'name': 'Project 1'}, 'p2': {'name': 'Project 2'}}
>>> mcp = mk_mcp_from_store(projects, name='project')
>>> mcp.name
'project Store'
>>> sorted(tool.name for tool in asyncio.run(mcp.list_tools()))
['delete_project', 'get_project', 'list_projects', 'set_project']
>>> asyncio.run(mcp.call_tool('get_project', {'key': 'p1'})).structured_content
{'result': {'name': 'Project 1'}}
```

An irregular plural and an explicit server name:

```pycon
>>> mcp = mk_mcp_from_store({}, name='entry', plural='entries', server_name='Ledger')
>>> mcp.name
'Ledger'
>>> sorted(tool.name for tool in asyncio.run(mcp.list_tools()))
['delete_entry', 'get_entry', 'list_entries', 'set_entry']
```

#### SEE ALSO
[`py2mcp.util.store_to_funcs()`](py2mcp.util.html.md#py2mcp.util.store_to_funcs): the CRUD functions without a server.
[`mk_mcp_server()`](#py2mcp.mk_mcp_server): expose your own functions instead.

### py2mcp.mk_mcp_server(funcs, , name='py2mcp Server', input_trans=None, auth=None, middleware=None, instructions=None, prompts=None, resources=None, icons=None, annotations=None)

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. Each function becomes one
tool, named after the function, with its signature and docstring as the
tool’s schema and description.

* **Parameters:**
  * **funcs** ([`Union`](https://docs.python.org/3/library/typing.html#typing.Union)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable), [`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)]]) – A function or iterable of functions to expose as MCP tools.
  * **name** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Name of the MCP server.
  * **input_trans** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)], [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)]]) – Called with the dict of keyword arguments of every tool
    call; the dict it returns is what the function receives. Build one
    with [`py2mcp.mk_input_trans()`](#py2mcp.mk_input_trans). `None` passes arguments
    through untouched.
  * **auth** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Optional `fastmcp.server.auth` provider attached at construction —
    used by the remote (HTTP) path for OAuth 2.1 (see [`py2mcp.http`](py2mcp.http.html.md#module-py2mcp.http)).
    `None` (the default) leaves the server unauthenticated, which is
    correct for the local stdio path.
  * **middleware** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Optional FastMCP middleware (a single middleware or a list),
    attached at construction, for cross-cutting concerns that must wrap
    *every* tool call — usage metering, cost logging, audit, rate limiting.
    Preferred over decorating each tool: you can’t forget to wrap one (a
    missed paid tool means untracked cost). On the remote path `auth`
    runs first, so a middleware can read the authenticated caller via
    `fastmcp.server.dependencies.get_access_token()`.
  * **instructions** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – Optional natural-language description of the server, surfaced
    to the client/model as the server’s `instructions` — a good place to
    explain what the tools do and the intended workflow. `None` (default)
    leaves it unset.
  * **prompts** ([`Union`](https://docs.python.org/3/library/typing.html#typing.Union)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable), [`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)], [`None`](https://docs.python.org/3/builtins/constants.html#None)]) – Optional callable or iterable of callables to register as MCP
    prompts (via FastMCP’s `@mcp.prompt`), so a prompts-and-tools server
    can be built declaratively in one call instead of reaching past the
    builder to register prompts by hand on the returned server.
  * **resources** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Mapping`](https://docs.python.org/3/library/typing.html#typing.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)]]) – Optional `{uri: callable}` mapping to register as MCP
    resources (via FastMCP’s `@mcp.resource(uri)`) — the callable is
    invoked to produce the resource’s content when a client reads `uri`.
  * **icons** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Optional server icon(s), sent in the MCP `serverInfo` so a client
    (e.g. claude.ai’s connector list) can show one: an `mcp.types.Icon`,
    a dict of its fields, a bare `src` (URL or `data:` URI), or a list
    of those. `None` (default) sends none.
  * **annotations** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – Optional per-tool MCP annotations (`readOnlyHint`,
    `destructiveHint`, `idempotentHint`, `openWorldHint`), so a host
    can gate tools by them instead of treating all as destructive. Either a
    `{tool_name: annotations}` mapping (tools absent from it get none), or
    a callable `func -> annotations | None` evaluated per function (e.g. to
    derive them from your own side-effect metadata). Each value is an
    `mcp.types.ToolAnnotations` or a dict of its fields. `None` (default)
    sets none.
* **Return type:**
  `FastMCP`
* **Returns:**
  A FastMCP server instance ready to run, with one tool per function.

### Examples

```pycon
>>> def add(a: int, b: int) -> int:
...     '''Add two numbers'''
...     return a + b
>>> mcp = mk_mcp_server(add)
>>> mcp.name
'py2mcp Server'
```

Several functions, a server name, and a look at the registered tools:

```pycon
>>> import asyncio
>>> def greet(name: str) -> str:
...     return f"Hello, {name}!"
>>> mcp = mk_mcp_server([add, greet], name="Math & Greetings")
>>> mcp.name
'Math & Greetings'
>>> sorted(tool.name for tool in asyncio.run(mcp.list_tools()))
['add', 'greet']
```

Calling a tool the way an MCP client would:

```pycon
>>> result = asyncio.run(mcp.call_tool('add', {'a': 2, 'b': 3}))
>>> result.structured_content
{'result': 5}
```

Prompts and resources, declared alongside the tools:

```pycon
>>> def summarize_request(topic: str) -> str:
...     return f"Summarize the latest on {topic}."
>>> def schema() -> dict:
...     return {"type": "object"}
>>> mcp = mk_mcp_server(
...     add,
...     prompts=summarize_request,
...     resources={"schema://analysis": schema},
... )
>>> sorted(p.name for p in asyncio.run(mcp.list_prompts()))
['summarize_request']
>>> [str(r.uri) for r in asyncio.run(mcp.list_resources())]
['schema://analysis']
```

Annotations, by tool name (or a callable `func -> annotations`):

```pycon
>>> mcp = mk_mcp_server([add, greet], annotations={'add': {'readOnlyHint': True}})
>>> tools = {t.name: t for t in asyncio.run(mcp.list_tools())}
>>> tools['add'].annotations.readOnlyHint, tools['greet'].annotations
(True, None)
```

#### SEE ALSO
[`mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs): the same from `'module:function'` strings.
[`mk_mcp_from_store()`](#py2mcp.mk_mcp_from_store): CRUD tools generated from a mapping.
[`py2mcp.mk_input_trans()`](#py2mcp.mk_input_trans): build an `input_trans` from per-argument converters.

### py2mcp.resolve_server_config(, config=None, refs=(), name=None)

Merge a config file and explicit `refs`/`name` into `(refs, name)`.

Refs from `--ref` are appended after any from the config file; an explicit
`name` wins over the config’s. Pure (no I/O beyond reading `config`), so
it is unit-testable without standing up a server.

* **Parameters:**
  * **config** ([`Union`](https://docs.python.org/3/library/typing.html#typing.Union)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path), [`None`](https://docs.python.org/3/builtins/constants.html#None)]) – Path of a JSON config as read by [`load_server_config()`](#py2mcp.load_server_config),
    or `None` for no config file.
  * **refs** ([`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – Extra `'module:function'` references, appended after the
    config’s.
  * **name** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – Server name; overrides the config’s `name`. When neither is
    given, `DFLT_SERVER_NAME`.
* **Return type:**
  [`tuple`](https://docs.python.org/3/builtins/stdtypes.html#tuple)[[`list`](https://docs.python.org/3/builtins/stdtypes.html#list)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)], [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]
* **Returns:**
  The merged list of references and the server name.
* **Raises:**
  [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError) – Neither the config nor `refs` supplies any reference, or
      the config file is malformed (see [`load_server_config()`](#py2mcp.load_server_config)).

### Examples

```pycon
>>> resolve_server_config(refs=['os.path:basename'], name='Paths')
(['os.path:basename'], 'Paths')
```

With a config file, its refs come first and its name is the fallback:

```pycon
>>> import json, tempfile
>>> from pathlib import Path
>>> path = Path(tempfile.mkdtemp()) / 'py2mcp_config.json'
>>> _ = path.write_text(json.dumps({'name': 'My Tools', 'refs': ['os.path:basename']}))
>>> resolve_server_config(config=path, refs=['os.path:dirname'])
(['os.path:basename', 'os.path:dirname'], 'My Tools')
>>> resolve_server_config(config=path, name='Override')
(['os.path:basename'], 'Override')
```

### py2mcp.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)

Build and **run** a Streamable-HTTP MCP server (blocking) via FastMCP/uvicorn.

For a self-hosted process. Binds `127.0.0.1` by 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). `auth` is resolved by [`mk_auth_provider()`](#py2mcp.mk_auth_provider);
`middleware` (a single FastMCP middleware or a list) is attached as in
[`mk_http_app()`](#py2mcp.mk_http_app); `instructions` sets the server’s model-facing description.
`prompts`/`resources` are forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs).

* **Return type:**
  [`None`](https://docs.python.org/3/builtins/constants.html#None)

### py2mcp.serve_stdio(refs, , name='py2mcp Server', input_trans=None, middleware=None, instructions=None, prompts=None, resources=None)

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()`](#py2mcp.mk_mcp_from_refs) + `FastMCP.run` so that
packaged integrations have one command to launch. No example here: the call
does not return while the server runs.

* **Parameters:**
  * **refs** ([`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – `'module:function'` references, one per tool.
  * **name** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Server name.
  * **input_trans** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)[[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)], [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)]]) – Forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs).
  * **middleware** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]) – A single FastMCP middleware or a list, forwarded for
    cross-cutting concerns; logging/metering is as useful on the local
    stdio path as on the remote one.
  * **instructions** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – The server’s model-facing description.
  * **prompts** ([`Union`](https://docs.python.org/3/library/typing.html#typing.Union)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable), [`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)], [`None`](https://docs.python.org/3/builtins/constants.html#None)]) – Forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs).
  * **resources** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Mapping`](https://docs.python.org/3/library/typing.html#typing.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)]]) – Forwarded to [`py2mcp.mk_mcp_from_refs()`](#py2mcp.mk_mcp_from_refs).
* **Return type:**
  [`None`](https://docs.python.org/3/builtins/constants.html#None)

#### SEE ALSO
[`py2mcp.http.serve_http()`](py2mcp.http.html.md#py2mcp.http.serve_http): the same over Streamable HTTP.
[`main()`](py2mcp.main.html.md#module-py2mcp.main): the command line that calls this.

### Modules

| [`base`](py2mcp.base.html.md#module-py2mcp.base)   | Private helpers shared by the builders in [`py2mcp.main`](py2mcp.main.html.md#module-py2mcp.main).   |
|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
| [`http`](py2mcp.http.html.md#module-py2mcp.http)   | Serve a py2mcp `FastMCP` server over Streamable HTTP with optional OAuth 2.1.                                                |
| [`main`](py2mcp.main.html.md#module-py2mcp.main)   | Build a `FastMCP` server from Python functions, reference strings, or a store.                                               |
| [`serve`](py2mcp.serve.html.md#module-py2mcp.serve) | Serve a py2mcp `FastMCP` server over stdio, as a packaged integration launches it.                                           |
| [`trans`](py2mcp.trans.html.md#module-py2mcp.trans) | Input transformation for py2mcp tools: convert arguments before a function runs.                                             |
| [`usage`](py2mcp.usage.html.md#module-py2mcp.usage) | Per-call usage logging for a py2mcp / FastMCP server: who called which tool, with what, and what came back.                  |
| [`util`](py2mcp.util.html.md#module-py2mcp.util)   | Resolve object references and turn a mapping into CRUD functions.                                                            |
