这篇文章将深入探讨 MCP (Model Context Protocol) 的核心原理,并通过一个简单的自定义天气工具示例,揭示 AI 模型如何与本地系统进行高效、安全的交互。
实战配置:启动你的自定义 MCP Server
首先,我们通过一个简单的步骤来配置并运行一个自定义的 MCP Server,该 Server 能够获取电脑所在地的实时时间和天气(不使用代理的前提下)。
步骤 1: 代码准备
下面是我编写的 MCP Server ,你可以将代码库拉取到本地目录
步骤 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)与工具发现
- 启动子进程: 当你在 Cursor 中保存
mcp.json配置后,Cursor 会在后台启动一个 子进程 (Child Process) ,执行你指定的node index.js脚本。 - 发送询问: Cursor(父进程)通过 标准输入 (stdin) 向你的脚本发送一条 JSON-RPC 格式的消息,请求获取可用的工具列表。
- 返回列表: 你的脚本(子进程)收到请求后,会通过 标准输出 (stdout) 返回一个包含其所有工具定义(如
TimeWeatherReporter)的 JSON 响应。这对应于代码中的server.setRequestHandler(ListToolsRequestSchema...)。
阶段二:待命与工具调用
- 待命: 只要 Cursor 不关闭,你的脚本进程就会在后台持续运行,等待调用指令。
- LLM 触发: 用户在聊天框输入指令(如"现在的天气怎么样?"),Cursor 内置的 AI 模型分析请求,决定调用哪个工具。
- 发送指令: Cursor 向你的脚本发送一条 JSON-RPC 调用请求,指定要执行的工具名称和参数。
- 执行任务: 你的脚本接收指令,执行相应的内部函数(例如请求
wttr.in获取天气)。 - 反馈结果: 脚本将执行结果打包成 JSON,通过 stdout 发回给 Cursor。
- 结果展示: 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);
});