ag2-mcp

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Serving an AG2 agent as an MCP server

将AG2 Agent部署为MCP服务器

ag2.mcp.MCPServer
turns an AG2
Agent
into a Model Context Protocol server: MCP clients (Claude Desktop, Cursor, the MCP Inspector, or any MCP-speaking app) connect and call your agent as a tool. It can also expose prompts and resources alongside the agent.
ag2.mcp.MCPServer
可将AG2
Agent
转换为Model Context Protocol(MCP)服务器:MCP客户端(Claude Desktop、Cursor、MCP Inspector或任何支持MCP的应用)可连接并将你的Agent作为工具调用。它还可以在Agent之外暴露提示词资源

Server side vs. client side — read this first

服务端 vs 客户端——先阅读此部分

There are two opposite directions, and this skill is only one of them.
DirectionYou want…Use
Server (this skill)other MCP clients to call your AG2 agent
ag2.mcp.MCPServer
Clientyour AG2 agent to call an external MCP server's tools
MCPServerTool
/ MCP toolkits — see
ag2-use-builtin-tools
If the user says "let Claude Desktop talk to my agent", "publish my agent over MCP", or "host an MCP endpoint" → this skill. If they say "give my agent the GitHub MCP tools" or "connect to an MCP server" →
ag2-use-builtin-tools
.
存在两种相反的使用方向,本技能仅覆盖其中一种。
方向你的需求…使用方案
服务端(本技能)让其他MCP客户端调用你的AG2 Agent
ag2.mcp.MCPServer
客户端让你的AG2 Agent调用外部MCP服务器的工具
MCPServerTool
/ MCP工具包——查看**
ag2-use-builtin-tools
**
如果用户提到“让Claude Desktop与我的Agent交互”、“通过MCP发布我的Agent”或“部署MCP端点”→ 使用本技能。如果用户提到“为我的Agent添加GitHub MCP工具”或“连接到MCP服务器”→ 使用
ag2-use-builtin-tools

When to use

使用场景

  • Expose an AG2 agent so external MCP clients (Claude Desktop, Cursor, IDEs) can call it.
  • Publish a single conversational
    ask
    -style tool that runs
    Agent.ask()
    and returns the reply.
  • Serve reusable prompts (templates) and resources (files/config/dynamic data) over MCP.
  • Need multi-turn history per client session, OAuth2-protected HTTP, or per-request context injection.
  • 暴露AG2 Agent,让外部MCP客户端(Claude Desktop、Cursor、IDE)可以调用它。
  • 发布单个会话式
    ask
    风格的工具,运行
    Agent.ask()
    并返回回复。
  • 通过MCP提供可复用的提示词(模板)和资源(文件/配置/动态数据)。
  • 需要为每个客户端会话保存多轮对话历史、OAuth2保护的HTTP服务,或每次请求的上下文注入。

Installation

安装

bash
pip install "ag2[mcp]"
Required. Run this install before delivering the code. Without the
mcp
extra,
from ag2.mcp import MCPServer
resolves to a stub that raises a "missing optional dependency" error on use.
bash
pip install "ag2[mcp]"
必须执行此步骤。在运行代码前先完成安装。如果不安装
mcp
扩展包,
from ag2.mcp import MCPServer
会解析为一个存根类,使用时会抛出“缺少可选依赖”错误。

60-second recipe — serve an agent over stdio

60秒快速入门——通过标准输入输出部署Agent

This is the form local MCP clients (Claude Desktop, Cursor, MCP Inspector) expect. The server reads/writes MCP frames over stdin/stdout.
python
import asyncio

from ag2 import Agent
from ag2.config import OpenAIConfig
from ag2.mcp import MCPServer

agent = Agent(
    name="assistant",
    prompt="You are a helpful assistant.",
    config=OpenAIConfig(model="gpt-4o-mini"),
)
这是本地MCP客户端(Claude Desktop、Cursor、MCP Inspector)期望的运行形式。服务器通过标准输入/输出读取/写入MCP帧。
python
import asyncio

from ag2 import Agent
from ag2.config import OpenAIConfig
from ag2.mcp import MCPServer

agent = Agent(
    name="assistant",
    prompt="You are a helpful assistant.",
    config=OpenAIConfig(model="gpt-4o-mini"),
)

The agent is exposed as ONE conversational tool, named "ask" by default,

Agent会暴露为一个名为"ask"的会话工具(默认名称),

taking a required
message
and an optional
context
string.

接收必填的
message
参数和可选的
context
字符串参数。

server = MCPServer( agent, name="assistant-mcp", # serverInfo.name in the handshake instructions="Ask me anything.", # client-facing usage hint (NOT the agent prompt) )
if name == "main": asyncio.run(server.run_stdio())

Register it with a client (Claude Desktop `claude_desktop_config.json` shown;
Cursor / other clients use the same `command` + `args` shape):

```json
{
  "mcpServers": {
    "assistant": {
      "command": "python",
      "args": ["/absolute/path/to/serve_stdio.py"],
      "env": { "OPENAI_API_KEY": "sk-..." }
    }
  }
}
The agent must have a model
config=
set. Serving an agent with no config raises
MCPAgentConfigError
on the first tool call.
server = MCPServer( agent, name="assistant-mcp", # 握手时的serverInfo.name instructions="Ask me anything.", # 面向客户端的使用提示(不是Agent的提示词) )
if name == "main": asyncio.run(server.run_stdio())

在客户端中注册(以下展示Claude Desktop的`claude_desktop_config.json`配置;Cursor/其他客户端使用相同的`command` + `args`格式):

```json
{
  "mcpServers": {
    "assistant": {
      "command": "python",
      "args": ["/absolute/path/to/serve_stdio.py"],
      "env": { "OPENAI_API_KEY": "sk-..." }
    }
  }
}
Agent 必须设置模型
config=
参数。部署未配置模型的Agent会在首次工具调用时抛出
MCPAgentConfigError

Serve over HTTP (streamable HTTP transport)

通过HTTP部署(流式HTTP传输)

MCPServer
is itself an ASGI3 application — hand it straight to
uvicorn
. It manages its own lifespan (it runs the streamable-HTTP session manager), so a standalone run just works.
python
import uvicorn

from ag2 import Agent
from ag2.config import OpenAIConfig
from ag2.mcp import MCPServer

agent = Agent(name="assistant", prompt="You help users.", config=OpenAIConfig(model="gpt-4o-mini"))

app = MCPServer(agent, path="/mcp")  # MCP endpoint mounted at /mcp

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)
HTTP transport knobs (ignored over stdio):
ParamDefaultEffect
path
"/mcp"
URL path the MCP endpoint is served at.
stateless
False
When
True
the transport issues no
mcp-session-id
, so every call is stateless regardless of
sessions=
.
json_response
False
Return plain JSON instead of SSE for responses.
security
None
OAuth2 bearer enforcement (see below).
MCPServer
本身就是一个ASGI3应用——可直接将其交给
uvicorn
运行。它会自行管理生命周期(运行流式HTTP会话管理器),因此独立运行即可正常工作。
python
import uvicorn

from ag2 import Agent
from ag2.config import OpenAIConfig
from ag2.mcp import MCPServer

agent = Agent(name="assistant", prompt="You help users.", config=OpenAIConfig(model="gpt-4o-mini"))

app = MCPServer(agent, path="/mcp")  # MCP端点挂载在/mcp路径

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)
HTTP传输配置项(标准输入输出模式下忽略):
参数默认值作用
path
"/mcp"
MCP端点的URL路径。
stateless
False
设置为
True
时,传输不会生成任何
mcp-session-id
,因此无论
sessions=
如何设置,每次调用都是无状态的。
json_response
False
返回纯JSON而非SSE格式的响应。
security
None
OAuth2令牌验证(详见下文)。

Customising the tool

自定义工具

By default the tool is named
ask
with an auto-generated description. Override:
python
server = MCPServer(
    agent,
    tool_name="consult_expert",
    tool_description="Consult the expert agent about a question.",
    stream_progress=True,   # forward agent stream events as MCP progress/log notifications (default True)
)
The tool always takes a required
message
and an optional
context
string (prepended to the message). Mirrors
Agent.as_tool()
's shape.
默认情况下,工具名为
ask
,描述为自动生成的内容。可进行如下覆盖:
python
server = MCPServer(
    agent,
    tool_name="consult_expert",
    tool_description="Consult the expert agent about a question.",
    stream_progress=True,   # 将Agent的流事件转发为MCP进度/日志通知(默认值为True)
)
工具始终接收必填的
message
参数和可选的
context
字符串参数(会前置到message内容中),与
Agent.as_tool()
的参数结构一致。

Prompts

提示词

Expose reusable prompt templates (MCP
prompts/list
+
prompts/get
). A renderer receives the call arguments as a
{name: value}
dict and returns either a plain
str
(becomes one
user
message) or a list of
PromptMessage
. Renderers may be sync or async.
python
from ag2.mcp import MCPServer, Prompt, PromptArgument, PromptMessage

def render_review(args: dict[str, str]) -> list[PromptMessage]:
    return [
        PromptMessage(role="user", text=f"Review this {args['language']} code:"),
        PromptMessage(role="user", text=args.get("code", "")),
    ]

server = MCPServer(
    agent,
    prompts=[
        Prompt(
            name="code_review",
            description="Generate a code-review prompt.",
            render=render_review,
            arguments=(
                PromptArgument(name="language", description="Programming language", required=True),
                PromptArgument(name="code", description="The code to review", required=False),
            ),
        ),
        # A bare-string renderer becomes a single user message.
        Prompt(name="greet", render=lambda args: f"Say hello to {args['who']}"),
    ],
)
The
prompts
MCP capability is advertised only when a non-empty list is passed.
暴露可复用的提示词模板(支持MCP的
prompts/list
+
prompts/get
接口)。渲染器会接收调用参数作为
{name: value}
字典,并返回纯
str
(会转换为一条
user
消息)或
PromptMessage
列表。渲染器可以是同步或异步的。
python
from ag2.mcp import MCPServer, Prompt, PromptArgument, PromptMessage

def render_review(args: dict[str, str]) -> list[PromptMessage]:
    return [
        PromptMessage(role="user", text=f"Review this {args['language']} code:"),
        PromptMessage(role="user", text=args.get("code", "")),
    ]

server = MCPServer(
    agent,
    prompts=[
        Prompt(
            name="code_review",
            description="Generate a code-review prompt.",
            render=render_review,
            arguments=(
                PromptArgument(name="language", description="Programming language", required=True),
                PromptArgument(name="code", description="The code to review", required=False),
            ),
        ),
        # 纯字符串渲染器会转换为单条user消息。
        Prompt(name="greet", render=lambda args: f"Say hello to {args['who']}"),
    ],
)
只有当传入非空列表时,才会对外宣传
prompts
MCP能力。

Resources

资源

Expose static and templated resources (MCP
resources/list
+
resources/read
, plus
resources/templates/list
for templates).
read
returns
str
(text) or
bytes
(binary); sync or async.
python
from pathlib import Path
from ag2.mcp import MCPServer, Resource, ResourceTemplate

server = MCPServer(
    agent,
    resources=[
        Resource(
            uri="config://app",
            name="app-config",
            description="Static app config.",
            mime_type="application/json",
            read=lambda: '{"env": "prod"}',
        ),
    ],
    resource_templates=[
        # RFC 6570 templates: {var} matches one path segment, {+var} spans '/'.
        ResourceTemplate(
            uri_template="file:///{+path}",
            name="file",
            description="Read a file by path.",
            read=lambda vars: Path(vars["path"]).read_text(),
        ),
    ],
)
mime_type
defaults per the MCP SDK (
text/plain
for
str
,
application/octet-stream
for
bytes
) when left
None
. The
resources
capability is advertised only when at least one resource or template is given.
暴露静态和模板化资源(支持MCP的
resources/list
+
resources/read
接口,以及针对模板的
resources/templates/list
接口)。
read
方法返回
str
(文本)或
bytes
(二进制);可以是同步或异步方法。
python
from pathlib import Path
from ag2.mcp import MCPServer, Resource, ResourceTemplate

server = MCPServer(
    agent,
    resources=[
        Resource(
            uri="config://app",
            name="app-config",
            description="Static app config.",
            mime_type="application/json",
            read=lambda: '{"env": "prod"}',
        ),
    ],
    resource_templates=[
        # RFC 6570模板:{var}匹配一个路径段,{+var}跨越多段路径。
        ResourceTemplate(
            uri_template="file:///{+path}",
            name="file",
            description="Read a file by path.",
            read=lambda vars: Path(vars["path"]).read_text(),
        ),
    ],
)
mime_type
设为
None
时,会根据MCP SDK的默认规则自动设置(
str
类型默认
text/plain
bytes
类型默认
application/octet-stream
)。只有当提供至少一个资源或模板时,才会对外宣传
resources
MCP能力。

Sessions — multi-turn history

会话——多轮对话历史

By default (
sessions=True
) each MCP session (keyed by the transport's
mcp-session-id
over HTTP, or a single per-process key over stdio) keeps its own conversation history that accumulates across
tools/call
invocations
. Tune it with
SessionConfig
, or disable with
sessions=False
for fully stateless calls.
python
from ag2.mcp import MCPServer, SessionConfig

server = MCPServer(
    agent,
    sessions=SessionConfig(
        max_sessions=1024,   # LRU cap; least-recently-used session's history is dropped past the cap
        ttl=3600,            # optional idle-expiry in seconds (None = never expire)
        storage=None,        # pluggable history backend; defaults to in-memory MemoryStorage
    ),
)
默认情况下(
sessions=True
),每个MCP会话(HTTP模式下由传输层的
mcp-session-id
标识,标准输入输出模式下为单个进程级标识)会保留自己的对话历史,且该历史会在多次
tools/call
调用中累积
。可通过
SessionConfig
进行配置,或设置
sessions=False
完全禁用会话,实现纯无状态调用。
python
from ag2.mcp import MCPServer, SessionConfig

server = MCPServer(
    agent,
    sessions=SessionConfig(
        max_sessions=1024,   # LRU上限;超过上限时会删除最近最少使用的会话历史
        ttl=3600,            # 可选的空闲过期时间(秒),None表示永不过期
        storage=None,        # 可插拔的历史存储后端;默认使用内存中的MemoryStorage
    ),
)

Or stateless — every call independent:

或者设置为无状态——每次调用完全独立:

stateless = MCPServer(agent, sessions=False)

`storage` accepts any `ag2.history.Storage` (e.g. a Redis-backed store)
for cross-replica continuity. Note: a `stateless=True` **HTTP transport** issues
no session id, so it stays stateless regardless of `sessions=`.
stateless = MCPServer(agent, sessions=False)

`storage`参数接受任何`ag2.history.Storage`实现(例如基于Redis的存储),以实现跨副本的会话连续性。注意:**HTTP传输设置`stateless=True`时不会生成会话ID**,因此无论`sessions=`如何设置,都会保持无状态。

Structured output →
structuredContent

结构化输出 →
structuredContent

If the agent has a
response_schema
that is an object schema (Pydantic model / dataclass / dict),
MCPServer
advertises it as the tool's
outputSchema
and returns validated
structuredContent
to clients. Scalar/union schemas aren't advertised — those replies flow back as plain text.
python
from pydantic import BaseModel
from ag2 import Agent
from ag2.config import OpenAIConfig
from ag2.mcp import MCPServer

class Weather(BaseModel):
    city: str
    temp_c: float

agent = Agent(name="weather", prompt="Report weather.", response_schema=Weather,
              config=OpenAIConfig(model="gpt-4o-mini"))
server = MCPServer(agent)
如果Agent的
response_schema
对象类型的 schema(Pydantic模型/数据类/字典),
MCPServer
会将其作为工具的
outputSchema
对外宣传,并向客户端返回经过验证的
structuredContent
。标量/联合类型的schema不会对外宣传——这类回复会以纯文本形式返回。
python
from pydantic import BaseModel
from ag2 import Agent
from ag2.config import OpenAIConfig
from ag2.mcp import MCPServer

class Weather(BaseModel):
    city: str
    temp_c: float

agent = Agent(name="weather", prompt="Report weather.", response_schema=Weather,
              config=OpenAIConfig(model="gpt-4o-mini"))
server = MCPServer(agent)

tool.outputSchema is set; call results carry result.structuredContent == {"city": ..., "temp_c": ...}

tool.outputSchema已设置;调用结果会携带result.structuredContent == {"city": ..., "temp_c": ...}

undefined
undefined

Per-request context —
AskContext
/
ContextProvider

每次请求上下文 ——
AskContext
/
ContextProvider

A
context_provider
is an async hook that runs per request. It receives the authenticated bearer token (an
mcp.server.auth.provider.AccessToken
, or
None
when unauthenticated) and returns an
AskContext
whose non-
None
fields are passed straight into
Agent.ask()
. Use it to inject per-principal variables, tools, or prompt — context the stateless executor otherwise omits.
python
from typing import Any
from ag2.mcp import AskContext, ContextProvider, MCPServer

async def provide(token: Any) -> AskContext:
    # Resolve the caller from `token`, then scope the turn to them.
    tenant = "acme"  # e.g. token.scopes / a claims lookup
    return AskContext(
        variables={"tenant": tenant},   # -> Agent.ask(variables=...)
        tools=None,                      # -> Agent.ask(tools=...)  (None = leave default)
        prompt="Be concise.",           # -> Agent.ask(prompt=...)
    )

server = MCPServer(agent, context_provider=provide)
AskContext
fields:
variables: dict | None
,
tools: list | None
,
prompt: list[str] | str | None
. Any field left
None
is omitted, so the default (stateless) behavior is preserved.
context_provider
是一个异步钩子,会在每次请求时运行。它接收已认证的Bearer令牌(
mcp.server.auth.provider.AccessToken
实例,未认证时为
None
)并返回
AskContext
对象,其非
None
字段会直接传入
Agent.ask()
。可用于注入每个主体的变量、工具或提示词——这些内容是无状态执行器默认不会包含的上下文。
python
from typing import Any
from ag2.mcp import AskContext, ContextProvider, MCPServer

async def provide(token: Any) -> AskContext:
    # 从`token`解析调用方信息,然后将对话限定为该用户的上下文。
    tenant = "acme"  # 例如从token.scopes或声明中获取
    return AskContext(
        variables={"tenant": tenant},   # -> Agent.ask(variables=...)
        tools=None,                      # -> Agent.ask(tools=...) (None表示保留默认值)
        prompt="Be concise.",           # -> Agent.ask(prompt=...)
    )

server = MCPServer(agent, context_provider=provide)
AskContext
的字段:
variables: dict | None
,
tools: list | None
,
prompt: list[str] | str | None
。任何设为
None
的字段都会被忽略,因此会保留默认的无状态行为。

Security — OAuth2 bearer (HTTP only) — needs external setup

安全——OAuth2 Bearer令牌(仅HTTP模式)——需要外部配置

For HTTP, protect the endpoint with OAuth 2.1 bearer auth. The MCP server acts purely as a Resource Server: it advertises trusted authorization server(s) via RFC 9728 Protected Resource Metadata at
/.well-known/oauth-protected-resource
and verifies presented tokens. Issuing tokens stays with your external authorization server.
python
from ag2.mcp import MCPServer
from ag2.mcp.security import oauth2_scheme, require

security = require(
    oauth2_scheme(url="https://auth.example.com"),  # absolute http(s) issuer URL
    resource_url="https://api.example.com/mcp",     # this server's public endpoint
    verifier=my_token_verifier,                     # your mcp TokenVerifier implementation
    required_scopes=["mcp.read"],                   # a token must carry every scope
)

app = MCPServer(agent, path="/mcp", security=security)
  • security.resource_url
    's path component must equal
    path
    (here
    /mcp
    ), or
    MCPServer
    raises
    ValueError
    .
  • Missing/invalid token →
    401
    (with a
    WWW-Authenticate
    header pointing at the metadata); insufficient scopes →
    403
    .
  • verifier
    is a bring-your-own
    mcp.server.auth.provider.TokenVerifier
    .
    oauth2_scheme(url=...)
    rejects non-
    http(s)
    URLs (an OIDC issuer string is not a usable authorization-server URL — pass the full URL).
Requires external setup: a real authorization server to mint tokens and a concrete
TokenVerifier
. Exercise the unauthenticated path in-process (see testing below); the token round-trip needs your OAuth provider.
对于HTTP模式,可使用OAuth 2.1 Bearer认证保护端点。MCP服务器仅作为资源服务器:它会通过RFC 9728定义的受保护资源元数据在
/.well-known/oauth-protected-resource
路径宣传可信的授权服务器,并验证传入的令牌。令牌的颁发由外部授权服务器负责。
python
from ag2.mcp import MCPServer
from ag2.mcp.security import oauth2_scheme, require

security = require(
    oauth2_scheme(url="https://auth.example.com"),  # 授权服务器的绝对http(s) URL
    resource_url="https://api.example.com/mcp",     # 本服务器的公开端点
    verifier=my_token_verifier,                     # 自定义的mcp TokenVerifier实现
    required_scopes=["mcp.read"],                   # 令牌必须包含所有指定的权限范围
)

app = MCPServer(agent, path="/mcp", security=security)
  • security.resource_url
    的路径部分必须等于
    path
    参数(此处为
    /mcp
    ),否则
    MCPServer
    会抛出
    ValueError
  • 令牌缺失/无效→返回
    401
    (包含指向元数据的
    WWW-Authenticate
    响应头);权限范围不足→返回
    403
  • verifier
    是自定义的
    mcp.server.auth.provider.TokenVerifier
    实现。
    oauth2_scheme(url=...)
    会拒绝非
    http(s)
    的URL(OIDC颁发者字符串不是可用的授权服务器URL——必须传入完整URL)。
需要外部配置:需要一个真实的授权服务器来生成令牌,以及具体的
TokenVerifier
实现。可通过进程内测试验证未认证路径(见下文测试部分);令牌的往返流程需要依赖你的OAuth服务提供商。

Testing in-process — no sockets, no subprocess

进程内测试——无需套接字,无需子进程

ag2.mcp.testing
stands the server up entirely in memory. Use
connect()
for a low-level
ClientSession
(list/call tools, prompts, resources) and
serve()
for an
httpx.AsyncClient
over the ASGI transport (exercise the HTTP path, status codes, metadata). Pair with
TestConfig
from
ag2.testing
to mock the LLM — no API keys needed.
python
import asyncio

from ag2 import Agent
from ag2.testing import TestConfig
from ag2.mcp import MCPServer, Resource
from ag2.mcp import testing

async def main() -> None:
    agent = Agent(name="assistant", prompt="p", config=TestConfig("Hello from the agent!"))
    server = MCPServer(
        agent,
        resources=[Resource(uri="config://app", name="cfg", read=lambda: '{"env": "prod"}')],
    )

    # In-memory MCP client/server pair (the MCP analog of an ASGI test client).
    async with testing.connect(server) as session:
        await session.initialize()

        tools = await session.list_tools()
        assert [t.name for t in tools.tools] == ["ask"]

        result = await session.call_tool("ask", {"message": "Hi"})
        assert "Hello from the agent" in result.content[0].text

        res = await session.read_resource("config://app")
        assert res.contents[0].text == '{"env": "prod"}'

    # Exercise the HTTP transport (initialize handshake, session id) in-memory:
    async with testing.serve(server) as client:
        resp = await client.post(
            "/mcp",
            headers={"Accept": "application/json, text/event-stream", "Content-Type": "application/json"},
            json={
                "jsonrpc": "2.0", "id": 1, "method": "initialize",
                "params": {"protocolVersion": "2025-06-18", "capabilities": {},
                           "clientInfo": {"name": "test", "version": "1.0"}},
            },
        )
        assert resp.status_code == 200
        assert "mcp-session-id" in resp.headers

    print("ok")

if __name__ == "__main__":
    asyncio.run(main())
testing.connect(server, raise_exceptions=..., **session_kwargs)
forwards extra kwargs (e.g.
logging_callback
/
message_handler
) to the client session — how you observe streamed progress / log notifications.
TestConfig
caveat for multi-turn:
TestConfig.create()
builds a fresh response iterator per turn, so giving it
TestConfig("a", "b")
will not show
"a"
then
"b"
across two separate MCP
call_tool
s — each call replays from the first scripted response. That's a property of the mock, not the server: session history really does accumulate (verify it by inspecting the growing message list a custom test client receives, or use a real model).
ag2.mcp.testing
可完全在内存中启动服务器。使用
connect()
获取底层的
ClientSession
(列出/调用工具、提示词、资源),使用
serve()
获取基于ASGI传输的
httpx.AsyncClient
(测试HTTP路径、状态码、元数据)。搭配
ag2.testing
中的
TestConfig
可模拟LLM——无需API密钥。
python
import asyncio

from ag2 import Agent
from ag2.testing import TestConfig
from ag2.mcp import MCPServer, Resource
from ag2.mcp import testing

async def main() -> None:
    agent = Agent(name="assistant", prompt="p", config=TestConfig("Hello from the agent!"))
    server = MCPServer(
        agent,
        resources=[Resource(uri="config://app", name="cfg", read=lambda: '{"env": "prod"}')],
    )

    # 内存中的MCP客户端/服务器对(相当于ASGI测试客户端的MCP版本)。
    async with testing.connect(server) as session:
        await session.initialize()

        tools = await session.list_tools()
        assert [t.name for t in tools.tools] == ["ask"]

        result = await session.call_tool("ask", {"message": "Hi"})
        assert "Hello from the agent" in result.content[0].text

        res = await session.read_resource("config://app")
        assert res.contents[0].text == '{"env": "prod"}'

    # 在内存中测试HTTP传输(初始化握手、会话ID):
    async with testing.serve(server) as client:
        resp = await client.post(
            "/mcp",
            headers={"Accept": "application/json, text/event-stream", "Content-Type": "application/json"},
            json={
                "jsonrpc": "2.0", "id": 1, "method": "initialize",
                "params": {"protocolVersion": "2025-06-18", "capabilities": {},
                           "clientInfo": {"name": "test", "version": "1.0"}},
            },
        )
        assert resp.status_code == 200
        assert "mcp-session-id" in resp.headers

    print("ok")

if __name__ == "__main__":
    asyncio.run(main())
testing.connect(server, raise_exceptions=..., **session_kwargs)
会将额外的参数(例如
logging_callback
/
message_handler
)转发给客户端会话——用于观察流式进度/日志通知。
TestConfig
多轮对话注意事项
TestConfig.create()
会为每轮对话创建新的响应迭代器,因此传入
TestConfig("a", "b")
不会在两次独立的MCP
call_tool
调用中依次返回
"a"
"b"
——每次调用都会从第一个脚本化响应开始重播。这是模拟工具的特性,而非服务器的问题:会话历史确实会累积(可通过自定义测试客户端接收的消息列表增长情况来验证,或使用真实模型)。

Public API reference

公开API参考

All importable from
ag2.mcp
:
SymbolKindPurpose
MCPServer
classWrap an
Agent
as an MCP server (ASGI app +
run_stdio()
).
SessionConfig
dataclass
max_sessions
,
ttl
,
storage
for multi-turn history.
Prompt
dataclass
name
,
render
,
description
,
arguments
— a prompt template.
PromptArgument
dataclass
name
,
description
,
required
— a declared prompt arg.
PromptMessage
dataclass
role
(
"user"
/
"assistant"
),
text
— one rendered message.
Resource
dataclass
uri
,
name
,
read
,
description
,
mime_type
— static resource.
ResourceTemplate
dataclass
uri_template
,
name
,
read
, ... — RFC 6570 dynamic resource.
AskContext
dataclass
variables
,
tools
,
prompt
— per-request injection into
ask()
.
ContextProvider
type alias`async (AccessToken
build_ask_tool
functionBuild the single conversational
MCPTool
standalone (advanced/tests).
From
ag2.mcp.security
:
oauth2_scheme
,
require
,
Scheme
,
Requirement
. From
ag2.mcp.testing
:
connect
,
serve
.
所有API均可从
ag2.mcp
导入:
符号类型用途
MCPServer
Agent
封装为MCP服务器(ASGI应用 +
run_stdio()
)。
SessionConfig
数据类配置多轮对话历史的
max_sessions
ttl
storage
参数。
Prompt
数据类定义提示词模板的
name
render
description
arguments
PromptArgument
数据类定义提示词参数的
name
description
required
属性。
PromptMessage
数据类定义单条渲染消息的
role
"user"
/
"assistant"
)、
text
属性。
Resource
数据类定义静态资源的
uri
name
read
description
mime_type
ResourceTemplate
数据类定义RFC 6570动态资源的
uri_template
name
read
等属性。
AskContext
数据类定义传入
ask()
的每次请求注入内容:
variables
tools
prompt
ContextProvider
类型别名`async (AccessToken
build_ask_tool
函数独立构建单个会话式
MCPTool
(高级场景/测试用)。
ag2.mcp.security
可导入:
oauth2_scheme
require
Scheme
Requirement
。从
ag2.mcp.testing
可导入:
connect
serve

Common pitfalls

常见陷阱

  • Missing
    mcp
    extra
    pip install "ag2[mcp]"
    ; otherwise the imports are dependency stubs that raise on use.
  • Agent has no model config
    MCPServer
    accepts it, but the first tool call raises
    MCPAgentConfigError
    . Set
    Agent(config=...)
    .
  • Confusing server with client
    MCPServer
    SERVES your agent. To CONSUME an external MCP server's tools from your agent, use
    MCPServerTool
    (see
    ag2-use-builtin-tools
    ).
  • instructions=
    ≠ system prompt
    instructions
    is client-facing "how to use this server" text in the handshake; it is not derived from the agent's prompt. Pass it explicitly.
  • stateless=True
    HTTP discards sessions
    — a stateless HTTP transport issues no
    mcp-session-id
    , so multi-turn history can't key. Use
    stateless=False
    (default) when you want sessions.
  • security.resource_url
    path mismatch
    — its path must equal
    path
    ; otherwise
    MCPServer.__init__
    raises
    ValueError
    .
  • Non-object
    response_schema
    — only object schemas get
    outputSchema
    /
    structuredContent
    ; scalars/unions come back as text.
  • 缺少
    mcp
    扩展包
    ——需执行
    pip install "ag2[mcp]"
    ;否则导入的是依赖存根类,使用时会抛出错误。
  • Agent未设置模型配置——
    MCPServer
    会接受未配置的Agent,但首次工具调用时会抛出
    MCPAgentConfigError
    。需设置
    Agent(config=...)
  • 混淆服务端与客户端——
    MCPServer
    用于部署你的Agent。若要让你的Agent调用外部MCP服务器的工具,请使用
    MCPServerTool
    (查看
    ag2-use-builtin-tools
    )。
  • instructions=
    ≠ 系统提示词
    ——
    instructions
    是握手时面向客户端的“如何使用此服务器”说明文本;并非从Agent的提示词派生而来,需显式传入。
  • stateless=True
    的HTTP模式会丢弃会话
    ——无状态HTTP传输不会生成
    mcp-session-id
    ,因此无法关联多轮对话历史。若需要会话,请使用默认的
    stateless=False
  • security.resource_url
    路径不匹配
    ——其路径必须与
    path
    参数一致;否则
    MCPServer.__init__
    会抛出
    ValueError
  • 非对象类型的
    response_schema
    ——只有对象类型的schema才会生成
    outputSchema
    /
    structuredContent
    ;标量/联合类型会以文本形式返回。

Going deeper

深入学习

  • Source:
    ag2/mcp/{server,sessions,prompts,resources,executor,info,security,testing}.py
  • Runnable reference covering every sample above:
    references/test_server.py
    (run with
    python references/test_server.py
    , no API keys needed)
  • MCP spec: https://modelcontextprotocol.io
  • Client side (consuming MCP servers from an agent): skill
    ag2-use-builtin-tools
  • 源码:
    ag2/mcp/{server,sessions,prompts,resources,executor,info,security,testing}.py
  • 包含上述所有示例的可运行参考代码:
    references/test_server.py
    (执行
    python references/test_server.py
    即可运行,无需API密钥)
  • MCP规范:https://modelcontextprotocol.io
  • 客户端场景(从Agent调用外部MCP服务器):技能
    ag2-use-builtin-tools