三種語言寫 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
英文地址,請注明出處:http://en.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…