參考
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 成功后: