三种语言写 MCP

发布于:2025-06-23 ⋅ 阅读:(14) ⋅ 点赞:(0)

参考

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 FastMCP

mcp = 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 {
    // 检查现有会话 ID
    const 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);
    }

    @Bean
    public 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 成功后:


网站公告

今日签到

点亮在社区的每一天
去签到