AS AgentScopeAPI 手册 / API Reference
MISSION 05 · Relay Operator · 200 XP

接线员Relay Operator

通过 stdio 消费一个 MCP 服务Consume an MCP server over stdio

类比Analog OpenAI Operator / MCP 工具生态OpenAI Operator / MCP tool ecosystem 技能Skills MCP 互操作MCP interop生产意识Production readiness工具系统Tool system

任务简报Brief

团队把 runbook 查询封装成了 MCP 服务,Python 与 Java 的 Agent 都要复用它。服务通过 stdio 启动,只暴露一个 lookup_runbook 工具;客户端必须完成发现、调用、读取结果和关闭。

The team wrapped runbook lookup as an MCP service that both Python and Java agents must reuse. The server starts over stdio and exposes one lookup_runbook tool; the client must discover, call, read and close.

MCP 的价值是“工具与 Agent 解耦”:服务端可以是任何语言、任何进程,客户端只关心协议。这一关用 Python 写参考服务,让 Java 客户端通过 stdio 连上去。你会亲手遇到两个真实差异:工具名在 Python 里是 mcp__runbook__lookup_runbook,在 Java 里却是 lookup_runbook;以及客户端必须在 finally 里关闭,否则子进程会泄漏。

MCP decouples tools from agents: the server can be any language or process, and the client only cares about the protocol. This mission uses a Python reference server and a Java client over stdio. You will hit two real differences: Python names the tool mcp__runbook__lookup_runbook, Java exposes lookup_runbook; and the client must close in finally or leak the child process.

学习目标Learning goals

  • 启动 stdio MCP 服务并发现工具。

    Start a stdio MCP server and discover its tools.

  • 把 MCP 工具注册进 Toolkit,并让 Agent 真正调用。

    Register the MCP tool in a Toolkit and let the agent call it.

  • 验证工具输出包含 runbook 段落,而不只是调用成功。

    Verify the tool output contains the runbook section, not just a successful call.

  • 处理生命周期、超时与跨语言命名差异。

    Handle lifecycle, timeouts and cross-language naming differences.

核心概念Core concepts

MCPClient / McpClientBuilder

客户端负责启动/连接服务、列出工具并转发调用。Python 用 MCPClient,Java 用 McpClientBuilder。

The client starts/connects to the server, lists tools and forwards calls. Python uses MCPClient; Java uses McpClientBuilder.

Tool discovery

服务端声明的工具会被转换成客户端 Toolkit 的 schema;启用哪些工具由客户端决定。

Server-declared tools become client Toolkit schemas; the client decides which to enable.

Lifecycle

连接、调用、关闭必须成对;finally 里关闭客户端,避免子进程和管道泄漏。

Connect, call and close must pair; close in finally to avoid leaked child processes and pipes.

双轨实现Build it twice

先完整跑通一条轨道,再切换重来。两条轨道写出同一种产物,方便直接对照行为。Run one track end to end, then switch and repeat. Both tracks write the same artifact so you can compare behavior directly.

当前轨道Current track
Python 2.0.11 轨道Track

参考服务在 lab/common/mcp_server.py,使用 MCP 2.x 的 MCPServer 与 stdio 传输。它不依赖网络,只读取本地 runbook.md。

The reference server lives in lab/common/mcp_server.py and uses MCP 2.x MCPServer with stdio. It needs no network and reads the local runbook.md.

01

构造并连接客户端Build and connect the client

cwd 与 command 必须指向能 import common.mcp_server 的环境;这里直接用当前 Python 解释器。

cwd and command must point to an environment that can import common.mcp_server; use the current interpreter.

lab/missions/m5_operator/mission.py
from agentscope.mcp import MCPClient, StdioMCPConfig

client = MCPClient(
    name="runbook",
    is_stateful=True,
    mcp_config=StdioMCPConfig(
        command=sys.executable,
        args=["-m", "common.mcp_server"],
        cwd=str(LAB_ROOT),
    ),
    enable_tools=["lookup_runbook"],
    execution_timeout=15,
)
await client.connect()
02

注册进 Toolkit 并调用Register in Toolkit and call

Python 会把服务名拼进工具名:mcp__runbook__lookup_runbook。模型调用时必须用完整名字。

Python prefixes the tool with the server name: mcp__runbook__lookup_runbook. The model must call the full name.

lab/missions/m5_operator/mission.py
toolkit = Toolkit(mcps=[client])

model = ScriptedModel([
    tool_response("mcp__runbook__lookup_runbook",
                  call_id="mcp-1", symptom="context overflow"),
    "Compress context and alert before the window fills.",
])
agent = Agent(name="operator", model=model, toolkit=toolkit, ...)
reply = await agent.reply(UserMsg("user", "Handle context overflow."))
03

关闭并判题Close and check

client.close() 放在 finally;check 会验证远程工具数量、schema 名、回复和工具输出中的 Context overflow / context_length_exceeded。

Put client.close() in finally; the check validates remote tool count, schema name, reply and the Context overflow / context_length_exceeded output.

lab/missions/m5_operator/mission.py
try:
    await client.connect()
    ...
finally:
    await client.close()

# python lab.py check m5
# PASS CODE: AS-M5-C9HY
运行判题Run the check
python lab.py check m5

如果客户端启动超时,先单独运行 python -m common.mcp_server 看服务能否启动,再检查 cwd 与 PYTHONPATH。

If client startup times out, run python -m common.mcp_server alone to verify the server, then check cwd and PYTHONPATH.

源码Source lab/missions/m5_operator/mission.py

判题证明了什么What the check proves

  • MCPClient 通过 stdio 连接并发现 1 个工具MCPClient connected over stdio and discovered 1 tool
  • Toolkit 把 MCP 工具转换成模型 schemaToolkit converted the MCP tool into a model-facing schema
  • Agent 调用远程工具并读取 runbook 段落The agent called the remote tool and read the runbook section
  • client.close() 在 finally 中执行client.close() ran in a finally block
  • 产物:lab/work/m5/mcp_session.jsonArtifact: lab/work/m5/mcp_session.json
Java 2.0.4 轨道Track

Java 通过 McpClientBuilder 连接同一个 Python 服务。需要保证 LabSupport.pythonExecutable() 找到装有 mcp 的 venv;否则初始化会超时。

Java connects to the same Python server through McpClientBuilder. LabSupport.pythonExecutable() must find a venv with mcp, or initialization times out.

01

构建 stdio 客户端Build the stdio client

stdioTransport 接收命令、参数和环境变量;这里用 PYTHONPATH 指向 lab 根目录,确保子进程能 import common.mcp_server。

stdioTransport takes command, args and env; PYTHONPATH points at the lab root so the child can import common.mcp_server.

lab/java/src/main/java/lab/m5/M5Operator.java
McpClientWrapper client = McpClientBuilder.create("runbook")
        .stdioTransport(
                LabSupport.pythonExecutable(),
                List.of("-m", "common.mcp_server"),
                Map.of("PYTHONPATH", LabSupport.labRoot().toString()))
        .initializationTimeout(Duration.ofSeconds(30))
        .timeout(Duration.ofSeconds(20))
        .buildSync();
02

注册并注意命名Register and mind the name

Java 暴露的工具名就是 lookup_runbook,没有 mcp__runbook__ 前缀。脚本里写错名字会得到 Tool not found。

Java exposes the tool as lookup_runbook with no mcp__runbook__ prefix. A wrong name yields Tool not found.

lab/java/src/main/java/lab/m5/M5Operator.java
Toolkit toolkit = new Toolkit();
toolkit.registration()
        .mcpClient(client)
        .enableTools(List.of("lookup_runbook"))
        .apply();

model.queue(LabSupport.toolCall("mcp-1", "lookup_runbook",
        Map.of("symptom", "context overflow")));
03

编译并判题Compile and check

检查会启动服务、验证 schema 名、执行工具、读取最后一个模型调用里的 ToolResultBlock,并确认 mcp_session.json 已写入。

The check starts the server, validates the schema name, runs the tool, reads the ToolResultBlock from the last model call and writes mcp_session.json.

shell
cd lab/java
mvn -q -o compile exec:java -Dexec.args="check m5"

# 期望看到
# PASS CODE: AS-M5-C9HY
运行判题Run the check
cd lab/java && mvn -q -o compile exec:java -Dexec.args="check m5"

Java 的 ToolResultBlock 在模型调用历史里;检查时从最后一次 FakeChatModel 调用提取,而不是从 agent state 的 context 里找。

Java's ToolResultBlock appears in the model call history; the check extracts it from the last FakeChatModel call rather than agent-state context.

源码Source lab/java/src/main/java/lab/m5/M5Operator.java

判题证明了什么What the check proves

  • McpClientBuilder 通过 stdio 连接并注册 1 个工具McpClientBuilder connected over stdio and registered 1 tool
  • Java 暴露的名字是 lookup_runbook(Python 则带 mcp__runbook__ 前缀)Java exposes lookup_runbook (Python prefixes mcp__runbook__)
  • Agent 调用远程工具并读取 runbook 段落The agent called the remote tool and read the runbook section
  • client.close() 在 finally 中执行client.close() ran in a finally block

Python ↔ Java 差异Python ↔ Java differences

  • Python 的工具名带 mcp__<server>__ 前缀;Java 2.0.4 直接使用远端工具名。脚本化调用时最容易在这里踩坑。

    Python prefixes tools with mcp__<server>__; Java 2.0.4 uses the remote name directly. This is the easiest scripting trap.

  • Python Toolkit(mcps=[client]) 一次性注册;Java 使用 registration().mcpClient(client).enableTools(...).apply() 显式选择工具。

    Python registers with Toolkit(mcps=[client]); Java uses registration().mcpClient(client).enableTools(...).apply() to choose tools.

  • 两边都是 stdio 子进程,但 Java 需要显式设置初始化/调用超时,并通过 PYTHONPATH/解释器路径保证服务能启动。

    Both use stdio child processes, but Java needs explicit init/call timeouts and a correct PYTHONPATH/interpreter path.

常见坑Pitfalls

  1. 客户端初始化超时:解释器找不到 mcp 包、PYTHONPATH 没指向 lab 根目录、或服务启动即崩溃。先单独启动服务验证。

    Client init timeout: the interpreter lacks mcp, PYTHONPATH misses the lab root, or the server crashes on start. Launch the server alone first.

  2. 工具名写错:Java 没有前缀,Python 有前缀。Tool not found 不是模型的问题,是脚本/注册名不一致。

    Wrong tool name: Java has no prefix, Python does. Tool not found is a registration/script mismatch, not a model issue.

  3. 忘记关闭客户端:stdio 子进程持续存活,端口/管道和许可证都可能泄漏。,close() 必须放在 finally。

    Forgetting to close: stdio child processes stay alive, leaking pipes and licenses. Put close() in finally.

  4. 直接信任远端工具输出:MCP 服务可能返回提示注入内容。生产环境要对外部工具输出做隔离、长度限制和权限控制。

    Trusting remote output blindly: an MCP server can return prompt-injection content. Isolate, length-limit and permission external tool output in production.

领取通行码Claim your pass code

先运行当前轨道的 check。CLI 会打印 PASS CODE、XP 与徽章。粘贴到这里;同一关在每条轨道可各领取一次。Run the check for the current track. The CLI prints PASS CODE plus the exact XP and badge. Paste it here; the same mission can be claimed once on each track.

记录到轨道Record on track

费曼复述Feynman recall

合上教程,用自己的话讲清机制。关键点覆盖 65%、篇幅 20%、结构 15%;60 分通过并一次性获得 20 XP。Explain the mechanism without looking. Coverage 65%, length 20%, structure 15%; 60 passes and earns +20 XP once.

MCP 解决了什么问题?stdio 客户端的生命周期里最容易出错的三个点是什么?

What problem does MCP solve? What are the three most error-prone points in a stdio client lifecycle?

0 chars

查看参考答案Show the model answer

MCP 用统一协议把工具服务与 Agent 解耦:服务端可以是任意语言或进程,客户端只负责发现和调用。stdio 生命周期最容易错在三点:启动环境(解释器、cwd、PYTHONPATH)、超时与工具名(Python 带前缀,Java 不带)、以及关闭(必须在 finally 里释放子进程)。

MCP decouples tool services from agents through a uniform protocol: servers can be any language or process, and clients only discover and call. The three most error-prone points in a stdio lifecycle are startup environment (interpreter, cwd, PYTHONPATH), timeouts and tool names (Python prefixes, Java does not), and shutdown (release the child process in finally).

间隔复习Spaced review

首次领取后会在明天安排复习。按真实感受评分,下一次间隔会自适应。After the first claim a review is scheduled for tomorrow. Rate how it felt; the next interval adapts.

领取通行码后会自动安排第一次复习。Claim the pass code to schedule your first review.
基于官方固定版本源码静态生成Generated from pinned official sources · Python 2.0.11 · Java 2.0.4
签名与文档为静态抽取;如有疑问请以源码为准。Signatures and docs are extracted statically; verify against the source when in doubt.