|
| 1 | +# to_mcp_server |
| 2 | + |
| 3 | +Exposes an ADK agent as an MCP server so any MCP host (Claude Code, OpenAI |
| 4 | +Codex, an IDE, or any MCP client) can drive it as a single tool. It is the MCP |
| 5 | +counterpart of `to_a2a`. |
| 6 | + |
| 7 | +## Introduction |
| 8 | + |
| 9 | +`to_mcp_server` turns a whole ADK agent into a standard |
| 10 | +[Model Context Protocol](https://modelcontextprotocol.io/) server. The agent — |
| 11 | +its model loop and all of its tools — is registered as a *single* MCP tool named |
| 12 | +after the agent. A host that speaks MCP sends a request string and receives the |
| 13 | +agent's final response; it never imports ADK and does not see the agent's |
| 14 | +individual tools. |
| 15 | + |
| 16 | +This solves the problem of making an ADK agent consumable by harnesses that are |
| 17 | +not ADK. Where `to_a2a` publishes an agent over A2A, `to_mcp_server` publishes it |
| 18 | +over MCP, so coding agents and IDEs that already speak MCP can delegate a task to |
| 19 | +an ADK agent as if it were any other tool. It builds on `Runner` to execute the |
| 20 | +agent and returns a `FastMCP` server, leaving the choice of transport (stdio for |
| 21 | +local hosts, streamable-http for networked ones) to the caller. |
| 22 | + |
| 23 | +## Get started |
| 24 | + |
| 25 | +Define an agent and expose it. Running the file starts the MCP server on stdio; |
| 26 | +an MCP host can also launch it as a subprocess. |
| 27 | + |
| 28 | +```python |
| 29 | +import random |
| 30 | + |
| 31 | +from google.adk.agents import LlmAgent |
| 32 | +from google.adk.tools.mcp_tool import to_mcp_server |
| 33 | + |
| 34 | + |
| 35 | +def roll_die(sides: int) -> int: |
| 36 | + """Roll a die with the given number of sides and return the result.""" |
| 37 | + return random.randint(1, sides) |
| 38 | + |
| 39 | + |
| 40 | +dice_agent = LlmAgent( |
| 41 | + name="dice_agent", |
| 42 | + description="Rolls dice with any number of sides and reports the outcome.", |
| 43 | + instruction="Use the roll_die tool to roll the dice the user asks for.", |
| 44 | + tools=[roll_die], |
| 45 | +) |
| 46 | + |
| 47 | +# The whole agent becomes one MCP tool named "dice_agent". |
| 48 | +server = to_mcp_server(dice_agent) |
| 49 | + |
| 50 | +if __name__ == "__main__": |
| 51 | + server.run(transport="stdio") |
| 52 | +``` |
| 53 | + |
| 54 | +A host configured to launch this file sees one tool, `dice_agent`, and calls it |
| 55 | +with a `request` string; the ADK agent runs its own model and `roll_die` loop and |
| 56 | +returns the answer. |
| 57 | + |
| 58 | +## How it works |
| 59 | + |
| 60 | +`to_mcp_server` creates a `FastMCP` server and registers one tool whose handler |
| 61 | +runs the agent through a `Runner`. If no `runner` is supplied, one is built with |
| 62 | +in-memory session, artifact, memory, and credential services. |
| 63 | + |
| 64 | +On each tool call the handler: |
| 65 | + |
| 66 | +1. Resolves an ADK session (see below), then wraps the incoming `request` string |
| 67 | + as a user `Content`. |
| 68 | +2. Drives `Runner.run_async` and iterates the event stream. |
| 69 | +3. Forwards intermediate (non-final) text events to the host as MCP **progress |
| 70 | + notifications**, so the host can show the agent working in real time. |
| 71 | +4. Maps the parts of the final response to MCP content blocks and returns them: |
| 72 | + text becomes `TextContent`, inline image data becomes `ImageContent`, audio |
| 73 | + becomes `AudioContent`, and any other inline data becomes an |
| 74 | + `EmbeddedResource`. This is why a multimodal agent's output is preserved |
| 75 | + rather than flattened to text. |
| 76 | + |
| 77 | +### Session continuity |
| 78 | + |
| 79 | +`to_mcp_server` keeps one ADK session per MCP connection, so successive tool |
| 80 | +calls on the same connection form a single multi-turn conversation. The mapping |
| 81 | +from connection to session is held in a `weakref.WeakKeyDictionary`, so a |
| 82 | +session's entry is dropped when its connection is garbage-collected. Over stdio |
| 83 | +there is one connection per process, so all calls share one conversation; over |
| 84 | +streamable-http each client connection gets its own session. |
| 85 | + |
| 86 | +`to_mcp_server` depends on `Runner`, the agent (`BaseAgent`/`LlmAgent`), |
| 87 | +`google.genai.types`, and `mcp.server.fastmcp.FastMCP`; it returns a `FastMCP` |
| 88 | +that the caller runs on a transport of their choice. |
| 89 | + |
| 90 | +## Configuration options |
| 91 | + |
| 92 | +| Option | Type | Default | Description | |
| 93 | +| --- | --- | --- | --- | |
| 94 | +| `agent` | `BaseAgent` | *required* | The agent to serve. Its model loop and all of its tools are exposed together as one MCP tool. | |
| 95 | +| `name` | `str \| None` | `None` | The MCP server and tool name. Defaults to the agent's name (or `"adk_agent"`). Set it when you want the tool to appear under a name other than the agent's. | |
| 96 | +| `instructions` | `str \| None` | `None` | Optional server instructions an MCP host may surface to its model to describe how to use the tool. | |
| 97 | +| `runner` | `Runner \| None` | `None` | A pre-built `Runner`. If omitted, one is created with in-memory services. Supply your own to use persistent or custom session, artifact, memory, or credential services — this is the recommended path for a long-lived networked server. | |
| 98 | + |
| 99 | +## Advanced applications |
| 100 | + |
| 101 | +### Serving over the network |
| 102 | + |
| 103 | +* **Problem solved**: a host on another machine needs to reach the agent. |
| 104 | +* **Implementation**: run the same server with the networked transport: |
| 105 | + `server.run(transport="streamable-http")`. Nothing about the agent changes; |
| 106 | + only the transport differs. |
| 107 | + |
| 108 | +### Bringing your own services |
| 109 | + |
| 110 | +* **Problem solved**: the default in-memory services do not persist across |
| 111 | + process restarts and are not suited to multi-client production serving. |
| 112 | +* **Implementation**: build a `Runner` with your chosen services and pass it in: |
| 113 | + `to_mcp_server(agent, runner=my_runner)`. The tool then uses those services |
| 114 | + for every call. |
| 115 | + |
| 116 | +### Multimodal responses |
| 117 | + |
| 118 | +* **Problem solved**: the agent produces images or audio, not just text. |
| 119 | +* **Implementation**: no extra work — non-text parts of the final response are |
| 120 | + returned as `ImageContent`, `AudioContent`, or `EmbeddedResource`, so the |
| 121 | + host receives them alongside any text. |
| 122 | + |
| 123 | +## Limitations |
| 124 | + |
| 125 | +* **Text input only**: the tool accepts a single `request` string. Passing |
| 126 | + media *into* the agent is not supported through the tool call, because MCP |
| 127 | + tool arguments are JSON that the host's model fills in and hosts do not place |
| 128 | + media in tool arguments. For media input, use MCP resources or elicitation |
| 129 | + instead. |
| 130 | +* **Default services are in-memory**: for a long-lived streamable-http server, |
| 131 | + sessions accumulate with no eviction; inject a `runner` with a persistent or |
| 132 | + cleaning session service. Tool calls on a single connection are expected to |
| 133 | + be sequential, since they share one session. |
| 134 | +* **Experimental**: `to_mcp_server` is `@experimental` and lives behind the |
| 135 | + `mcp` extra; its behavior may change in future releases. |
| 136 | + |
| 137 | +## Related samples |
| 138 | + |
| 139 | +* [MCP: serve an ADK agent](../../../../../contributing/samples/mcp/mcp_serve_agent) |
0 commit comments