OpenClaw MCP 工具开发指南

凌晨2点43分,我在调试一个MCP连接。那一刻我突然明白,MCP不是协议,而是AI与世界的和解方式...

什么是 MCP?

MCP (Model Context Protocol) 是由 Anthropic 提出的开放协议,旨在标准化 AI 模型与外部数据源、工具之间的连接方式。

在 OpenClaw 中,MCP 是 Agent Skills 的核心通信机制,让 AI 能够:

  • 🔌 统一接口访问各种数据源
  • 🔒 安全地执行外部工具
  • 🔄 动态发现和调用能力
  • 📊 获取实时上下文信息

MCP 架构原理

┌─────────────────┐      MCP Protocol      ┌─────────────────┐
│   AI Agent      │ ◄────────────────────► │  MCP Server     │
│  (OpenClaw)     │   JSON-RPC over SSE    │  (Your Skill)   │
└─────────────────┘                        └─────────────────┘
                                                    │
                       ┌────────────────────────────┼────────────────────────────┐
                       ▼                            ▼                            ▼
                ┌──────────────┐           ┌──────────────┐           ┌──────────────┐
                │   Database   │           │    APIs      │           │   Files      │
                └──────────────┘           └──────────────┘           └──────────────┘
          

核心概念

概念说明示例
ResourcesAI可读取的数据源数据库记录、文件内容
ToolsAI可调用的功能发送邮件、创建任务
Prompts可复用的提示模板代码审查模板

环境搭建

1. 安装 MCP SDK

# Node.js
npm install @modelcontextprotocol/sdk

# Python
pip install mcp

2. OpenClaw MCP 配置

# ~/.openclaw/mcp.json
{
  "servers": {
    "my-database": {
      "command": "node",
      "args": ["/path/to/my-mcp-server.js"],
      "env": {
        "DB_URL": "postgresql://localhost/mydb"
      }
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "your-token"
      }
    }
  }
}

实现 MCP Server

基础 Server (Node.js)

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

// 创建 Server
const server = new Server(
  {
    name: 'my-data-server',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// 声明可用工具
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'query_data',
        description: '查询数据库数据',
        inputSchema: {
          type: 'object',
          properties: {
            table: { type: 'string', description: '表名' },
            filters: { type: 'object', description: '过滤条件' },
          },
          required: ['table'],
        },
      },
    ],
  };
});

// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'query_data') {
    const { table, filters } = request.params.arguments;
    
    // 执行查询
    const results = await queryDatabase(table, filters);
    
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(results, null, 2),
        },
      ],
    };
  }
  throw new Error(`未知工具: ${request.params.name}`);
});

// 启动 Server
const transport = new StdioServerTransport();
await server.connect(transport);

Python 版本

from mcp.server import Server
from mcp.types import TextContent
import asyncio

app = Server("my-data-server")

@app.list_tools()
async def list_tools():
    return [
        {
            "name": "analyze_csv",
            "description": "分析CSV文件",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "file_path": {"type": "string"},
                    "operations": {"type": "array"}
                },
                "required": ["file_path"]
            }
        }
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "analyze_csv":
        result = await analyze_csv_file(
            arguments["file_path"],
            arguments.get("operations", [])
        )
        return [TextContent(type="text", text=result)]
    raise ValueError(f"未知工具: {name}")

if __name__ == "__main__":
    asyncio.run(app.run())

Resources 资源暴露

让 AI 能够读取结构化数据:

// 声明资源
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: 'database://users/schema',
        name: '用户表结构',
        mimeType: 'application/json',
      },
      {
        uri: 'database://orders/recent',
        name: '最近订单',
        mimeType: 'application/json',
      },
    ],
  };
});

// 读取资源
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const uri = request.params.uri;
  
  if (uri === 'database://users/schema') {
    const schema = await getTableSchema('users');
    return {
      contents: [
        {
          uri,
          mimeType: 'application/json',
          text: JSON.stringify(schema, null, 2),
        },
      ],
    };
  }
  
  throw new Error(`未知资源: ${uri}`);
});

高级功能

进度通知

// 向客户端发送进度更新
await server.notification({
  method: 'notifications/progress',
  params: {
    progressToken: request.params.meta?.progressToken,
    progress: 50,
    total: 100,
    message: '正在处理数据...',
  },
});

Prompts 模板

server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [
      {
        name: 'code_review',
        description: '代码审查模板',
        arguments: [
          {
            name: 'code',
            description: '要审查的代码',
            required: true,
          },
        ],
      },
    ],
  };
});

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  if (request.params.name === 'code_review') {
    const code = request.params.arguments?.code;
    return {
      description: '代码审查',
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请审查以下代码:\n\n\`\`\`\n${code}\n\`\`\``,\n          },
        },
      ],
    };
  }
});

安全最佳实践

⚠️ 输入验证

  • 所有输入必须经过严格验证
  • 使用 Zod / Pydantic 进行 schema 验证
  • 防止 SQL 注入和命令注入

⚠️ 权限控制

// 限制可访问的表
const ALLOWED_TABLES = ['users', 'orders', 'products'];

async function queryDatabase(table: string) {
  if (!ALLOWED_TABLES.includes(table)) {
    throw new Error(`无权访问表: ${table}`);
  }
  // ...
}

测试 MCP Server

# 使用 MCP Inspector 测试
npx @modelcontextprotocol/inspector node ./my-server.js

# 或使用 OpenClaw CLI
openclaw mcp test my-server

# 运行集成测试
openclaw mcp test my-server --scenario full

OpenClaw 集成

// 在 Skill 中使用 MCP
import { MCPClient } from '@openclaw/mcp';

const client = new MCPClient();

export async function analyzeWithMCP(data: string) {
  // 连接到 MCP Server
  await client.connect('my-data-server');
  
  // 调用工具
  const result = await client.callTool('analyze_data', {
    input: data,
    format: 'json'
  });
  
  return result;
}

常见问题排查

问题解决方案
连接失败检查 mcp.json 路径和命令是否正确
工具不显示确认 ListToolsRequestSchema 正确实现
参数错误检查 inputSchema 与实际调用是否匹配
超时增加超时设置或优化查询性能