Repository navigation
Feature: MCP Gateway — auto-expose PyNest services as Model Context Protocol tools #120
Description
Overview
This is PyNest's biggest opportunity to become the leading Python backend framework for agentic systems.
The Model Context Protocol (MCP) is an open standard (by Anthropic) that defines how AI agents (Claude, GPT, Cursor, Copilot, etc.) discover and invoke tools and resources from backend servers. By adding a McpModule to PyNest, any application built with this framework can expose its services as MCP tools with zero extra code — just decorators.
Motivation
Today, making a PyNest service callable by an AI agent requires:
- Writing a separate MCP server (Python SDK)
- Manually defining tool schemas (name, description, input JSON schema)
- Wiring up routing between MCP requests and service methods
- Keeping the tool definitions in sync with the service implementation
With McpModule, all of that disappears:
```python
Before: manual MCP server + PyNest app = two codebases
After: one PyNest app, automatically MCP-compatible
@Injectable
class WeatherService:
@mcptool(
description="Get the current weather forecast for a city",
schema=GetWeatherInput,
)
async def get_weather(self, city: str, units: str = "celsius") -> WeatherReport:
return await self.api.fetch(city, units)
```
This makes PyNest the first Python web framework with native AI agent integration.
Proposed API
McpModule.for_root()
```python
@module(
imports=[
McpModule.for_root(
name="my-api",
version="1.0.0",
transport="stdio", # or "sse", "http"
path="/mcp", # HTTP/SSE mount path
instructions="This server exposes weather and calendar tools.",
)
],
providers=[WeatherService, CalendarService],
...
)
class AppModule:
pass
```
@McpTool() — expose a service method as an MCP tool
```python
from nest.mcp import McpTool
@Injectable
class UserService:
@McpTool(
name="get_user", # defaults to method name
description="Retrieve a user by their ID",
schema=GetUserInput, # Pydantic model → JSON Schema
)
async def get_user(self, user_id: str) -> UserDto:
return await self.repo.find(user_id)
@McpTool(description="List all users with optional filter")
async def list_users(self, filter: UserFilterInput | None = None) -> list[UserDto]:
return await self.repo.find_all(filter)
@McpTool(description="Create a new user account")
async def create_user(self, data: CreateUserInput) -> UserDto:
return await self.repo.create(data)
```
Tool schema auto-generation: If the method uses Pydantic models as arguments, @McpTool auto-generates the JSON Schema from those type annotations — no schema= required.
@McpResource() — expose data as an MCP resource
```python
from nest.mcp import McpResource
@Injectable
class DocumentService:
@McpResource(
uri_template="docs://{doc_id}",
name="document",
description="Fetch a document by ID",
mime_type="text/plain",
)
async def get_document(self, doc_id: str) -> str:
return await self.storage.read(doc_id)
@McpResource(uri="docs://index", description="Index of all documents")
async def list_documents(self) -> list[str]:
return await self.storage.list()
```
@McpPrompt() — expose prompt templates
```python
from nest.mcp import McpPrompt
@Injectable
class PromptService:
@McpPrompt(
name="summarize",
description="Summarize a document using a given style",
)
async def summarize_prompt(self, document: str, style: str = "brief") -> list[McpMessage]:
return [
McpMessage(role="user", content=f"Summarize in {style} style:\n\n{document}")
]
```
Transport Support
| Transport | Use Case |
|---|---|
stdio |
Claude Desktop, local CLI agents |
sse (Server-Sent Events) |
Remote agents, web clients — mounted at /mcp by default |
http (Streamable HTTP) |
Production deployments, MCP 2025-03-26 spec |
The transport is configured once in McpModule.for_root(). The PyNest app runs both an HTTP REST server (FastAPI) and the MCP server concurrently on the same process.
Auto-discovery
When McpModule is imported, it scans all providers in the module graph for @McpTool, @McpResource, and @McpPrompt decorators at startup (via OnApplicationBootstrap) and registers them automatically. No manual registration needed.
OpenAPI ↔ MCP sync
For controllers that already have @Get / @Post routes with Pydantic response models, McpModule should offer an optional expose_routes=True flag that auto-generates MCP tool definitions from the existing OpenAPI schema:
```python
McpModule.for_root(
expose_routes=True, # auto-convert OpenAPI routes to MCP tools
route_prefix="/api", # only expose routes under this prefix
)
```
Full Example — Agentic API
```python
main.py
from nest.core import PyNestFactory
from nest.mcp import McpModule
app_module = AppModule # includes McpModule.for_root(transport="sse")
nest_app = await PyNestFactory.create(app_module)
http_server = nest_app.get_http_server()
Both REST + MCP available:
POST /users → REST
GET /mcp → MCP SSE stream (for Claude/agents)
GET /mcp/tools → list available tools (debugging)
uvicorn.run(http_server, host="0.0.0.0", port=3000)
```
Claude Desktop config (zero code on user side):
```json
{
"mcpServers": {
"my-api": {
"url": "http://localhost:3000/mcp"
}
}
}
```
Acceptance Criteria
-
McpModuleinnest/mcp/withfor_root(name, version, transport, path, instructions)factory -
@McpTool(name?, description, schema?)decorator — auto-generates JSON Schema from Pydantic type hints -
@McpResource(uri_template?, uri?, name, description, mime_type?)decorator -
@McpPrompt(name, description)decorator returninglist[McpMessage] - Auto-discovery of decorated methods at startup via provider scan
-
stdiotransport support (for local agents and Claude Desktop) -
ssetransport support (Server-Sent Events, mounted at configurable path) -
http(Streamable HTTP) transport support per MCP 2025-03-26 spec - Both REST and MCP transports run on the same FastAPI instance
-
expose_routes=Trueauto-converts OpenAPI routes to MCP tools -
mcpoptional dependency (pip install pynest-api[mcp]) -
pynest generate mcp-serverCLI scaffold command - Unit tests for tool schema generation from Pydantic models
- Integration test: MCP client calls a tool and receives the correct response
- Documentation page with Claude Desktop and programmatic client examples
Dependencies
mcpPython SDK (pip install mcp) — Anthropic's official MCP library- Feature Add official docs #2 (Lifecycle Hooks) — bootstrap discovery runs in
OnApplicationBootstrap - Feature silent publish package to test pypi #3 (ConfigModule) — MCP config (transport, path) sourced from
ConfigService
Why This Is the Differentiator
No other Python web framework has native MCP support. Adding this makes PyNest the natural choice for any developer building backend services that need to be consumed by AI agents — which is fast becoming the dominant use case for APIs. It turns every PyNest app into an agent-ready service with a single module import.
Related
- MCP specification: https://modelcontextprotocol.io/specification
- MCP Python SDK: https://github.com/modelcontextprotocol/python-sdk
- Issue Do we have any official documentation about Websockets? #104: Websocket documentation (MCP over WS is a future transport option)
Activity