三种语言写 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 成功后:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/85781.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/85781.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python训练营-Day38-Dataset和Dataloader类

在遇到大规模数据集时&#xff0c;显存常常无法一次性存储所有数据&#xff0c;所以需要使用分批训练的方法。为此&#xff0c;PyTorch提供了DataLoader类&#xff0c;该类可以自动将数据集切分为多个批次batch&#xff0c;并支持多线程加载数据。此外&#xff0c;还存在Datase…

SVN上传代码

SVN&#xff08;Subversion&#xff09;是一个常用的版本控制系统&#xff0c;提供了对代码管理和协作的支持。以下是SVN常见操作&#xff08;如获取代码、上传代码、合并冲突处理等&#xff09;的命令行流程及实例&#xff1a; 1. 获取代码&#xff08;Checkout&#xff09; 在…

【appium】2.初始连接脚本配置

连接配置 from appium import webdriver desired_caps {platformName: Android,automationName: UIAutomator2,deviceName: ZTEB880,appPackage: com.taobao.taobao,appActivity: com.taobao.tao.welcome.Welcome,noReset: True }driver webdriver.Remote(http://localhost:…

FliTik翻页时钟v1.1.25.36,支持安卓TV/手机/车机+windows电脑端

FliTik翻页时钟v1.1.25.36&#xff0c;支持安卓TV/手机/车机windows电脑端 FliTik翻页时钟是一款集高颜值与强大功能于一身的全平台数字时钟工具类应用&#xff0c;支持TV、iOS、安卓、PC以及鸿蒙系统。它不仅拥有精美的翻页动画…

以AI赋能创意未来:即梦3.0与Seedance1.0Lite重磅登陆POE!

近年来&#xff0c;随着人工智能技术的不断突破&#xff0c;AI模型的应用场景也在逐渐拓宽。在这一过程中&#xff0c;如何整合和利用现有的AI技术&#xff0c;实现更为便捷的创作服务&#xff0c;成为了许多科技企业关注的焦点。近日&#xff0c;全球知名的AI模型整合平台POE正…

云计算与5G:如何利用5G网络优化云平台的性能

想象一下&#xff0c;你正在用手机看视频、进行在线游戏&#xff0c;甚至是使用云存储来保存你的重要文件。所有这些背后&#xff0c;其实都离不开一个无形的力量——云计算。而今天&#xff0c;5G网络的出现&#xff0c;就像为这些云服务加装了一对翅膀&#xff0c;让它们飞得…

GPT-1 与 BERT 架构

GPT-1 架构特点 仅解码器架构&#xff1a;摈弃了传统transformer中的编码器层&#xff0c;而是直接用解码器和自注意力&#xff0c;同时抛弃掉了交叉多头注意力层&#xff0c;自注意力通过mask来完成计算。注意力块添加到12个&#xff0c;Attention的输出维数为762维&#xff0…

Day04_C语言基础数据结构重点复习笔记20250618

1.什么是计算机的大小端存储&#xff1f; 答&#xff1a;大端是数据的高位字节存储在低地址&#xff0c;低位字节存储在高地址&#xff0c;网络协议&#xff08;如TCP/IP&#xff09;通常采用大端序&#xff08;称为“网络字节序”&#xff09;。例如&#xff1a;32位整数 0x12…

基于OpenSSL实现AES-CBC 128算法的 SeedKey DLL的生成与使用

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

进程间通信、线程间通信

进程间通信、线程间通信 进程间通行&#xff08;Inter-Process Communication&#xff0c; IPC&#xff09;和线程间通信&#xff08;Thread Communication&#xff09;的方式不完全相同&#xff0c;因为进程和线程的运行环境和特性不同 进程和线程的本质区别 进程 进程是操…

【FPGA学习】FPGA入门学习即数字逻辑复习

前言&#xff1a;最近开始学习FPGA了&#xff0c;希望通过博客记录下每一次学习的过程&#xff0c;与大家共勉。 目录 一、组合逻辑电路的设计&#xff08;工程学习引入&#xff09; 二、3-8译码器设计、下载和功能演示&#xff08;在的8段数码管显示&#xff09; 2.1 Logs…

ffmpeg python rgba图片合成 4444格式mov视频,保留透明通道

def convert_pngs_to_mov(input_pattern, output_path, frame_rate30):"""将BGRA四通道PNG序列转换为ProRes 4444编码的MOV视频&#xff08;保留透明通道&#xff09;参数:input_pattern: PNG序列路径模式&#xff08;如&#xff1a;"/path/to/frames/fram…

Java 实现 PDF 转图片功能:实战教程 + 场景解析

作者:云起川南|专注 Java 实战与自动化集成 在 PDF 文档处理的各类业务场景中,“将 PDF 页面转为图片”是一个高频、刚需的功能,广泛应用于 预览展示、文件归档、图片加密水印、OCR 文本识别 等系统中。 本文将带你一步一步实战如何使用 Java 实现 PDF 转图片 功能,使用开…

面试题-有个对象key全部是string,值全部是number要定义他,不使用interface和type如何定义

在 TypeScript 里&#xff0c;若要定义一个键为string类型、值为number类型的对象&#xff0c;并且不使用interface和type&#xff0c;可以采用以下几种方式&#xff1a; 1. 内联类型注解&#xff08;Inline Type Annotation&#xff09; 直接在变量声明时使用索引签名进行类…

领域驱动设计(DDD)【3】之事件风暴

文章目录 说明一 事件风暴理论知识1.1 事件风暴的核心目标1.2事件风暴的关键步骤1.2.1 准备工作1.2.2 核心流程1.2.3 事件风暴的输出 1.3 事件风暴的优势1.4 常见问题Q1&#xff1a;事件风暴适合所有项目吗&#xff1f;Q2&#xff1a;事件风暴后如何落地&#xff1f;Q3&#xf…

Vue3中监听 Ref 类型的数字数组

在 Vue 3 中&#xff0c;监听一个 Ref 类型的数字数组&#xff08;如 ref<number[]>([])&#xff09;时&#xff0c;根据需求的不同&#xff0c;有几种监听方式&#xff1a; 1. 监听整个数组的引用变化 当整个数组被重新赋值时触发&#xff1a; typescript 复制 下载…

PoolThreadCache 类的结构和源码实现

PoolThreadCache 在 Netty 的内存池中扮演着线程本地缓存的角色。它的主要目的是减少线程在分配内存时对全局 PoolArena 的竞争&#xff0c;通过缓存一部分最近释放的内存块&#xff0c;使得同一线程后续申请相同规格的内存时能够快速获取&#xff0c;从而提高分配效率。 下面…

Linux中的阻塞信号与信号原理

在Linux操作系统中&#xff0c;信号&#xff08;Signal&#xff09;是进程间通信和进程控制的核心机制之一。信号是一种异步通知机制&#xff0c;可以向进程发送异步事件通知&#xff0c;以便进程能够处理系统级别的事件。本文将详细探讨Linux中的信号原理&#xff0c;重点讲解…

QT学习教程(三十五)

事件处理&#xff08;- Event Processingn&#xff09; 事件是视窗系统或者Qt 本身在各种不同的情况下产生的。当用户点击或者释放鼠标&#xff0c;键盘时&#xff0c;一个鼠标事件或者键盘事件就产生了。当窗口第一次显示时&#xff0c;一个绘制事件会产生告诉新可见的窗口绘…

【Dify 案例】【MCP实战】【三】【超级美食家】

接上次的超级助理,我们这一期给出一个超级美食家 首先:我的MCP要申请一个key ` 我们来看看这个MCP服务怎么使用呢。`https://modelscope.cn/mcp/servers/@worryzyy/howtocook-mcp插件里面需要配置 {"mcpServers":{"amap-amap-sse":{"url":&qu…