pydantic-mcp-demo

pydantic-mcp-demo

Serve a pydantic-ai toolset as an MCP server, enabling reuse of tools across agents and MCP surfaces.

Category
访问服务器

README

pydantic-mcp-demo

Serve a pydantic-ai toolset as an MCP server. One definition, two surfaces.

pip install git+https://github.com/thejens/pydantic-mcp-demo.git

What it looks like

Minimal — decorator style

Instantiate MCPServer, decorate tools with @server.tool(), call .run():

from dataclasses import dataclass
from pydantic_ai import RunContext
from pydantic_ai_mcp import MCPServer

@dataclass
class Deps:
    api_key: str

server = MCPServer(deps=Deps(api_key="sk-…"), name="demo")

@server.tool()
async def whoami(ctx: RunContext[Deps]) -> str:
    """Return the current user."""
    return f"Authenticated as {ctx.deps.api_key[:4]}…"

server.run(transport="stdio")

One toolset → agent and MCP server

The whole point: define tools once, use them everywhere:

from pydantic_ai import Agent
from pydantic_ai.toolsets import FunctionToolset
from pydantic_ai_mcp import MCPServer

toolset: FunctionToolset[Deps] = FunctionToolset(id="demo")

@toolset.tool()
async def whoami(ctx: RunContext[Deps]) -> str: ...

# Agent path — unchanged
agent = Agent(model="openai:gpt-4o", toolsets=[toolset])

# MCP path — same toolset, deps called fresh per invocation
server = MCPServer(toolsets=[toolset], deps=make_deps)
server.run(transport="stdio")

Session state

Persist data across MCP calls without touching FastMCP APIs. Make Deps a Pydantic model: plain fields are saved to the session store; Field(exclude=True) fields are ephemeral and rebuilt each call:

from pydantic import BaseModel, ConfigDict, Field
from pydantic_ai_mcp import MCPServer

class Deps(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)
    # persisted across calls ──────────────────────────────────
    user_id: str | None = None
    notes: dict[str, str] = Field(default_factory=dict)
    # rebuilt each call (not stored) ──────────────────────────
    http: httpx.AsyncClient | None = Field(default=None, exclude=True)

async def make_deps(deps: Deps) -> Deps:
    if deps.user_id is None:
        deps.user_id = await auth()        # runs once per session, then cached
    deps.http = httpx.AsyncClient()        # fresh every call
    return deps

server = MCPServer(
    deps=make_deps,
    session_deps=Deps,                     # enables load-before / save-after
    name="my-server",
)

@server.tool()
def remember(ctx: RunContext[Deps], key: str, value: str) -> str:
    ctx.deps.notes[key] = value            # mutate — auto-saved after this returns
    return f"Stored {key!r}"

server.run(transport="stdio")

For distributed deployments, swap the in-memory store for Redis with one extra parameter:

from key_value.aio.stores.redis import RedisStore

server = MCPServer(
    deps=make_deps,
    session_deps=Deps,
    session_state_store=RedisStore(url=os.environ["REDIS_URL"]),
)
server.run(transport="streamable-http", host="0.0.0.0", port=8000)

Mount on FastAPI

MCPServer is a FastMCP subclass, so http_app() is already available:

from fastapi import FastAPI
from pydantic_ai_mcp import MCPServer

server = MCPServer(deps=make_deps, name="my-server")

@server.tool()
async def my_tool(ctx: RunContext[Deps]) -> str: ...

app = FastAPI()
app.mount("/mcp", server.http_app())

Installation

pip install git+https://github.com/thejens/pydantic-mcp-demo.git
# or
uv add git+https://github.com/thejens/pydantic-mcp-demo.git

Requires Python 3.12+ and pydantic-ai ≥ 1.107.


Usage

Tools

Register tools with @server.tool() (pydantic-ai RunContext convention), or pass pre-built FunctionToolset / AbstractToolset instances via toolsets=[…].

Prompts

Prompt functions follow the same convention — RunContext first, then named arguments. Register them with @server.prompt or pass via prompts=[…] at construction:

@server.prompt
async def expert_prompt(ctx: RunContext[Deps], domain: str) -> str:
    """System prompt that makes the model a domain expert."""
    return f"You are an expert {domain} engineer."

The function name becomes the prompt name; the docstring its description; remaining parameters become MCP prompt arguments.

Deps factory

For real applications, deps are created fresh per call. Pass any callable:

def make_deps() -> Deps:
    return Deps(api_key=os.environ["API_KEY"])

async def make_deps_async() -> Deps:
    secret = await vault.get("api_key")
    return Deps(api_key=secret)

server = MCPServer(deps=make_deps, ...)

Session deps

Make Deps a Pydantic model and pass it as session_deps. Before each tool call the serialisable fields are loaded from the store and the instance is passed to the factory. After the call the (possibly mutated) instance is saved back.

  • Plain fields — JSON-serialisable types, persisted to the session store
  • Field(exclude=True) fields — any type; omitted by model_dump() and restored to their default before the factory fills them in

All fields must have defaults; the model is instantiated with no arguments for new sessions.

This mirrors how pydantic-ai shares a mutable deps instance across all tool calls within a single agent run. session_deps extends that contract across MCP round-trips — the session is the unit of continuity instead of the run. The same toolset works unmodified with a plain pydantic-ai Agent (pass Deps() as deps).

Distributed sessions with Redis

By default session state is kept in memory. Pass session_state_store to swap the backend:

from key_value.aio.stores.redis import RedisStore

server = MCPServer(
    deps=make_deps,
    session_deps=Deps,
    session_state_store=RedisStore(url=os.environ["REDIS_URL"]),
)

RedisStore is from py-key-value-aio[redis]. Any backend implementing AsyncKeyValue works — DynamoDB, Firestore, PostgreSQL, Valkey, and more.

Multiple toolsets

server = MCPServer(
    toolsets=[search_toolset, files_toolset, metrics_toolset],
    deps=make_deps,
    prompts=[analyst_prompt, researcher_prompt],
    name="full-suite",
    instructions="Company intelligence tools.",
)
server.run(transport="stdio")

API

MCPServer extends FastMCP — all FastMCP methods (run(), run_async(), http_app(), add_middleware(), etc.) are inherited.

Constructor

MCPServer(
    *,
    toolsets: Sequence[AbstractToolset[DepsT]] = (),
    deps: DepsT | Callable[[], DepsT] | Callable[[DepsT], DepsT] = None,
    session_deps: type[DepsT] | None = None,
    prompts: Sequence[Callable[..., Any]] | None = None,
    name: str = "pydantic-ai-mcp",
    bootstrap_deps: Any = None,
    **fastmcp_kwargs,
)
Parameter Description
toolsets AbstractToolset instances — same as Agent(toolsets=[...])
deps Deps instance, () -> DepsT factory, or (state: DepsT) -> DepsT session factory
session_deps Pydantic BaseModel class — loaded from the session store before each call, saved back after
prompts (ctx: RunContext[DepsT], **kwargs) -> str | list[Message] | PromptResult
name Server name shown to MCP clients
bootstrap_deps Deps used only during startup tool discovery (safe as None for FunctionToolset)
**fastmcp_kwargs Forwarded to FastMCP.__init__() — e.g. instructions, session_state_store, lifespan

Decorators

Decorator Description
@server.tool() Register a pydantic-ai style tool (RunContext as first arg)
@server.prompt Register a pydantic-ai style prompt function

Inherited from FastMCP

Method Description
server.run(transport="stdio", ...) Start server (sync, blocks until exit)
server.run_async(transport="stdio", ...) Start server (async)
server.http_app(path=None, transport="http", ...) Return ASGI/Starlette app for mounting on FastAPI

Examples

File What it shows
examples/01_simple_tools.py Minimal — @server.tool() decorators with fixed deps
examples/02_deps_factory.py Per-call deps factory, env config, @server.prompt
examples/03_reuse_across_agent_and_mcp.py Core case — one toolset in both an Agent and an MCP server
examples/04_session_deps.py Session state — auth cached once, notes persisted; tools just mutate ctx.deps
examples/05_redis_session_store.py Redis backing — same code as 04, one extra parameter

How it works

MCPServer subclasses FastMCP. During ASGI lifespan startup it calls get_tools() on each registered toolset, then wraps each discovered tool in a thin FastMCP.Tool subclass that injects pydantic-ai deps and runs the tool through pydantic-ai's normal call path. On each MCP call:

  1. If session_deps is set: deserialise stored state → construct Deps → pass to factory
  2. Validate and coerce MCP args through pydantic-ai's schema validator
  3. Build a RunContext and call toolset.call_tool() — the same path the agent run loop uses
  4. If session_deps is set: serialise the (possibly mutated) Deps instance back to the store

Because state is passed by reference into the factory and into RunContext.deps, any mutations a tool makes to ctx.deps fields are automatically captured in step 4.


Status

Prototype exploring the pydantic-ai ↔ MCP bridge before potential upstreaming. The API may change as the session/deps contract evolves.

Feedback and issues welcome.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选