三种语言写 MCP
参考
https://zhuanlan.zhihu.com/p/1915029704936760261
https://www.5ee.net/archives/tmXJAgWz
https://github.com/modelcontextprotocol/python-sdk
https://github.com/modelcontextprotocol/typescript-sdk
https://modelcontextprotocol.io/quickstart/server
https://modelcontextprotocol.io/quickstart/server#java
什么是 MCP(Model Context Protocol)
https://modelcontextprotocol.io/introduction
LLM 使用 MCP 协议调用我们自己写的工具。
使用 Python 写一个 MCP
stdio
前置条件:
- Python3
- UV
MCP 推荐使用 UV 创建 MCP Server 项目。
https://docs.astral.sh/uv/getting-started/installation/
安装 UV:
curl -LsSf https://astral.sh/uv/install.sh | sh
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
安装完成后,使用如下命令显示 UV 的版本:
uv --version
创建一个 MCP 项目的目录 mcp_server,命令行进入这个 mcp_server 目录,使用如下命令初始化这个项目,并指定这个项目使用的 Python 版本:
uv init . -p 3.13
这里是由 uv 自己决定项目的虚拟环境,你也可以通过 uv venv you_env_name --python 3.11.5 这种方式指定创建的虚拟环境的环境名
执行后,就给你初始化好一个项目了,项目的结构如下:
.git 文件夹
:这是一个 Git 版本控制系统 的隐藏文件夹。当你在一个目录中运行 git init 或 git clone 命令时,Git 会在这里存储项目的所有版本历史、配置、分支信息等。
它的存在表明你的项目已经初始化为一个 Git 仓库,可以进行版本控制(例如,跟踪文件修改、创建分支、合并代码等)。
.gitignore
: 这是一个 Git 配置文件,用于指定哪些文件或目录应该被 Git 忽略,不被纳入版本控制。
.python-version
:这个文件通常与 **pyenv**
或其他 Python 版本管理工具相关。
main.py
:程序入口。
pyproject.toml
:这是一个在现代 Python 项目中越来越常见的配置文件,遵循 PEP 518 和 PEP 621 规范。它用于定义项目的元数据、构建系统以及依赖关系。uv
和其他现代 Python 工具(如 Rye
, Poetry
, PDM
, Hatch
)会读取这个文件来管理项目。它取代了传统的 setup.py
和 requirements.txt
在项目配置和依赖管理方面的一些功能。
安装 mcp 的 SDK:
uv add "mcp[cli]"
如果后面写代码找不到依赖,需要在 Pycharm 中设置项目的解释器为项目的 .venv 目录
编写代码
在项目的根目录下创建 main.py 文件:
from mcp.server.fastmcp import FastMCPmcp = FastMCP("Demo")@mcp.tool()
def add(a: int, b: int) -> int:"""Add two numbers"""return a + b@mcp.tool()
def get_today_weather() -> str:"""get today weather"""return "晴天"@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:"""Get a personalized greeting"""return f"Hello, {name}!"if __name__ == "__main__":mcp.run(transport='stdio')
使用如下命令运行 mcp,确保不会出现报错:
uv run main.py
使用 MCP
打开 cursor,配置文件中配置:
{"mcpServers": {"ts_mcp_server": {"name": "MCP 服务器","type": "stdio","description": "","isActive": true,"registryUrl": "","command": "uv","args": ["--directory","E:/ai-projects/python_mcp_server/","run","main.py"]}}
}
Cursor 识别成功我们的 MCP 后,我们提问,它会自发的察觉到可能需要掉用 MCP,我们点击对话中的 Run tool,它就调用我们开发的 MCP 了:
sse
编写代码
类型从 stdio 改成 sse(Server-Sent Events),这个 mcp 就使用 SSE 协议了,它的 URL 就是 server 的地址,后面加上 /sse。
还支持 streamable-http 协议,如果是 streamable-http,它的 URL 就是 server 地址加上 /mcp。
if __name__ == "__main__":mcp.run(transport='sse')
使用 mcp
cursor 中添加 mcp servers:
{"mcpServers": {"server-name": {"url": "http://localhost:3000/sse","headers": {"API_KEY": "value"}}}
}
{"mcpServers": {"server-name": {"url": "http://localhost:3000/mcp","headers": {"API_KEY": "value"}}}
}
用 ts 编写一个 MCP
stdio
编写代码
前置条件:
- node
- ts
- npm
初始化一个项目:
# 创建项目目录
mkdir ts_mcp_server
cd ts_mcp_server# 初始化npm项目
npm init -y# 安装依赖
npm install @modelcontextprotocol/sdk zod
npm install --save-dev typescript @types/node# 安装 express,streamable-http 需要使用的 http 框架
npm install express
npm install --save-dev @types/express
创建 tsconfig.json 文件:
{"compilerOptions": {"target": "ES2022","module": "NodeNext","moduleResolution": "NodeNext","esModuleInterop": true,"forceConsistentCasingInFileNames": true,"strict": true,"skipLibCheck": true,"outDir": "dist","rootDir": "src","sourceMap": true,"declaration": true,"resolveJsonModule": true},"include": ["src/**/*"],"exclude": ["node_modules", "dist"]
}
创建 src/index.ts 文件:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";// 创建 MCP 服务器
const server = new McpServer({name: "ts_mcp_server",version: "1.0.0"
});// 添加一个简单的打招呼工具
server.tool("get_today_weather",{ name: z.string().describe("get today weather") },async (params: { name: string }) => ({content: [{ type: "text", text: `晴天` }]})
);// 添加一个加法工具
server.tool("add",{a: z.number().describe("第一个数字"),b: z.number().describe("第二个数字")},async (params: { a: number, b: number }) => ({content: [{ type: "text", text: `${params.a} + ${params.b} = ${params.a + params.b}` }]})
);// 启动服务器
async function main() {const transport = new StdioServerTransport();await server.connect(transport);console.error("MCP 服务器已启动");
}main().catch((error) => {console.error("服务器启动失败:", error);process.exit(1);
});
更新 package.json:
{"name": "ts_mcp_server","version": "1.0.0","description": "ts mcp server","main": "dist/index.js","type": "module","scripts": {"build": "tsc","start": "node dist/index.js","start:http": "node dist/streamable_http.js","dev:http": "ts-node --esm src/streamable_http.ts","test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC","dependencies": {"@modelcontextprotocol/sdk": "^1.13.0","express": "^5.1.0","zod": "^3.25.67"},"devDependencies": {"@types/express": "^5.0.3","@types/node": "^24.0.3","typescript": "^5.8.3"}
}
编译项目:
npm run build
启动 mcp server:
npm run start
使用 mcp
cursor 中添加这个 mcp:
{"mcpServers": {"ts_mcp_server": {"name": "MCP 服务器","type": "stdio","description": "","isActive": true,"registryUrl": "","command": "node","args": ["E:/ai-projects/ts_mcp_server/build/index.js"]}}
}
streamable-http
编写代码
项目中的其他代码和上面的 stdio 的例子一样,只是我们把 streamable-http 这种方式的 mcp 写在一个新文件 streamabl_http.ts 文件中:
import express from "express";
import {randomUUID} from "node:crypto";
import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {StreamableHTTPServerTransport} from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {isInitializeRequest} from "@modelcontextprotocol/sdk/types.js";
import {z} from "zod";const app = express();
app.use(express.json());// 用于按会话 ID 存储传输实例的映射
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
const servers: { [sessionId: string]: McpServer } = {};// 处理客户端到服务器通信的 POST 请求
app.post('/mcp', async (req, res) => {try {// 检查现有会话 IDconst sessionId = req.headers['mcp-session-id'] as string | undefined;let transport: StreamableHTTPServerTransport;console.log('收到请求:', {method: req.body.method,isInitialize: isInitializeRequest(req.body),sessionId,headers: req.headers,body: req.body});if (sessionId && transports[sessionId]) {// 复用现有传输实例transport = transports[sessionId];console.log('使用现有会话:', sessionId);} else if (req.body.method === 'initialize') {// 新的初始化请求const newSessionId = randomUUID();console.log('创建新会话:', newSessionId);// 创建新的服务器实例const server = new McpServer({name: "ts-streamable-server",version: "1.0.0"});// 添加工具server.tool("get_today_weather",{ name: z.string().describe("获取今天的天气") },async (params: { name: string }) => ({content: [{ type: "text", text: `晴天` }]}));server.tool("add",{a: z.number().describe("第一个数字"),b: z.number().describe("第二个数字")},async (params: { a: number, b: number }) => ({content: [{ type: "text", text: `${params.a} + ${params.b} = ${params.a + params.b}` }]}));// 创建新的传输实例transport = new StreamableHTTPServerTransport({sessionIdGenerator: () => newSessionId});// 存储实例transports[newSessionId] = transport;servers[newSessionId] = server;// 当连接关闭时清理传输实例transport.onclose = () => {console.log('会话已关闭:', newSessionId);delete transports[newSessionId];delete servers[newSessionId];};// 连接到服务器await server.connect(transport);console.log('服务器已连接到传输层');// 设置响应头res.setHeader('mcp-session-id', newSessionId);} else {// 无效请求console.log('无效请求:需要初始化请求或有效的会话 ID');res.status(400).json({jsonrpc: '2.0',error: {code: -32000,message: '错误请求:需要初始化请求或有效的会话 ID',},id: null,});return;}// 处理请求await transport.handleRequest(req, res, req.body);} catch (error) {console.error('处理请求时出错:', error);res.status(500).json({jsonrpc: '2.0',error: {code: -32000,message: '服务器内部错误',},id: null,});}
});// 处理 GET 和 DELETE 请求的可复用处理函数
const handleSessionRequest = async (req: express.Request, res: express.Response) => {const sessionId = req.headers['mcp-session-id'] as string | undefined;if (!sessionId || !transports[sessionId]) {res.status(400).send('无效或缺失的会话 ID');return;}const transport = transports[sessionId];await transport.handleRequest(req, res);
};// 处理会话删除的函数
const handleDeleteSession = async (req: express.Request, res: express.Response) => {const sessionId = req.headers['mcp-session-id'] as string | undefined;if (!sessionId || !transports[sessionId]) {res.status(400).send('无效或缺失的会话 ID');return;}try {// 获取传输实例const transport = transports[sessionId];// 关闭传输连接if (transport.close) {await transport.close();}// 清理资源console.log('正在删除会话:', sessionId);delete transports[sessionId];delete servers[sessionId];res.status(200).send('会话已成功删除');} catch (error) {console.error('删除会话时出错:', error);res.status(500).send('删除会话时出错');}
};// 通过 SSE 处理服务器到客户端通知的 GET 请求
app.get('/mcp', handleSessionRequest);// 处理会话终止的 DELETE 请求
app.delete('/mcp', handleDeleteSession);app.listen(3000, () => {console.log('MCP 服务器成功启动在端口 3000');
});
编译并启动:
npm run build
npm run start:http
使用 mcp
{"mcpServers": {"ts_mcp_server_name": {"url": "http://localhost:3000/mcp"}}
}
添加成功后:
使用 Java 编写一个 MCP
stdio
编写代码
前置条件:
- java17 及以上
- SpringBoot3.3.X 及以上
- Maven
从 https://start.spring.io/ 这个 spring initializr 网站或 IDEA 中创建一个 SpringBoot 项目。
添加如下依赖:
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server</artifactId><version>1.0.0</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId></dependency>
创建一个服务 bean:
package com.example.mcpserver;import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;@Service
@SuppressWarnings("unused")
public class WeatherService {@Tool(description = "get today weather")public String getTodayWeather() {return "晴天";}@Tool(description = "add two number")public Double addTwoNumber(@ToolParam(description = "first number") Double firstNumber,@ToolParam(description = "Two-letter US state code (e.g. CA, NY)") Double secondNumber) {return firstNumber + secondNumber;}
}
在启动类中注入 bean:
package com.example.mcpserver;import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
public class McpserverApplication {public static void main(String[] args) {SpringApplication.run(McpserverApplication.class, args);}@Beanpublic ToolCallbackProvider weatherTools(WeatherService weatherService) {return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();}
}
在 idea 中打包项目或执行 maven 的打包命令:
mvn package
确保项目的根目录下生成打包后的 jar 文件。
使用 mcp
{"mcpServers": {"java_mcp_server": {"command": "C:/Users/Administrator/.jdks/corretto-17.0.13/bin/java.exe","args": ["-Dspring.ai.mcp.server.stdio=true","-jar","E:/ai-projects/java_mcp_server/target/mcpserver-0.0.1-SNAPSHOT.jar"]}}
}
这里的 command 配置的不是 java,因为电脑安装的 java 版本是 1.8,不能启动这个 jar 包,我改成了我的电脑上的 jdk17 的位置来执行这个 jar 包
sse
编写代码
在 stdio 代码的基础上,依赖中添加:
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId><version>1.0.0</version></dependency>
代码和 stdio 一致,需要将 application.yml 中修改为如下内容:
spring:ai:mcp:server:stdio: false
使用 mcp
确保项目启动。
{"mcpServers": {"java_mcp_server_name": {"url": "http://localhost:8080/sse"}}
}
cursor 添加 mcp 成功后: