English | 中文
tRPC-MCP-Python
ttRPC-MCP-Python is the trpc-python plugin for the MCP (Model Context Protocol). It lets you expose and call MCP capabilities (Tool / Resource / Prompt) the trpc way, and plugs MCP into trpc service governance.
In one line:
trpc-mcp = official MCP SDK (FastMCP / ClientSession) + trpc-python governance (selector / filter / options) + SSE & Streamable-HTTP transports
The server declares capabilities with decorators and is hosted by the trpc server. The client calls them through McpProxy. The transport bridges to the trpc client, so the full trpc governance stack is reused, while the API stays aligned with the official MCP SDK.
Highlights
- Native trpc experience: the server declares capabilities with
@trpc_mcp.tool / @trpc_mcp.resource / @trpc_mcp.prompt; the clientMcpProxyresolves and calls a backend from config like any other trpc client. - Built on the official MCP SDK: implemented on top of the official
mcpSDK (FastMCP/ClientSession/ official transports). New protocol features are mostly inherited, so the maintenance cost stays low. - trpc service governance: the client reuses selector (IP / custom), filter, and options (protocol / namespace / timeout / dyeing).
- Multiple transports: SSE and Streamable-HTTP can be switched (stdio is not supported). Multi-process SSE can sync sessions through redis.
- All process models: single-process merge (single loop / multi-thread), separate, and peer all work. Each service process runs one MCP app and one uvicorn, and exits gracefully with the framework on SIGTERM.
- Bidirectional channel: besides client→server calls, server→client callbacks are supported (
roots/list,sampling,elicitation, and others, injected throughsession_args). - Structured output:
outputSchemaandCallToolResult.structuredContentare supported, so callers can read results by field. - Multi-service isolation: a capability can be bound to one service, or shared and visible to every service.
- Version adaptive: depends on
mcp>=1.11.0,<2and adapts to version differences (such asicons/meta/ resourceannotationsandcall_tool(meta=)). Older versions degrade automatically and do not raise.
Install and quick start
Requirements
- Python
>=3.10(3.12 recommended). Use a virtual environment to isolate dependencies. - The trpc-python SDK (import name
trpc_sdk, PyPI nametrpc-python). Windows is not supported.
Install
pip install trpc-mcp
Dependencies: trpc-python (provides trpc_sdk), mcp>=1.11.0,<2, uvicorn, and redis (required for multi-process SSE).
Quick start
1. Server: declare capabilities and start
# trpc_main.py
import trpc_mcp
from trpc_sdk.server import create_server
@trpc_mcp.tool() # omit mcp_name -> visible to every service
def add(ctx, a: int, b: int) -> int:
"""Add two integers."""
return a + b
@trpc_mcp.resource("echo://static", mcp_name="trpc.test.mcp.Greeter1")
def echo_resource(ctx) -> str: # bound to one service
return "this is static resource!"
@trpc_mcp.prompt("echo")
def echo_prompt(ctx, prompt: str) -> str:
return f"this is {prompt}"
def serve(conf_path: str):
svr = create_server(conf_path)
trpc_mcp.register_trpc_mcp_server(svr, None) # attach registered MCP capabilities to the trpc server
svr.serve()
Start:
python trpc_main.py --conf trpc_python.yaml
2. Client: call with McpProxy
import trpc_mcp
from trpc_sdk.abc import TrpcContext
from trpc_sdk.client import Options
async def call():
proxy = trpc_mcp.McpProxy(
name="trpc.test.mcp.Greeter1", # resolve from config
options=[Options().set_protocol("http_mcp").set_namespace("Development")],
)
try:
await proxy.initialize(TrpcContext())
tools = await proxy.list_tools(TrpcContext())
result = await proxy.execute_tool(TrpcContext(), "add", {"a": 1, "b": 2})
print(result.structuredContent, result.content)
finally:
await proxy.cleanup()
Runnable examples, config notes, and one-shot scripts for each feature (tool / prompt / resource / structured output / icons·meta / roots / custom selector) are in
examples/README.md.
Design
The client reuses the trpc client governance path and bridges the transport to the official MCP SDK. The server is a trpc adapter that subclasses the official FastMCP.
flowchart TD
subgraph Client
A[TrpcContext + options] --> B[McpProxy]
B --> C[official ClientSession + sse_client / streamable_http_client]
C --> D[httpx bridge TrpcHttpxTransport]
D --> E[trpc http client: selector / filter / options / codec]
end
E -->|HTTP / SSE| F[one uvicorn thread per service process: start_mcp]
subgraph Server
F --> G[TrpcMcpServer subclasses FastMCP]
G --> H[Tools / Resources / Prompts]
end
Key points:
- Client:
McpProxydrives the officialClientSessionand official transports, but every HTTP round trip goes through a customhttpxtransport into the trpc client. Each request therefore runs trpc node selection, filter, options, and codec. - Server:
TrpcMcpServersubclasses the officialFastMCP.import trpc_mcpregisters the protocol starter withtrpc_sdk.transport.register_protocol_handle("http_mcp", start_mcp). The SDK calls it in each service process and starts one uvicorn thread that hosts everyhttp_mcpport of that process. Requests pass the trpc server filter chain before entering MCP. - Session isolation: crash isolation for a single Streamable-HTTP session is provided by the official SDK (
mcp>=1.11.0). trpc-mcp reuses it directly.
Usage
Declaring server capabilities
@trpc_mcp.tool(name=, title=, description=, structured_output=, annotations=, icons=, meta=, mcp_name=)@trpc_mcp.resource(uri, name=, title=, description=, mime_type=, icons=, annotations=, meta=, mcp_name=)@trpc_mcp.prompt(name=, title=, description=, icons=, mcp_name=)
mcp_name selects which service a capability is bound to. Leave it unset for a shared capability (visible to every service). The first function argument is always ctx (TrpcContext). Use trpc_mcp.get_http_req(ctx), get_server_addr(ctx), get_session_func(ctx), and similar helpers to read the context.
Client McpProxy
Common methods: list_tools / execute_tool / list_resources / list_resource_templates / read_resource / list_prompts / get_prompts.
- host / base_url / transport / target can be resolved from config, or set explicitly in the constructor or
options(priority: constructor arguments > options > config). optionsis a list oftrpc_sdk.client.Options, applied in order, with later entries overriding earlier ones.McpProxyappendsset_protocol("http_mcp")at the end, and adds a default timeout when no options are passed.- Custom HTTP headers:
set_http_header_to_context(ctx, {...})injects them into every HTTP request on that connection. - Official
ClientSessioncallbacks (server→client):initialize(ctx, session_args={"list_roots_callback": ..., "sampling_callback": ..., "elicitation_callback": ...}).
Configuration
MCP settings live under plugins.mcp. For example, transport and base_url:
plugins:
mcp:
server:
transport: sse # sse / streamable-http
base_url: /test
settings:
message_path: /messages/
client:
transport: sse # default transport when McpProxy does not set one
base_url: /test
Multi-process SSE uses redis to sync sessions (otherwise a request routed to a different process invalidates the session):
plugins:
mcp:
server:
redis:
host: 127.0.0.1
port: 6379
db: 0
Process and thread model
The process model comes from global.processmodel in the framework YAML. In every model, each service process has exactly one MCP app and one uvicorn: uvicorn runs on its own daemon thread and binds every http_mcp port owned by that process with SO_REUSEPORT.
| Model | YAML | Where MCP / uvicorn runs |
|---|---|---|
| merge, single loop (default) | processmodel omitted |
1 in the main process |
| merge, multi-thread | mode: single_process, io_num / handle_num > 1 |
1 in the main process (started by serving thread 0; other threads do not start another) |
| separate | mode: multi_process, io_handle_type: separate, handle_num: N |
1 per rpc task process; the I/O process does not listen on http_mcp ports |
| peer | mode: multi_process, io_handle_type: peer (or merge), handle_num: N |
1 per peer worker process |
global:
processmodel:
mode: multi_process
io_handle_type: separate
handle_num: 2 # 0 means the CPU count
Automatic process-count adjustment: under separate / peer, when the process count is greater than 1, register_trpc_mcp_server sets the process count to 1 before fork and logs a warning if plugins.mcp.server.redis is missing or settings.stateless_http is false. An MCP session is in-process state, so multiple processes require both “redis for SSE session sync” and “stateless HTTP”. Merge mode is unaffected.
Graceful shutdown: after SIGTERM, the framework exit_callbacks stop uvicorn from accepting new connections and start graceful shutdown (timeout is the framework drain time). post_drain_callbacks then wait for the uvicorn thread to finish (drain time + 2 seconds; a timeout logs a warning).
Limits:
- The uvicorn event loop sits outside the SDK thread model: MCP requests run server filters and capability functions on the uvicorn thread. They do not occupy framework handle threads and are not covered by its queueing or overload protection.
- When an MCP capability function calls downstream with a trpc client, that client connection belongs to the uvicorn thread's loop. Do not reuse an HTTP client session created on another loop.
Notes
- Protocol: both server and client use
http_mcpforprotocol. A custom selector must also returnnode.protocolashttp_mcp, or the codec is downgraded. - base_url: client and server must match, or path joining returns 404.
- SSE: a single process is enough. Multiple processes (separate / peer) must configure redis and
stateless_http: true, otherwise the process count is reduced to 1 (see “Process and thread model” above). - Streamable-HTTP:
- Stateful (
stateless_http: false): single process only. redis is not supported yet. - Stateless (
stateless_http: true): multiple processes are supported, butlist_rootsis not.
- Stateful (
list_roots: supported in every SSE setup. Streamable-HTTP requiresstateless_http: falseand a single process.- stdio is not supported (configuring it raises immediately).
Layout
trpc_mcp/tools.py/resources.py/prompts.py: capability decorators and registries.trpc_mcp/_trpc_mcp_server.py:TrpcMcpServer(subclassesFastMCP), including service isolation, tool filter, andcall_tool.trpc_mcp/_trpc_server_transport.py: thehttp_mcpprotocol starterstart_mcp. Per process it builds the MCP app, starts the uvicorn thread, and registers graceful-shutdown callbacks.trpc_mcp/service_desc.py:register_trpc_mcp_server, which adjusts the process count for the process model.trpc_mcp/_sse_transport.py: SSE transport (including multi-process redis sync).trpc_mcp/mcp_client.py: clientMcpProxy.trpc_mcp/_httpx_transport.py: bridges official MCP SDK httpx requests to the trpc client (including streamable-transport version adaptation).trpc_mcp/_codec.py: client and server codec for thehttp_mcpprotocol.trpc_mcp/_compat.py: version-compatibility shim for optionalmcp.typessymbols (such asIcon).trpc_mcp/mcp_api.py/utils.py: context readers and helpers for injecting HTTP headers and parameters.examples/: runnable examples, seeexamples/README.md.docs/troubleshooting.md: troubleshooting notes;docs/compatibility_old_vs_new.md: compatibility comparison of the old and new implementations.
FAQ
Can the client protocol be http?
Use http_mcp everywhere. If the client or a custom selector falls back to plain http, the MCP codec is downgraded and an SSE POST 202 ACCEPTED is treated as an error. McpProxy forces http_mcp internally, but node.protocol returned by a selector still has to be http_mcp.
Why does list_roots fail on streamable-http?
list_roots is a server-initiated request back to the client and needs a stateful session. Under Streamable-HTTP it requires stateless_http: false and a single process. Stateless mode or multiple processes cannot reach the same session. SSE does not have this limit.
Why does multi-process SSE require redis?
Sessions in sse-starlette are in-process state. With multiple processes, the process that receives the POST may not be the process holding the SSE connection, so redis is required to sync messages and sessions across processes.
Will new features raise if an older mcp is installed?
No. icons / meta / resource annotations, call_tool(meta=), and similar APIs exist only in newer versions. trpc-mcp filters arguments against the installed SDK signature. Older versions ignore them and log a warning instead of raising TypeError. See docs/troubleshooting.md.
Metadata
Release files for trpc-mcp 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| trpc_mcp-1.0.0.tar.gz | 108.1 kB | Without provenance | Details for trpc_mcp-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| trpc_mcp-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for trpc_mcp-1.0.0-py3-none-any.whl |
Total release size: 164.1 kB
Release files / trpc_mcp-1.0.0.tar.gz
| Download URL | trpc_mcp-1.0.0.tar.gz |
|---|---|
| Size | 108.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eaef4c711868a5fb89fbbfb2d73bdd11041fd862710bdbae61c60191b825d746
|
|
BLAKE2b-256 checksum How to use checksums |
1378b753bd4d72306e74a912f03989a3fb5f091c5125698fca9e43c692062a8a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / trpc_mcp-1.0.0-py3-none-any.whl
| Download URL | trpc_mcp-1.0.0-py3-none-any.whl |
|---|---|
| Size | 56.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
068f8f8fcebe244bc4411959f384dec735b838b93b6dcc299bf2a91429602490
|
|
BLAKE2b-256 checksum How to use checksums |
7acaec9b562d50deba352927dbbb300ecedb296f91f5ad36772566d2c0ed40e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Security for trpc-mcp 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.