当前位置: 首页 > news >正文

三种语言写 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 518PEP 621 规范。它用于定义项目的元数据、构建系统以及依赖关系。uv 和其他现代 Python 工具(如 Rye, Poetry, PDM, Hatch)会读取这个文件来管理项目。它取代了传统的 setup.pyrequirements.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 成功后:

相关文章:

  • 大数据量的分页,怎么办?
  • MagnTek MT6816-ACD 一款基于各向异性磁阻(AMR)技术的磁性角度传感器 IC
  • 现代浏览器剪贴板操作指南 + 示例页面 navigator.clipboard 详解与实战
  • 1.20.1 服务器系统(windows,Rocky 和 Ubuntu )体验
  • 浅议 3D 展示技术为线上车展新体验带来的助力​
  • 【Docker基础】Docker镜像管理:docker rmi、prune详解
  • 基于Spring Boot瀚森健身房会员管理系统设计与实现【源码+文档】
  • React JSX语法
  • 基于YOLO的智能车辆检测与记录系统
  • Vue.js 粒子连线动画组件 - FlyingLines
  • 【RAG优化】深度解析开源项目MinerU:从PDF解析到多模态理解的工业级解决方案
  • 《Whisper:开启语音识别新时代的钥匙》
  • 什么是跨域问题?后端如何解决跨域问题?
  • JVM(面试)
  • 【C/C++】Gtest + GMock 简单使用示例
  • 简说MQTT
  • 【分布式理论】读确认数与写确认数:分布式一致性的核心概念
  • C# 将 Enum枚举转成List,并显示在下拉列表中
  • 如何开发HarmonyOS 5的分布式通信功能?
  • 动态ds-vnp之normal和shortcut两种方式配置案例
  • 学seo可以做网站吗/郑州做网站推广电话
  • 烂网站做竞价行吗/常见的网络营销手段
  • 万荣做网站/百度平台客服电话是多少
  • 企业画册设计图片/成都关键词优化报价
  • 云服务器可以做图片外链网站吗/网络营销策划书论文
  • php 网站授权/网络营销活动策划