【AgentScope Java新手村系列】(15)MCP协议工具

第十五章 MCP 协议工具:tools.json 声明式接入 MCP Server,配置即集成

"我们想把 GitHub MCP server、PostgreSQL MCP server、Slack MCP server 一起接进 agent。1.x 时代我们写 3 个 Tool 子类;2.0 用 workspace/tools.json 一行声明一个 MCP server,agent 启动时自动发现工具。这是 2.0 推荐的『配置即集成』。"

本章你将学到:MCP server 是什么、tools.json 里怎么写 MCP 段、如何在 Java 端补强 MCP、常见 MCP server 接入示例。

15.1 MCP 是什么?

Model Context Protocol(MCP) 是 Anthropic 在 2024 年推出的开放协议,让 LLM 应用以统一方式发现并调用外部工具。AgentScope 2.0 把 MCP server 作为 agent 工具的一种"来源"------你在 tools.json 里声明一个 MCP server,agent 启动时通过 stdiosse 协议连上它,自动把 server 暴露的工具当作 agent 自己的 tool。

15.2 第一个 MCP 集成

复制代码
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${env:GITHUB_TOKEN}"
      }
    }
  }
}

HarnessAgent.builder().workspace(path) 启动时会自动扫描 workspace/tools.jsonmcpServers 段、连接每个 server、把工具注册到 agent------不需要额外开关

复制代码
HarnessAgent agent = HarnessAgent.builder()
        ...
        .workspace(Path.of("./workspace"))
        .build();

跑起来后,agent 就能调用 GitHub MCP server 暴露的 create_issue / list_repos / search_code 等工具了。

15.3 三种连接方式

协议 适用 声明方式
stdio 本地进程,最常见 command + args
sse 远程 HTTP SSE server url + headers
ws 双向 WebSocket url + headers

stdio 例子:

复制代码
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "./data"]
    }
  }
}

sse 例子:

复制代码
{
  "mcpServers": {
    "remote-knowledge": {
      "url": "https://mcp.example.com/sse",
      "headers": {
        "Authorization": "Bearer ${env:MCP_TOKEN}"
      }
    }
  }
}

15.4 不想写 JSON 文件?可以在 Java 代码里直接配

用 JSON 文件当然可以,但有时候你想在代码里动态拼参数------比如 token 从环境变量读、超时按环境切换。这时候直接用 ToolsConfig + McpServerConfig 在 Java 代码里配,效果和 tools.json 完全一样:

复制代码
import io.agentscope.harness.agent.tools.McpServerConfig;
import io.agentscope.harness.agent.tools.ToolsConfig;

ToolsConfig cfg = new ToolsConfig();
Map<String, McpServerConfig> servers = new LinkedHashMap<>();

McpServerConfig github = new McpServerConfig();
github.setTransport("stdio");
github.setCommand("npx");
github.setArgs(List.of("-y", "@modelcontextprotocol/server-github"));
github.setEnv(Map.of(
        "GITHUB_PERSONAL_ACCESS_TOKEN", System.getenv("GITHUB_TOKEN")));
servers.put("github", github);

cfg.setMcpServers(servers);

HarnessAgent agent = HarnessAgent.builder()
        ...
        .toolsConfig(cfg)
        .build();

McpServerConfigio.agentscope.harness.agent.tools)支持 transport / command / args / env / url / headers / timeout 等字段,与 tools.jsonmcpServers 段一一对应。

15.5 与 Permission 协作

MCP 工具默认会经过 Permission 系统------你可以在 rule 里直接写 MCP 工具名:

复制代码
PermissionContextState perms = PermissionContextState.builder()
        .mode(PermissionMode.ACCEPT_EDITS)                   // 大部分操作直接放行
        .addAskRule("create_issue",                          // create_issue 是 MCP GitHub server 的工具
                new PermissionRule("create_issue", null,
                        PermissionBehavior.ASK, "userSettings"))  // 建 Issue 前要弹窗问用户
        .addDenyRule("drop_table",                           // drop_table 是本地工具
                new PermissionRule("drop_table", null,
                        PermissionBehavior.DENY, "userSettings")) // 删表直接拒绝
        .build();

MCP 工具和本地 @Tool 工具在 Permission 系统里地位完全平等------create_issue(来自 GitHub MCP server)和 drop_table(来自本地 Java 类)同等待遇。

15.6 常见 MCP server 一览

名称 工具集
@modelcontextprotocol/server-github 仓库、Issue、PR
@modelcontextprotocol/server-filesystem 读、写、列目录
@modelcontextprotocol/server-postgres 查 SQL、DDL
@modelcontextprotocol/server-slack 发消息、查频道
@modelcontextprotocol/server-puppeteer 浏览器自动化
@modelcontextprotocol/server-git git 操作

完整列表见 MCP 官方 server 仓库

15.7 工具命名冲突

如果两个 MCP server 都暴露了同名工具,AgentScope 会按下列优先级保留一个:

  1. Toolkit 中 Java 端注册的工具(最高)
  2. 第一个启动成功的 MCP server
  3. 后面的同名工具被忽略 + 启动日志里 WARN

要避免冲突:用 toolFilter 限定:

复制代码
{
  "mcpServers": { "...": "..." },
  "toolFilter": {
    "deny": ["github_legacy_*"]
  }
}

15.8 完整可运行示例

这个例子在演示什么?

你有一个 devops agent,可以查 GitHub Issue 和操作本地文件。生产环境通过 workspace/tools.json 声明两个 MCP server(GitHub、Filesystem);开发环境先用 @Tool 模拟 MCP 行为,避免依赖外部进程。两种方式代码结构相同,仅工具来源不同。

复制代码
public class Chapter15_McpTools {

    // Mock MCP GitHub server 工具
    public static class MockGitHubTools {
        @Tool(name = "create_issue", description = "创建 GitHub Issue")
        public String createIssue(String repo, String title) {
            return "Issue \"" + title + "\" 已创建到 " + repo + "。";
        }

        @Tool(name = "list_repos", description = "列出用户的仓库")
        public String listRepos() {
            return "- agentscope/agentscope-java\n- agentscope/agentscope-python";
        }

        @Tool(name = "search_code", description = "在代码仓库中搜索")
        public String searchCode(String query) {
            return "搜索 \"" + query + "\" 的结果。";
        }
    }

    // Mock MCP Filesystem server 工具
    public static class MockFilesystemTools {
        @Tool(name = "read_file", description = "读取文件内容")
        public String readFile(String path) {
            return "文件 " + path + " 的内容。";
        }

        @Tool(name = "list_directory", description = "列出目录内容")
        public String listDirectory(String path) {
            return "目录 " + path + " 的内容。";
        }
    }

    public static void main(String[] args) {
        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new MockGitHubTools());
        toolkit.registerTool(new MockFilesystemTools());

        HarnessAgent agent = HarnessAgent.builder()
                .name("devops")
                .sysPrompt("你是一个 devops 助理,可以查 GitHub issue 和操作本地文件。")
                .model(model())
                .workspace(Path.of("./workspace"))
                .toolkit(toolkit)
                .build();

        agent.call(
                List.of(new UserMessage("user",
                        "看看 agentscope/agentscope-java 最近有什么 Issue。")),
                RuntimeContext.empty())
                .block();
    }
}

workspace/tools.json(生产环境用,替代上面 Java 端的 mock):

复制代码
{
  "mcpServers": {
    "github": {
      "transport": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${env:GITHUB_TOKEN}"
      }
    },
    "filesystem": {
      "transport": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "./data"]
    }
  }
}

开发时用 @Tool mock,生产时去掉 mock 换成 tools.json------业务代码完全不变,只改工具来源。

15.9 MCP Tool Meta:在工具调用中传递元数据

RC2 新增 McpMeta 机制------你可以在 RuntimeContext 中放入元数据(traceId、userId、回调地址等),框架自动在 MCP CallToolRequestmeta 参数中原样传递。

复制代码
import io.agentscope.core.tool.mcp.McpMeta;

// 把 traceId 和 callbackUrl 放进 McpMeta
McpMeta meta = new McpMeta(Map.of(
        "traceId", "abc-123",                                  // 链路追踪 ID
        "callbackUrl", "https://hook.example.com/cb"           // 结果回调地址
));

RuntimeContext ctx = RuntimeContext.builder()
        .put(McpMeta.class, meta)                               // 注入 RuntimeContext
        .build();

// 当 agent 调 MCP 工具时,meta 自动透传给 MCP server
agent.call(List.of(new UserMessage("user", "创建 issue")), ctx).block();

当 agent 调用 MCP 工具时,traceIdcallbackUrl 会自动出现在 MCP server 收到的 meta 参数中。MCP server 可用这些 meta 做日志关联、结果推送等。

只实现了 McpMeta 接口的对象才会被提取------普通 RuntimeContext 条目不会泄露给 MCP server。

15.10 本章小结

  • MCP server 通过 stdio / sse / ws 接入 agent。
  • workspace/tools.jsonmcpServers 段声明,运行时可改。
  • Java 端 McpServerSpec 可补强:超时、env、headers。
  • MCP 工具与 Permission 系统无缝集成。