> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpatter.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP — Model Context Protocol

> Plug a Patter agent into MCP servers (Google Workspace, PayPal, Postgres, GitHub, …) without writing wrapper handlers. Tools are discovered at call start and dispatched through the MCP client transparently.

# MCP (Model Context Protocol)

The Model Context Protocol is an open spec that lets servers expose tools, resources, and prompts to any compatible client. Patter speaks MCP as a client: point your agent at one or more MCP server URLs and the SDK queries each server's `tools/list` at call start, wraps the discovered tools with synthetic handlers that dispatch back through `tools/call`, and merges them into `agent.tools` before the model sees the function-tool list.

The result: any MCP-exposed capability — Google Workspace, PayPal, GitHub, Postgres, internal services — becomes a tool your phone agent can call, with zero wrapper code.

## When to use this

| Scenario                                                                                    | Use this                                                                                   |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| You already run an MCP server (or the vendor does) and want the phone agent to use it.      | `mcp_servers=[...]`                                                                        |
| You want to expose a single bespoke action to the agent and you control the implementation. | A regular `tool(...)` with a `handler` or `webhook_url` (see [Tools](/python-sdk/tools)).  |
| You need both.                                                                              | Mix freely — MCP tools and user-defined tools coexist; a name collision raises at startup. |

## Installation

MCP support is an **optional dependency**. If you do not configure `mcp_servers`, you never pay the install cost.

```bash theme={null}
pip install "getpatter[mcp]"
```

Under the hood this pulls in the official [`mcp`](https://pypi.org/project/mcp/) Python SDK. Configuring `mcp_servers` without the extra installed raises a clear error at call start.

## Quickstart — URL string (shorthand)

The simplest form: pass a list of URL strings. Each is treated as `{"url": <str>, "transport": "streamable-http"}` with no auth headers.

```python theme={null}
import asyncio
from getpatter import Patter, Twilio, OpenAIRealtime

phone = Patter(
    carrier=Twilio(),
    phone_number="+15550001234",
    webhook_url="api.example.com",
)

agent = phone.agent(
    engine=OpenAIRealtime(),
    system_prompt=(
        "You are a database assistant. When the caller asks about an order, "
        "use the Postgres MCP server to query the orders table."
    ),
    mcp_servers=[
        "https://mcp.example-postgres.com/mcp",
    ],
)

async def main():
    await phone.serve(agent, port=8000)

asyncio.run(main())
```

At call start you'll see a log line:

```
MCP server 'mcp[0]' registered 7 tool(s)
MCP: merged 7 tool(s) into agent
```

## Full options form — auth headers + telemetry name

When you need authentication or want a friendly name in logs, use the dict form. Headers are attached to every transport request — typically a bearer token.

<CodeGroup>
  ```python Python theme={null}
  agent = phone.agent(
      engine=OpenAIRealtime(),
      system_prompt="You are a workspace assistant. Use the available tools to help the caller.",
      mcp_servers=[
          {
              "url": "https://mcp.googleworkspace.example/mcp",
              "headers": {"Authorization": f"Bearer {os.environ['GWS_MCP_TOKEN']}"},
              "name": "google-workspace",
          },
          {
              "url": "https://mcp.paypal.example/mcp",
              "headers": {"Authorization": f"Bearer {os.environ['PAYPAL_MCP_TOKEN']}"},
              "name": "paypal",
          },
          # Mix shorthand and full form freely:
          "https://mcp.public-weather.example/mcp",
      ],
  )
  ```

  ```typescript TypeScript theme={null}
  const agent = phone.agent({
    engine: new OpenAIRealtime(),
    systemPrompt: "You are a workspace assistant. Use the available tools to help the caller.",
    mcpServers: [
      {
        url: "https://mcp.googleworkspace.example/mcp",
        headers: { Authorization: `Bearer ${process.env.GWS_MCP_TOKEN}` },
        name: "google-workspace",
      },
      {
        url: "https://mcp.paypal.example/mcp",
        headers: { Authorization: `Bearer ${process.env.PAYPAL_MCP_TOKEN}` },
        name: "paypal",
      },
      "https://mcp.public-weather.example/mcp",
    ],
  });
  ```
</CodeGroup>

## Configuration reference

| Field     | Type             | Required | Description                                                                                         |
| --------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------- |
| `url`     | `str`            | Yes      | The MCP server's streamable-HTTP endpoint.                                                          |
| `headers` | `dict[str, str]` | —        | Headers attached to every transport request. Use for `Authorization` and similar. Defaults to `{}`. |
| `name`    | `str`            | —        | Logical name for log lines and telemetry. Defaults to `mcp[<index>]`.                               |

A bare string is shorthand for `{"url": <string>}`.

## Hosted MCP example - Resemble Detect and Signal

Use this pattern when a caller provides a recording or media URL that should be
checked for synthetic speech, or when caller-provided text should be scored for
fraud and scam intent before the agent makes a trust decision. Resemble hosts an
MCP server, so the agent can discover Detect, Signal, Watermark, and
Intelligence tools without a custom wrapper.

```python theme={null}
import asyncio
import os

from getpatter import OpenAIRealtime, Patter, Twilio

phone = Patter(
    carrier=Twilio(),
    phone_number="+15550001234",
    webhook_url="api.example.com",
)

agent = phone.agent(
    engine=OpenAIRealtime(),
    system_prompt=(
        "You verify suspicious caller-provided recordings with Resemble Detect "
        "and suspicious requests with Resemble Signal. When a media URL is "
        "available, call the Detect tool. When a caller asks for a reset code, "
        "wire transfer, credential, or account change, call the Signal tool "
        "before deciding whether to continue or escalate."
    ),
    mcp_servers=[
        {
            "url": "https://mcp.resemble.ai/mcp",
            "headers": {
                "Authorization": f"Bearer {os.environ['RESEMBLE_API_KEY']}",
            },
            "name": "resemble-trust",
        },
    ],
)

async def main():
    await phone.serve(agent, port=8000)

asyncio.run(main())
```

This is a tool-call integration for caller-provided media URLs. It does not
inspect live caller audio or replace application-specific escalation policy.

## Real-world example — Postgres MCP server

Connect a phone agent to a Postgres MCP server so the caller can query an orders table by phone number.

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  import os

  from getpatter import (
      Patter, Twilio, DeepgramSTT, AnthropicLLM, ElevenLabsTTS,
  )

  phone = Patter(
      carrier=Twilio(),
      phone_number=os.environ["PHONE_NUMBER"],
      webhook_url=os.environ["WEBHOOK_URL"],
  )

  agent = phone.agent(
      stt=DeepgramSTT(),
      llm=AnthropicLLM(),
      tts=ElevenLabsTTS(voice_id="rachel"),
      system_prompt=(
          "You are a customer-service agent for an e-commerce store. "
          "When asked about an order, use the Postgres tools to query the "
          "orders table by order ID. Always confirm details with the caller "
          "before performing any write."
      ),
      first_message="Hi! What can I help you with today?",
      mcp_servers=[
          {
              "url": "https://mcp.internal.example/postgres/mcp",
              "headers": {"Authorization": f"Bearer {os.environ['PG_MCP_TOKEN']}"},
              "name": "orders-db",
          },
      ],
  )

  async def main():
      await phone.serve(agent, port=8000)

  asyncio.run(main())
  ```

  ```typescript TypeScript theme={null}
  import {
    Patter, Twilio, DeepgramSTT, AnthropicLLM, ElevenLabsTTS,
  } from "getpatter";

  const phone = new Patter({
    carrier: new Twilio(),
    phoneNumber: process.env.PHONE_NUMBER!,
    webhookUrl: process.env.WEBHOOK_URL!,
  });

  const agent = phone.agent({
    stt: new DeepgramSTT(),
    llm: new AnthropicLLM(),
    tts: new ElevenLabsTTS({ voiceId: "rachel" }),
    systemPrompt:
      "You are a customer-service agent for an e-commerce store. " +
      "When asked about an order, use the Postgres tools to query the " +
      "orders table by order ID. Always confirm details with the caller " +
      "before performing any write.",
    firstMessage: "Hi! What can I help you with today?",
    mcpServers: [
      {
        url: "https://mcp.internal.example/postgres/mcp",
        headers: { Authorization: `Bearer ${process.env.PG_MCP_TOKEN}` },
        name: "orders-db",
      },
    ],
  });

  await phone.serve(agent, { port: 8000 });
  ```
</CodeGroup>

## How it works

1. **Agent build time.** `mcp_servers` is stored on the `Agent` immutably. No connections are opened.
2. **Call start.** The stream handler instantiates an `MCPManager`, opens one streamable-HTTP connection per server, and calls `tools/list` on each. Discovered tools are wrapped with a synthetic handler that, when invoked by the model, dispatches `tools/call` back over the same connection.
3. **Conflict check.** `MCPManager.assert_no_conflicts(...)` (Py) / `MCPManager.assertNoConflicts(...)` (TS) compares MCP-discovered tool names against your `agent.tools`. A duplicate raises before the call connects.
4. **During the call.** MCP tools are indistinguishable from local handler tools — `ToolExecutor` invokes the synthetic handler, which round-trips through MCP and returns the result text to the model.
5. **Call end.** Every MCP connection is closed. Cleanup is best-effort and never blocks call teardown.

## Tool-name collisions

If an MCP-discovered tool has the same name as a tool you defined in `agent.tools`, the SDK refuses to start the call:

```
ValueError: MCP tool 'send_email' collides with a user-supplied tool of the
same name. Rename one of them or remove the duplicate from agent.tools.
```

Patter does **not** silently shadow either side — that would be a footgun. Rename one or drop the duplicate.

## Failure handling

A dead MCP server should never kill the call:

* **Connect failure** (server unreachable, TLS error, bad auth): logged at `error`, the server is skipped, the call proceeds with the remaining MCP servers and your local tools.
* **`tools/list` failure**: logged, server is skipped, call proceeds.
* **Per-call invocation failure**: the synthetic handler returns the same `{"error": "...", "fallback": true}` envelope that local tools use, so the model can recover gracefully and apologise to the caller.
* **Close failure on call end**: logged at `debug`, swallowed. Teardown always completes.

## Cost & latency

Each configured server adds **one HTTP handshake plus one `tools/list` round-trip at call start** — roughly 50–200 ms × N servers added to the time-to-first-token of the call.

If your call setup latency budget is tight, configure fewer servers, or split your fleet so latency-critical agents only see the MCP servers they actually need. Process-wide caching of `tools/list` results is on the roadmap.

<Warning>
  **MVP transport limitation.** The current release supports **streamable-HTTP** transport only. The legacy SSE transport and `stdio` (subprocess) transport are not exposed yet. If your MCP server only speaks `stdio`, run it behind a streamable-HTTP shim, or wait for a future release.
</Warning>

## Compared to writing a tool by hand

A tool that wraps an HTTP API by hand needs: a JSON schema for the parameters, a handler or webhook to call the API, error envelope shaping, and an entry in `agent.tools`. With MCP, the server already exposes a typed schema for every tool it offers, and Patter wires the dispatch automatically. You write zero per-tool code on the Patter side.

For one-off or in-process tools, a regular [`tool(...)`](/python-sdk/tools) is simpler. For sets of tools that someone else already maintains as an MCP server, MCP wins.

## What's next

<CardGroup cols={2}>
  <Card title="Tools & Function Calling" icon="wrench" href="/python-sdk/tools">
    Define your own tools with webhooks or in-process handlers.
  </Card>

  <Card title="PatterTool" icon="phone-arrow-up-right" href="/python-sdk/patter-tool">
    The inverse pattern — expose a Patter agent **as** an MCP-compatible tool.
  </Card>
</CardGroup>
