[AI/应用/MCP] MCP Server/Tool 开发指南

0 序

  • 本文旨在总结和分享 MCP Server 和 MCP Tool 的开发经验。
  • 总结踩坑的日志。
  • 旨在用最精简的方式剖析/展示 MCP 应用的开发过程细节。
  • 欢迎交流。

1 概述: MCP Server/Tool 开发指南

MCP Server(SSE模式) - NACOS MCP Server (Python)版

使用 Nacos MCP Wrapper Python 开发 MCP Server。

step0 前置要求

  • 安装 python / pip 环境
  • 推荐版本: python 3.10
  • 安装 python 依赖
shell 复制代码
$ pip install python-dotenv

$ pip install nacos-mcp-wrapper-python nacos-sdk-python

$ pip list | findstr nacos
nacos-mcp-wrapper-python              1.1.0
nacos-sdk-python                      3.0.4
  • nacos_mcp_wrapper 是一个 MCP (Model Context Protocol) 的 Nacos 集成库。

step1 开发并启动 Mcp Server/Tool (SSE版)

.env

  • 配置环境变量 for register_nacos_mcp_server.py
properties 复制代码
NACOS_SERVER_ADDR="127.0.0.1:8848"
NACOS_USERNAME="nacos"
NACOS_PASSWORD="nacos"

MCP_APP_SERVER_NAME="nacos-mcp-python"
MCP_APP_SERVER_PORT=18001

register_nacos_mcp_server.py

python 复制代码
import os
from dotenv import load_dotenv
from nacos_mcp_wrapper.server.nacos_mcp import NacosMCP
from nacos_mcp_wrapper.server.nacos_settings import NacosSettings
import logging

# @description 启动 Mcp Server,并注册到 NACOS
# @dependency : pip install python-dotenv nacos-mcp-wrapper-python nacos-sdk-python

# step0 前置准备
# step0.1 加载环境变量 from `.env` 文件
load_dotenv()

# step0.2 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


# step1 Create an MCP server instance
nacos_settings = NacosSettings()
# 验证所需的端口是否畅通: ssh 127.0.0.1 -p 8848 -v , ssh 127.0.0.1 -p 9848 -v
nacos_settings.SERVER_ADDR = os.getenv("NACOS_SERVER_ADDR")  # <nacos_server_addr> e.g. "127.0.0.1:8848"
nacos_settings.USERNAME = os.getenv("NACOS_USERNAME") # e.g. "nacos" or ""
nacos_settings.PASSWORD = os.getenv("NACOS_PASSWORD") # e.g. "nacos" or ""
mcp = NacosMCP(
    os.getenv("MCP_APP_SERVER_NAME")
    , nacos_settings=nacos_settings
    , version="1.0.1"
    , port=os.getenv("MCP_APP_SERVER_PORT")
)


# step2 Register some tools for the mcp server / 注册工具(装饰器方式,与官方 MCP SDK 完全一致)
## Register an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two integers together"""
    return a + b


## Register a subtraction tool
@mcp.tool()
def minus(a: int, b: int) -> int:
    """Subtract two numbers"""
    return a - b

## calculate tool
@mcp.tool()
def calculate(expression: str) -> str:
    """执行数学计算表达式"""
    return str(eval(expression))

## get weather tool
@mcp.tool()
def get_weather(city: str) -> str:
    """获取指定城市的天气"""
    return f"{city} 今天晴天,25°C"

# 运行逻辑:
#   程序启动后会注册几个工具:add / minus / calculate / get_weather / ...
#   使用 SSE 方式运行在端口 (如: 18001)(在第 9 行设置)
#   连接到 Nacos 服务注册中心 (地址:${NACOS_SERVER_ADDR}, 如: 127.0.0.1:8848)
#   服务名称为 nacos-mcp-python,版本 1.0.1
#   异常会被捕获并打印错误信息

if __name__ == "__main__":
    logger.info(f"正在连接 Nacos 服务器:{nacos_settings.SERVER_ADDR}")
    logger.info(f"用户名:{nacos_settings.USERNAME}")
    try:
        # transport/通信模式
        mcp.run(transport="sse") # sse: Server-sent events/SSE (Server-sent Events):服务器推送事件到客户端;适用于:需要实时数据推送的场景; 特点:单向通信(服务器 → 客户端),基于 HTTP 长连接
        # mcp.run(transport="stdio") # stdio :标准输入输出:通过进程的 stdin/stdout 通信; 适用于:本地进程间通信、命令行工具集成; 特点:双向通信,适合本地部署
        # mcp.run(transport="streamable-http") # streamable-http/流式 HTTP:支持流式响应的 HTTP 协议; 适用于:需要双向通信的 Web 场景; 特点:双向通信,支持更复杂的交互模式
    except Exception as e:
        error_msg = str(e)

        # 如果是 404 错误,可能是首次启动,尝试继续
        if "404" in error_msg and "not found" in error_msg:
            logger.warning("⚠ 未在 Nacos 中找到现有配置,将创建新服务...")
            # 可以选择重试或继续
        else:
            print(f"Runtime error: {e}")
            logger.error(f"Runtime error: {e}", exc_info=True)
            print(f"\n连接失败,请检查:")
            print(f"1. Nacos 服务器 {nacos_settings.SERVER_ADDR} 是否可访问")
            print(f"2. 用户名密码是否正确")
            print(f"3. 网络防火墙是否开放端口")

查验

  • mcp server 端 : 日志
log 复制代码
C:\Users\xxx\.conda\envs\ai-env\python.exe D:\Workspace\xxx\DemoPythonProject\nacos_mcp\registry_nacos_mcp.py 
2026-03-25 00:28:29,937 - INFO - 正在连接 Nacos 服务器:127.0.0.1:8848
2026-03-25 00:28:29,938 - INFO - 用户名:nacos
2026-03-25 00:28:29,953 - INFO - create new rpc client: 9aec5ecb-8ea3-4262-98c5-ac0c3247b228
2026-03-25 00:28:29,953 - INFO - init app conn labels from client config,{}
2026-03-25 00:28:29,953 - INFO - init app conn labels from env,{}
2026-03-25 00:28:29,954 - INFO - final app conn labels: {}
2026-03-25 00:28:29,954 - INFO - rpc client init label, labels : {'source': 'sdk', 'module': 'ai'}
2026-03-25 00:28:29,954 - INFO - rpc client register connection listener: AIGrpcRedoService
2026-03-25 00:28:29,955 - INFO - rpc client register server push request: ConnectResetRequest handler: ConnectResetRequestHandler
2026-03-25 00:28:29,955 - INFO - rpc client register server push request: ClientDetectionRequest handler: ClientDetectionRequestHandler
2026-03-25 00:28:29,956 - INFO - rpc client start to connect server, server: 127.0.0.1:9848
2026-03-25 00:28:30,091 - INFO - [get_access_token] AccessToken: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTc3NDM4NzcxMH0.oLQo0kzFuNDOTQlUpQXcm4QqEE-gNIYjWepdGlXs2-4, TTL: 18000, force_refresh: True
2026-03-25 00:28:30,099 - INFO - connect to server success,labels:{'source': 'sdk', 'module': 'ai'},tenant:public,connection_id:1774369710005_183.xxx.202.190_7436
2026-03-25 00:28:30,114 - INFO - receive stream server request, connection_id:1774369710005_183.xxx.202.190_7436, original info: metadata {
  type: "SetupAckRequest"
  clientIp: "172.17.0.2"
}
body {
  value: "{\"headers\":{},\"abilityTable\":{\"supportPersistentInstanceByGrpc\":true,\"fuzzyWatch\":true,\"lock\":true,\"mcp\":true},\"module\":\"internal\"}"
}

2026-03-25 00:28:30,218 - INFO - rpc client successfully connected to server:127.0.0.1:9848, connection_id:1774369710005_183.xxx.202.190_7436
2026-03-25 00:28:30,223 - INFO - rpc client notify [connected] event to listeners
2026-03-25 00:28:30,530 - ERROR - failed to invoke nacos config server : Error [404]: MCP server `nacos-mcp-python` not found in namespaceId: `public`
2026-03-25 00:28:30,531 - INFO - can not found McpServer info from nacos,nacos-mcp-python,version:1.0.1
2026-03-25 00:28:30,552 - ERROR - failed to invoke nacos config server : Error [404]: MCP server `nacos-mcp-python` not found in namespaceId: `public`
2026-03-25 00:28:30,553 - INFO - [9aec5ecb-8ea3-4262-98c5-ac0c3247b228] release mcp server: nacos-mcp-python, version 1.0.1
2026-03-25 00:28:30,890 - INFO - mcp_id:ea9e94c3-18f2-48a7-ae4e-9de982ea8718, namespace_id:public, mcp_name:nacos-mcp-python changed, mcp_server_detail_info:<class 'v2.nacos.ai.model.mcp.mcp.McpServerDetailInfo'>
2026-03-25 00:28:30,890 - INFO - Register to nacos success,nacos-mcp-python,version:1.0.1
INFO:     Started server process [28956]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:18001 (Press CTRL+C to quit)
2026-03-25 00:28:41,025 - INFO - mcp service changed: nacos-mcp-python -> {"id":"ea9e94c3-18f2-48a7-ae4e-9de982ea8718","name":"nacos-mcp-python","protocol":"mcp-sse","frontProtocol":"mcp-sse","description":"nacos-mcp-python","repository":null,"packages":null,"versionDetail":{"version":"1.0.1","release_data":null,"is_latest":true},"version":"1.0.1","remoteServerConfig":{"serviceRef":{"namespaceId":"public","groupName":"DEFAULT_GROUP","serviceName":"nacos-mcp-python::1.0.1","transportProtocol":null},"exportPath":"/sse","frontEndpointConfigList":[]},"localServerConfig":null,"enabled":true,"status":"active","capabilities":["TOOL"],"backendEndpoints":[{"protocol":null,"address":"192.168.60.18","port":18001,"path":"/sse","headers":null}],"frontendEndpoints":[],"toolSpec":{"specificationType":null,"encryptData":null,"tools":[{"name":"add","description":"Add two integers together","inputSchema":{"properties":{"a":{"title":"A","type":"integer"},"b":{"title":"B","type":"integer"}},"required":["a","b"],"title":"addArguments","type":"object"}},{"name":"minus","description":"Subtract two numbers","inputSchema":{"properties":{"a":{"title":"A","type":"integer"},"b":{"title":"B","type":"integer"}},"required":["a","b"],"title":"minusArguments","type":"object"}},{"name":"calculate","description":"执行数学计算表达式","inputSchema":{"properties":{"expression":{"title":"Expression","type":"string"}},"required":["expression"],"title":"calculateArguments","type":"object"}},{"name":"get_weather","description":"获取指定城市的天气","inputSchema":{"properties":{"city":{"title":"City","type":"string"}},"required":["city"],"title":"get_weatherArguments","type":"object"}}],"toolsMeta":{},"securitySchema":null},"allVersions":[{"version":"1.0.1","release_data":null,"is_latest":true}],"namespaceId":"public"}
2026-03-25 00:28:41,025 - INFO - mcp_id:ea9e94c3-18f2-48a7-ae4e-9de982ea8718, namespace_id:public, mcp_name:nacos-mcp-python changed, mcp_server_detail_info:<class 'v2.nacos.ai.model.mcp.mcp.McpServerDetailInfo'>
  • nacos server 端 : MCP管理 - MCP 列表 (可选步骤)

MCP Server 并不依赖 NACOS Server,只是说 NACOS Server(3.x起) 为 MCP Server 提供了 MCP 服务注册与发现的能力。

step2 客户端(curl为例): 请求 mcp server 以获得 SessionId

shell 复制代码
$ curl -N http://127.0.0.1:18001/sse
event: endpoint
data: /messages/?session_id=1deb0a8254604faf8416fc7beb540604

: ping - 2026-03-24 15:29:28.860631+00:00

...

注意:-N 参数至关重要,它告诉 curl 禁用缓冲,立即显示服务器推过来的数据。

step2 客户端(curl为例): 新建第2个窗口/进程,基于 SessionId 请求 mcp server 完成【会话初始化】操作

  • 为什么 MCP 客户端需要2个窗口?
  • MCP (Model Context Protocol) 在使用 SSE (Server-Sent Events) 传输时,采用了 "读写分离" 的模式:
  • (Read): 一个持久的 HTTP 长连接,服务器通过它源源不断地推送数据(Events)。
  • (Write): 短暂的 HTTP POST 请求,客户端通过它发送指令。
    因此,我们需要打开两个终端窗口:一个负责"听",一个负责"说"。
shell 复制代码
$ SESSION_ID=1deb0a8254604faf8416fc7beb540604
echo "${SESSION_ID}"
1deb0a8254604faf8416fc7beb540604

$ curl -X POST "http://127.0.0.1:18001/messages/?session_id=$SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": { "name": "test-client", "version": "1.0.0" }
    }
  }'

注:Body: 标准的 JSON-RPC 2.0 格式。

  • MCP 客户端 (2个窗口):
  • MCP 服务器端
  • 类似日志: "INFO: 127.0.0.1:65285 - "GET /sse HTTP/1.1" 200 OK"

    注:如果不完成此初始化操作,直接调用 tool ,将调用失败,报类似错误:
  • "WARNING - Failed to validate request: Received request before initialization was complete"
  • "data: {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid request parameters","data":""}}"

step3 客户端(curl为例): 在第2个窗口/进程,基于 SessionId 调用 mcp server 的指定 tool

shell 复制代码
$ curl -X POST "http://127.0.0.1:18001/messages/?session_id=$SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "minus",
      "arguments": {"a": 3, "b": 4}
    },
    "id": 1
  }'

注:Body: 标准的 JSON-RPC 2.0 格式。

调用其他 tool 的示范

Z FAQ/Troubleshooting for MCP

Q: 发送 POST 后没反应?

  • 检查 客户端窗口1: 结果是异步返回的,一定要看监听窗口。
  • 检查斜杠:确认 POST URL 是 /messages/?... 而不是 /messages?...。可以加 -v 参数查看是否返回了 307 Temporary Redirect。

Q: Session ID 过期?

  • SSE 连接如果断开,Session ID 就会失效。每次重新运行 curl -N .../sse 都会生成一个新的 ID,发 POST 时记得更新。

Q: 乱码或无输出?

  • 确保 curl 加上了 -N (no buffer)。
  • 确保 curl 加上了 -H "Content-Type: application/json",否则服务器可能不解析 Body。

Y 推荐文献

X 参考文献

相关推荐
数据知音5 天前
[AI应用] Spring AI 应用开发指南
ai应用-智能体/agents·ai应用编程框架-springai