用20行代码带你实现一个mcp

335 阅读3分钟

【MCP】用20行代码带你实现一个mcp

🌸 实现很简单,但是通过这个过程你可以了解到mcp最基础的编写思路。

🌸 预告一下,下篇文章,我会讲更加通用的实现方法,怎么实现mcp的client端,怎么分别通过shell调用和普通的 sse请求拿到数据,并且会讲怎么将mcp集成进传统的ai开发中,并实现一个最小demo

最后实现的效果如下😏

mcp result.png

二、实现

2.1 依赖

首先,我们需要安装必要的依赖:

npm install @modelcontextprotocol/sdk zod

2.2 core

接下来,让我们编写我们的MCP server

核心思路如下:

  • 定义McpServer类
  • 定义环境变量读取
  • Mcp实例的返回值添加 tool,这里扩展一下resourceprompt
// server.resource(
//     "user-profile",
//     new ResourceTemplate("users://{userId}/profile", { list: undefined }),
//     async (uri, { userId }) => ({
//       contents: [{
//         uri: uri.href,
//         text: `Profile data for user ${userId}`
//       }]
//     })
//   );// server.prompt(
//   "code-review-prompt",
//   { code: z.string().describe("代码") },
//   ({ code }) => ({
//     messages: [{
//       role: "user",
//       content: {
//         type: "text",
//         text: `请对以下代码进行review,并给出修改建议,注意用二次元风格结尾加上喵:\n\n${code}`
//       }
//     }]    
//   })
// );
  • 最后完整代码如下
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// step1:初始化MCP服务器
const server = new McpServer({
    name: "代码review",
    version: "1.0.0"
});
const anime_suffix = process.env.anime_suffix ?? "喵";
console.log(anime_suffix);
// step2:定义工具
server.tool(
  "code-review",
  { code: z.string().describe("代码") },
  async ({ code }) => {
    return {
    // 这里其实相当于一个prompt约束人设
      content: [{ type: "text", text: "注意用二次元风格给我代码审查的结果,每一句话后面需要加上颜文字或者" + anime_suffix }, { type: "text", text: code }]
    };
  }
);
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP code review 服务器已启动");
}
// step3:启动服务器
main().catch((error) => {
  console.error("服务器启动失败:", error);
  process.exit(1);
});

就这样,不到20行核心代码🤏,我们的mcp就实现了!

2.3 在Cursor中配置我们的MCP服务

接下来,我们需要在Cursor中注册我们的MCP服务。创建一个.cursor/mcp.json文件:

{
  "mcpServers": {
     "mcp-code-review": {
      "command": "cmd",
      "args": [
        "/c",
        "node",
        "D:\myProject\ai-explore\packages\mcp\code-review.js"
      ],
      "env": {
        "anime_suffix": "desu"
      }
    }
  }
}

然后在Cursor设置中启用MCP功能,这样我们mcp就可以在Cursor中使用了!

三、传统Tool Call

为了更好地理解MCP的优势,让我们对比一下传统的Tool Call方式(以DeepSeek为例)

3.1 传统Tool Call方式(DeepSeek)

import { OpenAI } from 'openai';
import fs from 'fs';
​
// 模拟天气API的函数
function get_weather(location, date) {
    console.log(`获取${location}${date}的天气信息`);
    return { location, date, temperature: "25°C", condition: "晴朗", humidity: "60%" };
}
const config = {
    model: "deepseek-chat",
    query: "你好,北京明天的天气怎么样?",
    baseURL: 'https://api.deepseek.com',
    apiKey: "你的apiKey",
}
const openai = new OpenAI({
    baseURL: config.baseURL,
    apiKey: config.apiKey,
});
​
const main = async () => {
    // Function call 示例
    const functionCallResponse = await openai.chat.completions.create({
        model: config.model,
        messages: [{ role: "user", content: config.query }],
        tools: [
            {
                type: "function",
                function: {
                    name: "get_weather",
                    description: "获取指定位置的天气信息",
                    parameters: {
                        type: "object",
                        properties: {
                            location: {
                                type: "string",
                                description: "城市名称,如'北京'、'上海'等"
                            },
                            date: {
                                type: "string",
                                description: "日期,如'今天'、'明天'等"
                            }
                        },
                        required: ["location", "date"]
                    }
                }
            }
        ]
    });
    fs.writeFileSync("response.json", JSON.stringify(functionCallResponse, null, 2));
    
    // 处理function call的返回值
    if (functionCallResponse.choices && functionCallResponse.choices.length > 0) {
        for (const choice of functionCallResponse.choices) {
            if (choice.message && choice.message.tool_calls && choice.message.tool_calls.length > 0) {
                // 遍历所有工具调用
                for (const toolCall of choice.message.tool_calls) {
                    // 获取函数名称和参数
                    const functionName = toolCall.function.name;
                    const args = JSON.parse(toolCall.function.arguments);
                    console.log(`函数调用: ${functionName},参数:`, args);
                    
                    // 根据函数名称分发处理
                    let toolResponse;
                    switch (functionName) {
                        case "get_weather":
                            toolResponse = get_weather(args.location, args.date);
                            break;
                        default:
                            console.log(`未知的函数调用: ${functionName}`);
                            toolResponse = { error: `未找到函数: ${functionName}` };
                    }
                    
                    console.log(`函数调用结果:`, toolResponse);
                    
                    // 将结果发送回模型
                    const finalResponse = await openai.chat.completions.create({
                        model: config.model,
                        messages: [
                            { role: "user", content: config.query },
                            choice.message,
                            {
                                role: "tool",
                                tool_call_id: toolCall.id,
                                content: JSON.stringify(toolResponse)
                            }
                        ]
                    });
                    fs.writeFileSync(`final_response_${functionName}.json`, JSON.stringify(finalResponse, null, 2));
                }
            }
​
        }
    }
    
}
​
main();

四、其他

这篇文章,简单说明了mcp的基础demo编写。

还是可以延申出来很多东西,例如鉴权等

例如我们如果把上面的 env.anime_suffix 换一个东西, 就实现了鉴权