Pydantic AI MCP server: give an agent Sume's hosted tools

Connect a Pydantic AI agent to Sume's hosted MCP server with MCPToolset and an API-key header, filter the tools, and hold paid calls for approval.

5 min readSume
All posts

To give a Pydantic AI agent Sume's tools, create an MCPToolset for https://mcp.sume.com/mcp with your Sume API key in an Authorization: Bearer header, keep only the tools the agent needs with .filtered(), hold paid calls with .approval_required(), and register the toolset with Agent(toolsets=[...]).

Pydantic AI's side comes from its MCP client guide, MCPToolset reference, and Toolsets pages; Sume's side comes from OAuth and API keys, MCP tools and gates, and Jobs and results, all read on 2026-09-27. Sume has no Pydantic AI package or plugin: this is Pydantic AI's own MCP client talking to Sume's remote server. Sume's basics page says hosted MCP still works but is not part of the primary path today.

How do I connect a Pydantic AI agent to Sume's MCP server?

Install pydantic-ai-slim[mcp]. MCPToolset wraps the FastMCP client, and a URL string connects over Streamable HTTP, which Pydantic AI calls the recommended way to reach a remote MCP server; only a path ending in /sse switches to SSE. Register the toolset through the agent's toolsets argument. Pydantic AI recommends its MCP capability for most uses and MCPToolset when you manage the client yourself or need options the capability doesn't expose; this post uses MCPToolset so it can be wrapped with .filtered() and .approval_required(). The example shows the agent five Sume tools and pauses generate_video for approval unless the call is a dry run:

import os
from pydantic_ai import Agent, DeferredToolRequests
from pydantic_ai.mcp import MCPToolset

ALLOWED = {"tools_schema", "balance_get", "generate_video", "jobs_wait", "jobs_result"}

sume = (
    MCPToolset(
        "https://mcp.sume.com/mcp",
        headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
    )
    .filtered(lambda ctx, tool_def: tool_def.name in ALLOWED)
    .approval_required(
        lambda ctx, tool_def, args: tool_def.name == "generate_video" and not args.get("dry_run")
    )
)

agent = Agent(
    "openai:gpt-5.2",
    instructions="Preview generate_video with dry_run=true first. Wait with jobs_wait; never resubmit.",
    toolsets=[sume],
    output_type=[str, DeferredToolRequests],
)

Should the Sume key go in auth or headers?

Either works. For HTTP transports, MCPToolset takes auth: a bearer-token string, an httpx2.Auth, or 'oauth' for FastMCP's OAuth flow. Pydantic AI's docs say static headers like API keys can go in headers instead. Sume's MCP docs accept the key as Authorization: Bearer or as x-api-key, so send one of the two.

  • Sume keeps API-key remote MCP as the path for automation that does not speak OAuth. An API-key session sees write and paid tools, which is why the example filters.
  • headers and a custom http_client are mutually exclusive.
  • A shared MCPToolset connects as a single identity. If each of your users has their own Sume key, build the toolset per run with @agent.toolset(per_run_step=False).
  • Read the key from the environment. Sume's docs say not to paste API keys into chat and to rotate a key that appears in logs or chat history.

Which Sume tools should the agent see?

filtered() decides ahead of each step of the run which tools are available, from your function's answer for each tool definition; approval_required() then decides per call whether to pause. Sume's live tool ids use underscores, and the Sume MCP tools list groups every hosted tool by whether it reads, writes, or spends. The example's two functions work out like this:

Tool groups from Sume's MCP tools and gates; wrapper behavior from Pydantic AI's Toolsets page, read 2026-09-27.
ToolSume group`filtered()``approval_required()`
tools_schemaDiscoveryKeptNo
balance_getAccount and catalogKeptNo
generate_videoPaidKeptYes, unless dry_run
jobs_wait, jobs_resultJobs, readKeptNo
jobs_cancelJobs, writeDroppedNot reached

How do paid Sume calls wait for a person?

The function you pass to approval_required() receives the run context, the tool definition, and the validated arguments. When it returns true, the run ends with DeferredToolRequests, whose approvals list the pending calls; that is why the agent's output_type includes it. Resume with the run's message_history and DeferredToolResults(approvals={tool_call_id: True}). A False sends "The tool call was denied." back to the model.

The gate fits Sume's playbook for inspecting a tool before paying: call the paid tool with dry_run=true, confirm the estimate, balance, and queue behavior, then submit with a fresh idempotency_key, which every write and paid tool requires. max_spend_usd caps a call only when you pass it. Safe automation for AI agents that call paid APIs covers the gates.

Will a long jobs_wait call time out?

Not with the defaults. Pydantic AI's timeouts page says read_timeout bounds a single MCP request, 300 seconds by default, and that the agent's tool_timeout doesn't apply to tools from an MCP server; init_timeout covers the initial connection and handshake. Sume's jobs_wait holds one call for at most 55 seconds, or 50 when timeout_seconds is omitted, so a wait fits.

  • On wait_slice_expired, call jobs_wait again with the same ids; never resubmit the paid create.
  • A 524, 522, 523, or 525 on jobs_wait is a transport failure, never a job outcome.
  • When the server reports a tool error, Pydantic AI by default sends it back to the model as a retry prompt (tool_error_behavior='retry'). Set 'failed' to record it as a failed tool result and let the model decide what to do next.
  • MCP tool call timeouts on long-running video jobs covers batch waits and reading a whole wave of results.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume