案例 02: 集成 MCP Server

学会通过 MCP 协议扩展 Agent 的工具能力。


场景

你的项目需要访问外部系统(如数据库、API、文件服务器),你想让 Agent 能够:

  • ✅ 查询 PostgreSQL 数据库
  • ✅ 读取远程文件系统
  • ✅ 调用第三方 API

通过 MCP (Model Context Protocol),你可以轻松实现这些功能。


目标

学完本案例后,你将能够:

  • ✅ 理解 MCP 协议的基本概念
  • ✅ 配置现有的 MCP Server
  • ✅ 开发自定义 MCP Server
  • ✅ 在 Agent 中使用 MCP 工具

前置知识

预计时间:30 分钟


步骤 1: 使用现有 MCP Server

1.1 安装 OpenCode MCP Filesystem Server

# 安装官方文件系统 MCP Server
npm install -g @modelcontextprotocol/server-filesystem

1.2 配置 opencode.json

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/path/to/allowed/directory"
      ],
      "env": {
        "ALLOWED_DIRECTORIES": "/path/to/allowed/directory"
      }
    }
  }
}

1.3 测试 MCP Server

opencode run
 
# Agent 现在可以使用文件系统工具了
> 读取 /path/to/allowed/directory/config.json

步骤 2: 配置 PostgreSQL MCP Server

2.1 安装 PostgreSQL MCP Server

npm install -g @modelcontextprotocol/server-postgres

2.2 配置数据库连接

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://user:password@localhost:5432/mydb"
      ],
      "env": {
        "DATABASE_URL": "postgresql://user:password@localhost:5432/mydb"
      }
    }
  }
}

2.3 测试数据库查询

opencode run
 
> 查询用户表的前10条记录

Agent 会自动调用 MCP 提供的数据库工具。


步骤 3: 开发自定义 MCP Server

3.1 初始化项目

mkdir my-mcp-server
cd my-mcp-server
npm init -y
 
npm install @modelcontextprotocol/sdk zod

3.2 创建 Server

index.ts:

#!/usr/bin/env node
 
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js"
import { z } from "zod"
 
// 创建 Server 实例
const server = new Server(
  {
    name: "my-mcp-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
)
 
// 定义工具
const tools = [
  {
    name: "calculate",
    description: "执行数学计算",
    inputSchema: {
      type: "object",
      properties: {
        expression: {
          type: "string",
          description: "数学表达式,例如:2 + 3 * 4",
        },
      },
      required: ["expression"],
    },
  },
]
 
// 列出工具
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools,
}))
 
// 执行工具
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params
 
  if (name === "calculate") {
    try {
      // 简单的数学计算(生产环境应使用更安全的库)
      const expression = args.expression as string
      const result = eval(expression) // 注意:生产环境不要使用 eval
 
      return {
        content: [
          {
            type: "text",
            text: `${expression} = ${result}`,
          },
        ],
      }
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `计算错误: ${error}`,
          },
        ],
        isError: true,
      }
    }
  }
 
  return {
    content: [
      {
        type: "text",
        text: `未知工具: ${name}`,
      },
    ],
    isError: true,
  }
})
 
// 启动 Server
async function main() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error("My MCP Server running on stdio")
}
 
main().catch(console.error)

3.3 配置 opencode.json

{
  "mcpServers": {
    "calculator": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/index.ts"],
      "cwd": "/path/to/my-mcp-server"
    }
  }
}

3.4 测试自定义 Server

opencode run
 
> 计算 2 + 3 * 4

步骤 4: 高级 MCP Server 功能

4.1 添加资源 (Resources)

资源是 MCP Server 提供的数据。

import {
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js"
 
// 定义资源
const resources = [
  {
    uri: "config://app",
    name: "应用配置",
    description: "读取应用配置",
    mimeType: "application/json",
  },
]
 
// 列出资源
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources,
}))
 
// 读取资源
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params
 
  if (uri === "config://app") {
    const config = {
      appName: "MyApp",
      version: "1.0.0",
      features: ["feature1", "feature2"],
    }
 
    return {
      contents: [
        {
          uri,
          mimeType: "application/json",
          text: JSON.stringify(config, null, 2),
        },
      ],
    }
  }
 
  throw new Error(`未知资源: ${uri}`)
})

4.2 添加提示 (Prompts)

提示是预定义的 Prompt 模板。

import {
  ListPromptsRequestSchema,
  GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js"
 
// 定义提示
const prompts = [
  {
    name: "analyze-config",
    description: "分析应用配置",
    arguments: [
      {
        name: "config",
        description: "配置 JSON",
        required: true,
      },
    ],
  },
]
 
// 列出提示
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
  prompts,
}))
 
// 获取提示
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  const { name, arguments: args } = request.params
 
  if (name === "analyze-config") {
    const config = args.config as string
 
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `请分析以下应用配置:\n\n${config}\n\n提供改进建议。`,
          },
        },
      ],
    }
  }
 
  throw new Error(`未知提示: ${name}`)
})

完整代码

MCP Server (index.ts)

#!/usr/bin/env node
 
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
  ListPromptsRequestSchema,
  GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js"
 
const server = new Server(
  {
    name: "my-mcp-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
      resources: {},
      prompts: {},
    },
  }
)
 
// 工具
const tools = [
  {
    name: "calculate",
    description: "执行数学计算",
    inputSchema: {
      type: "object",
      properties: {
        expression: {
          type: "string",
          description: "数学表达式",
        },
      },
      required: ["expression"],
    },
  },
]
 
// 资源
const resources = [
  {
    uri: "config://app",
    name: "应用配置",
    description: "读取应用配置",
    mimeType: "application/json",
  },
]
 
// 提示
const prompts = [
  {
    name: "analyze-config",
    description: "分析应用配置",
    arguments: [
      {
        name: "config",
        description: "配置 JSON",
        required: true,
      },
    ],
  },
]
 
// 处理请求
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }))
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources }))
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts }))
 
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params
 
  if (name === "calculate") {
    try {
      const result = eval(args.expression as string)
      return {
        content: [{ type: "text", text: `${args.expression} = ${result}` }],
      }
    } catch (error) {
      return {
        content: [{ type: "text", text: `计算错误: ${error}` }],
        isError: true,
      }
    }
  }
 
  return {
    content: [{ type: "text", text: `未知工具: ${name}` }],
    isError: true,
  }
})
 
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params
 
  if (uri === "config://app") {
    return {
      contents: [{
        uri,
        mimeType: "application/json",
        text: JSON.stringify({ appName: "MyApp", version: "1.0.0" }, null, 2),
      }],
    }
  }
 
  throw new Error(`未知资源: ${uri}`)
})
 
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  const { name, arguments: args } = request.params
 
  if (name === "analyze-config") {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `请分析以下配置:\n\n${args.config}\n\n提供改进建议。`,
          },
        },
      ],
    }
  }
 
  throw new Error(`未知提示: ${name}`)
})
 
async function main() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error("My MCP Server running on stdio")
}
 
main().catch(console.error)

opencode.json

{
  "mcpServers": {
    "calculator": {
      "command": "node",
      "args": ["index.ts"],
      "cwd": "/path/to/my-mcp-server"
    }
  }
}

原理解析

MCP 架构

通信协议

MCP Server 通过 stdio 与 OpenCode 通信:

[OpenCode] --stdin--> [MCP Server]
[OpenCode] <--stdout-- [MCP Server]
[OpenCode] <--stderr-- [MCP Server - 日志]

工具执行流程


扩展阅读

相关文档

其他案例


💡 最佳实践

1. 安全性

  • ✅ 验证所有输入参数
  • ✅ 限制资源访问权限
  • ❌ 不要在生产环境使用 eval()

2. 错误处理

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    // 执行工具逻辑
  } catch (error) {
    return {
      content: [{
        type: "text",
        text: `工具执行错误: ${error instanceof Error ? error.message : String(error)}`
      }],
      isError: true,
    }
  }
})

3. 日志记录

console.error("Tool called:", request.params.name)
console.error("Arguments:", JSON.stringify(request.params.arguments))

4. 性能优化

  • 缓存频繁访问的资源
  • 使用流式处理大文件
  • 限制返回结果的大小

🎯 知识检查点

完成本案例后,检查你是否能回答以下问题:

  • MCP 协议的作用是什么?
  • 如何配置现有的 MCP Server?
  • 如何开发自定义 MCP Server?
  • Tools、Resources、Prompts 的区别是什么?
  • MCP Server 如何与 OpenCode 通信?

如果都能回答,恭喜你掌握了 MCP 集成! 🎉


准备好学习下一个案例了? 👉 案例 03: 调试会话问题