深入MCP本质——编写自定义MCP Server并通过Cursor调用

这篇文章将深入探讨 MCP (Model Context Protocol) 的核心原理,并通过一个简单的自定义天气工具示例,揭示 AI 模型如何与本地系统进行高效、安全的交互。


实战配置:启动你的自定义 MCP Server

首先,我们通过一个简单的步骤来配置并运行一个自定义的 MCP Server,该 Server 能够获取电脑所在地的实时时间和天气(不使用代理的前提下)。

步骤 1: 代码准备

下面是我编写的 MCP Server ,你可以将代码库拉取到本地目录

github.com/Objecteee/m...

步骤 2: Cursor 配置

在 Cursor IDE 的 mcp.json 配置文件中添加如下配置,指定你的本地 Server 脚本的路径:

json 复制代码
{
  "Real Weather Tool": {
    "command": "node",
    "args": [
      "E:\project\mcp\myMCP\index.js"  # 请确保这里是你的本地文件路径
    ]
  }
}

配置完成后,您即可在 Cursor 的对话框中通过自然语言调用这个工具,例如询问:"现在的天气怎么样?"


1. 核心架构:什么是 MCP?

MCP (Model Context Protocol) 是由 Anthropic 提出的一种开放协议,旨在解决 大型语言模型 (LLM)外部世界(本地文件、数据库、API 等) 之间的信息鸿沟。

  • 没有 MCP 时: LLM 仅依赖训练数据,无法感知外部世界的实时状态(如当前时间、实时天气、本地代码结构)。它就像一个"孤岛上的智者"。
  • 有了 MCP: 我们为 LLM 提供了一个标准化的"工具插座"。任何遵循 MCP 协议的外部程序(即 MCP Server )都可以插入这个插座,使 LLM 能够 控制 这些工具并 获取 实时数据。

2. 本质原理:Cursor 如何连接并调用工具?

Cursor 连接和调用自定义 Server 的过程,本质上是基于 标准输入/输出 (Stdio)进程间通信 (IPC) ,它采用 JSON-RPC 格式进行数据交换。我们可以将整个过程分为两个阶段:

阶段一:握手(Handshake)与工具发现

  1. 启动子进程: 当你在 Cursor 中保存 mcp.json 配置后,Cursor 会在后台启动一个 子进程 (Child Process) ,执行你指定的 node index.js 脚本。
  2. 发送询问: Cursor(父进程)通过 标准输入 (stdin) 向你的脚本发送一条 JSON-RPC 格式的消息,请求获取可用的工具列表。
  3. 返回列表: 你的脚本(子进程)收到请求后,会通过 标准输出 (stdout) 返回一个包含其所有工具定义(如 TimeWeatherReporter)的 JSON 响应。这对应于代码中的 server.setRequestHandler(ListToolsRequestSchema...)

阶段二:待命与工具调用

  1. 待命: 只要 Cursor 不关闭,你的脚本进程就会在后台持续运行,等待调用指令。
  2. LLM 触发: 用户在聊天框输入指令(如"现在的天气怎么样?"),Cursor 内置的 AI 模型分析请求,决定调用哪个工具。
  3. 发送指令: Cursor 向你的脚本发送一条 JSON-RPC 调用请求,指定要执行的工具名称和参数。
  4. 执行任务: 你的脚本接收指令,执行相应的内部函数(例如请求 wttr.in 获取天气)。
  5. 反馈结果: 脚本将执行结果打包成 JSON,通过 stdout 发回给 Cursor。
  6. 结果展示: Cursor 接收到工具输出的原始数据,将其作为 上下文 传递给 AI 模型,由模型组织成自然语言的答案回复用户。

3. 代码层面的关键实现:MCP Server 运行机制 (index.js)

我们的注意力不要放在代码的实现上,要放在代码的流程上,因为MCP只是一种规范。

这个 Node.js 脚本通过遵循 MCP 规范,并利用标准输入/输出流 (Stdio) 实现通信。以下是驱动 MCP Server 运行的五个关键代码片段:

3.1 核心模块导入:定义协议与传输层

代码片段 作用描述 核心原理
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; Server 实例 MCP 服务端核心类,负责处理 JSON-RPC 请求和响应。
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; Stdio 传输层 声明通信载体是基于 标准输入/输出 (stdin/stdout) ,而非网络端口。
import { CallToolRequestSchema, ListToolsRequestSchema } from ... 协议定义 导入请求的 JSON Schema,用于解析 Cursor 发来的不同类型的指令。

3.2 初始化 MCP 服务器

vbscript 复制代码
const server = new Server(
  {
    name: "simple-weather-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

作用: 创建服务器实例。capabilities.tools: {} 明确告诉 MCP,该服务器具备提供工具的能力。

3.3 注册工具列表:响应"握手"请求

php 复制代码
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "TimeWeatherReporter",
        description: "Get current system time and weather...",
        inputSchema: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "Optional. City name (e.g. 'Beijing')...",
            },
          },
          required: [],
        },
      },
    ],
  };
});

作用 这是握手阶段(Handshake) 的关键。当 Cursor 首次启动子进程时,会发送 ListToolsRequestSchema 请求。该代码片段负责返回一个清晰的 JSON Schema 描述:

  • name: 工具的唯一标识符。
  • description: 供 AI 理解工具用途的自然语言描述。
  • inputSchema: JSON Schema 格式的参数定义,AI 依赖此信息来构造正确的调用参数。

3.4 处理工具调用:执行业务逻辑

csharp 复制代码
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "TimeWeatherReporter") {
    const location = request.params.arguments?.location;
    return await getSystemStyleWeather(location); // 调用实际的业务函数
  }
  throw new Error("Tool not found");
});

作用: 这是执行阶段(Execution) 的核心。当 Cursor 的 AI 决定调用工具时,会发送 CallToolRequestSchema 请求。这段代码根据 request.params.name 匹配到对应的业务逻辑 (getSystemStyleWeather),执行后将结果返回给 Cursor。

3.5 启动服务器:连接到 Stdio 流

javascript 复制代码
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Simple Weather MCP Server running on stdio");
}

作用: 连接点。 StdioServerTransport() 创建了传输通道,server.connect(transport) 则将服务器实例绑定到这个通道上。至此,您的 Node.js 脚本准备就绪,可以通过 stdin/stdout 与 Cursor 进程进行双向通信。


4. 总结:MCP 的本质

角色 实体 职责
雇主 (Client) Cursor IDE (父进程) 启动并监控工具,发送调用指令,接收并处理结果。
雇员 (Server) node index.js (子进程) 接收指令,执行实际任务(如 API 调用、文件读写),返回原始数据。
工作合同 (Protocol) MCP 协议 规定了握手和调用过程中 JSON 数据的结构和标准。
对讲机 (Transport) Stdio (标准输入/输出) 实现本地父进程与子进程之间即时、安全的通信。

MCP 的精妙之处在于它的解耦性安全性:Cursor 客户端无需关心你的 Server 是用 Node.js、Python 还是 Go 编写。只要你的程序符合 MCP 协议,能够通过命令行流接收和发送 JSON 数据,它就能无缝地成为 AI 的外部工具,极大地扩展了 LLM 的能力边界。


自定义MCP Server代码

javascript 复制代码
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
import { z } from "zod";

// 1. Initialize MCP Server
const server = new Server(
  {
    name: "simple-weather-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// 2. Define Tool Logic using wttr.in (No API Key required)
async function getSystemStyleWeather(location) {
  // --- Get Real Local Time (System Time) ---
  const now = new Date();
  const localTime = now.toLocaleTimeString("zh-CN", {
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    hour12: false,
  });
  const localDate = now.toLocaleDateString("zh-CN");

  // --- Determine URL ---
  // wttr.in automatically detects location by IP if no location is provided
  // format=j1 gives us a JSON response
  let url = "https://wttr.in/?format=j1";
  if (location) {
    url = `https://wttr.in/${encodeURIComponent(location)}?format=j1`;
  }

  try {
    const response = await axios.get(url);
    const data = response.data;
    
    // Parse wttr.in specific JSON structure
    const current = data.current_condition[0];
    const area = data.nearest_area[0];

    const weatherInfo = {
      location: location || area.areaName[0].value, // Use detected name if no input
      region: area.region[0].value,
      condition: current.weatherDesc[0].value,
      temperature_c: current.temp_C,
      feelslike_c: current.FeelsLikeC,
      humidity: current.humidity,
      wind_kph: current.windspeedKmph,
      data_source: "wttr.in (IP-based auto-location)"
    };

    // --- Construct Result ---
    const result = {
      status: "success",
      tool: "SystemWeatherReporter",
      system_time: `${localDate} ${localTime}`,
      weather: weatherInfo
    };

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(result, null, 2),
        },
      ],
    };

  } catch (error) {
    return {
      content: [{ type: "text", text: `Unable to fetch weather data. Please check your network connection. Error: ${error.message}` }],
      isError: true,
    };
  }
}

// 3. Register Tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "TimeWeatherReporter",
        description: "Get current system time and weather. Automatically detects location if not specified. No API key required.",
        inputSchema: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "Optional. City name (e.g. 'Beijing'). If omitted, uses auto-detection.",
            },
          },
          required: [],
        },
      },
    ],
  };
});

// 4. Handle Tool Calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "TimeWeatherReporter") {
    const location = request.params.arguments?.location;
    return await getSystemStyleWeather(location);
  }
  throw new Error("Tool not found");
});

// 5. Start Server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Simple Weather MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Server error:", error);
  process.exit(1);
});
相关推荐
1024肥宅8 小时前
JavaScript性能与优化:手写实现关键优化技术
前端·javascript·面试
一字白首8 小时前
Vue 项目实战,从注册登录到首页开发:接口封装 + 导航守卫 + 拦截器全流程
前端·javascript·vue.js
前端西瓜哥8 小时前
平面几何:如何绘制一个星形?
前端
天天扭码8 小时前
解放双手!使用Cursor+Figma MCP 高效还原响应式设计稿
前端·mcp
今天不要写bug8 小时前
基于qrcode前端实现链接转二维码的生成与下载
前端·javascript·typescript·vue
小小工匠9 小时前
LLM - MCP Powered Agent_从工具失配到架构重构的实战指南
agent·mcp·千具之痛
JIngJaneIL9 小时前
基于Java + vue干洗店预约洗衣系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
搬山境KL攻城狮9 小时前
记-SPA单页面应用Chrome自动翻译导致中文错别字问题
前端·chrome
HIT_Weston9 小时前
61、【Ubuntu】【Gitlab】拉出内网 Web 服务:Gitlab 配置审视(五)
前端·ubuntu·gitlab