自定义 MCP Server:从零写一个连接器

1 阅读6分钟

前面两节我们用了乐享连接器——内置的,现成的,点启用就能用。但有些服务不在内置列表里:公司内部的 OA 系统、自建的 API 服务、第三方平台没有公开 MCP Server。这时就需要自己写一个 MCP Server。这一节手把手教你用 Python 写一个自定义连接器,把任意 API 接入 WorkBuddy。


1. 什么场景需要自定义 Server

内置连接器有 30+,覆盖了主流办公场景。但总有例外:

场景内置连接器能解决吗
查公司内部 OA 审批流❌ 没有
调自建的后台 API❌ 没有
接第三方平台(没有官方 MCP)❌ 没有
把脚本结果推送到特定渠道⚠️ 部分可以
定时抓取某个网站数据❌ 没有

这些场景的共同点:服务不在 WorkBuddy 内置列表里,但有 HTTP API。这时就需要自定义 MCP Server。


2. MCP Server 架构一览

MCP Server 本质是一个 HTTP 服务,遵循 JSON-RPC 2.0 协议:

WorkBuddy(MCP Client)
    ↓ JSON-RPC 请求
MCP Server(你写的)
    ↓ HTTP 调用
目标 API(OA/自建系统/第三方)
    ↓ JSON 响应
MCP Server
    ↓ JSON-RPC 响应
WorkBuddy → 展示给用户

一个 MCP Server 需要实现以下核心能力:

能力说明必须
工具列表(tools/list)返回 Server 提供哪些工具
工具调用(tools/call)执行具体的工具调用
资源列表(resources/list)返回可访问的资源(可选)
提示模板(prompts/list)预定义的提示词(可选)

WorkBuddy 通过这两个 JSON-RPC 接口发现和调用工具。 你只需要实现这两个接口,Server 就能工作。


3. 最简 Server:Python 实现

用一个完整的最小示例来演示。先装依赖:

pip install fastapi uvicorn

完整代码

# mcp_server.py
from fastapi import FastAPI, Request
from pydantic import BaseModel
from typing import Any, List, Dict, Optional
import uvicorn
import json

app = FastAPI()

# ========== 1. 工具定义 ==========
TOOLS = [
    {
        "name": "get_weather",
        "description": "查询指定城市的天气",
        "inputSchema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,如北京、上海"
                }
            },
            "required": ["city"]
        }
    },
    {
        "name": "send_notification",
        "description": "发送通知消息",
        "inputSchema": {
            "type": "object",
            "properties": {
                "message": {"type": "string", "description": "通知内容"},
                "channel": {"type": "string", "description": "渠道:email/sms/dingtalk"}
            },
            "required": ["message", "channel"]
        }
    }
]

# ========== 2. 工具实现 ==========
async def get_weather(city: str) -> Dict:
    """模拟天气查询"""
    weather_map = {
        "北京": {"temp": 28, "condition": "晴"},
        "上海": {"temp": 32, "condition": "多云"},
        "广州": {"type": 35, "condition": "雷阵雨"}
    }
    result = weather_map.get(city, {"temp": "未知", "condition": "未知"})
    return {"city": city, "weather": result}

async def send_notification(message: str, channel: str) -> Dict:
    """模拟发送通知"""
    return {
        "status": "success",
        "channel": channel,
        "message": message,
        "timestamp": "2026-07-13T10:00:00Z"
    }

# ========== 3. JSON-RPC 入口 ==========
@app.post("/mcp")
async def mcp_endpoint(request: Request):
    """MCP 协议入口"""
    body = await request.json()

    method = body.get("method")
    params = body.get("params", {})
    request_id = body.get("id")

    # 工具列表
    if method == "tools/list":
        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "result": {"tools": TOOLS}
        }

    # 工具调用
    elif method == "tools/call":
        tool_name = params.get("name")
        tool_args = params.get("arguments", {})

        if tool_name == "get_weather":
            result = await get_weather(**tool_args)
        elif tool_name == "send_notification":
            result = await send_notification(**tool_args)
        else:
            result = {"error": f"Unknown tool: {tool_name}"}

        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "result": {
                "content": [
                    {
                        "type": "text",
                        "text": json.dumps(result, ensure_ascii=False)
                    }
                ]
            }
        }

    return {"jsonrpc": "2.0", "id": request_id, "error": "Method not found"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8080)

运行 Server

python mcp_server.py
# Server 启动在 http://localhost:8080/mcp

关键点解释

部分作用
TOOLS 列表声明 Server 提供哪些工具,供 WorkBuddy 发现
inputSchema定义每个工具的参数,AI 根据这个知道怎么调用
tools/listWorkBuddy 启动时调用,获取工具列表
tools/call用户请求时调用,执行具体逻辑
返回格式content[].text 必须是 JSON 字符串,AI 会解析

4. 配置到 WorkBuddy

Server 写好了,怎么让 WorkBuddy 发现它?修改 ~/.workbuddy/mcp.json

{
  "mcpServers": {
    "my-weather-server": {
      "command": "python",
      "args": ["D:/path/to/mcp_server.py"],
      "url": "http://localhost:8080/mcp"
    }
  }
}

两种启动方式:

方式说明适用场景
command + argsWorkBuddy 帮你启动进程本地 Server
urlServer 自己运行,WorkBuddy 通过 HTTP 调用远程 Server / 已运行的进程

配置完成后:

  1. 重启 WorkBuddy(或刷新连接器)
  2. 到"连接器管理"页,找到 my-weather-server
  3. 点击"Trust"信任这个自定义 Server
  4. 开始使用:
问:"北京今天天气怎么样?"
WorkBuddy 自动调用 get_weather 工具,返回:
  {"city": "北京", "weather": {"temp": 28, "condition": "晴"}}

5. 进阶:带认证的 Server

生产环境不能裸奔,加一层认证:

# 带 API Key 认证的版本
from fastapi import Header, HTTPException

async def mcp_endpoint(
    request: Request,
    authorization: Optional[str] = Header(None)
):
    # 验证 API Key
    if authorization != "Bearer YOUR_API_KEY":
        raise HTTPException(status_code=401, detail="Unauthorized")

    # ... 原有逻辑 ...

配置时也加上 Key:

{
  "mcpServers": {
    "my-secure-server": {
      "command": "python",
      "args": ["mcp_server.py"],
      "env": {
        "API_KEY": "your-secret-key"
      }
    }
  }
}

安全建议:

  • 不要把 Key 写死在代码里,用环境变量
  • 生产用 HTTPS
  • 限制调用频率防滥用

6. 进阶:调用真实 API

上面的示例是模拟的。实战中通常是调真实 API:

import httpx

async def query_oa_approval(approval_id: str) -> Dict:
    """查询 OA 审批单"""
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://oa.company.com/api/approvals/{approval_id}",
            headers={"Authorization": "Bearer YOUR_TOKEN"},
            timeout=10
        )
        return response.json()

# 在 TOOLS 中注册
{
    "name": "query_oa_approval",
    "description": "查询公司 OA 审批单状态",
    "inputSchema": {
        "type": "object",
        "properties": {
            "approval_id": {
                "type": "string",
                "description": "审批单号"
            }
        },
        "required": ["approval_id"]
    }
}

调真实 API 的要点:

  1. httpxaiohttp 保持异步
  2. 超时设置(timeout=10)防止卡死
  3. 错误处理(API 可能返回 400/500)
  4. 日志记录(方便排查问题)

7. Node.js 实现(可选)

如果你更熟 Node.js,也可以用 TypeScript 写:

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

const server = new Server({
  name: "my-custom-server",
  version: "1.0.0"
}, {
  capabilities: { tools: {} }
});

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [{
      name: "my_tool",
      description: "My custom tool",
      inputSchema: {
        type: "object",
        properties: {
          param: { type: "string", description: "A parameter" }
        },
        required: ["param"]
      }
    }]
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  // 实现你的逻辑
  return { content: [{ type: "text", text: "result" }] };
});

const transport = new StdioServerTransport();
await server.connect(transport);

Node.js 方式:通过 stdio 通信,适合本地进程。Python 方式:通过 HTTP,更灵活。选你熟悉的。


8. 调试技巧

写 MCP Server 免不了调试,几个有用技巧:

本地测试

# 直接发 JSON-RPC 请求测试
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list"
  }'

WorkBuddy 调试

  1. 打开 WorkBuddy 的开发者工具(设置 → 高级 → 开发者模式)
  2. 调用工具时,底部面板会显示:
    • 调了哪个工具
    • 传了什么参数
    • 返回了什么

常见错误

错误原因解决
404Server 没启动或端口错检查 Server 是否运行
401认证失败检查 API Key
工具不显示没有 Trust到连接器管理页 Trust
调用超时Server 逻辑太慢加超时设置,加日志

小结

学会的要点
自定义 Server 场景内置没有的 API、服务
MCP Server 架构JSON-RPC 协议,tools/list + tools/call
Python 最小实现FastAPI + uvicorn,50 行代码跑起来
配置到 WorkBuddymcp.json + Trust 机制
认证与安全API Key + 环境变量 + HTTPS
调真实 APIhttpx 异步调用 + 错误处理
调试技巧curl 测试 + WorkBuddy 调试面板

技术路线

1节:理解 MCP,启用第一个连接器 ✅
    ↓
第2节:乐享连接器深度实战 ✅
    ↓
第3节(本文):自定义 MCP Server ✅
    ↓
第4节:MCP + Skill 组合技(浏览器 + API 双通道)
    ↓
第5节:消息通知全集成(飞书/钉钉/企业微信)
    ↓
第6节:综合实战(日报自动生成系统)

📺 系列文章:

基础篇(Skill 开发)

连接器篇(MCP 实战)