Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Pydantic Demo

БесплатноНе проверен

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

GitHubEmbed

Описание

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

README

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.

from github.com/thejens/pydantic-mcp-demo

Установка Pydantic Demo

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/thejens/pydantic-mcp-demo

FAQ

Pydantic Demo MCP бесплатный?

Да, Pydantic Demo MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Pydantic Demo?

Нет, Pydantic Demo работает без API-ключей и переменных окружения.

Pydantic Demo — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Pydantic Demo в Claude Desktop, Claude Code или Cursor?

Открой Pydantic Demo на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Pydantic Demo with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории ai