超详细MCP开发指南,有手就会,开发属于自己的MCP吧。
什么是MCP
MCP官方文档:modelcontextprotocol.io/quickstart
用户视角 :MCP与传统的Chat对话服务系统不同,它可以直接处理、编辑和分析用户本地的文件,就像拥有一位能够亲自处理本地文件的"AI PC私人助理"!
技术视角 :MCP(Model Context Protocol)本质上是一个允许AI模型安全访问外部工具和数据的协议。然而,它的意义远不止于此------它是连接AI能力与现实世界的桥梁。通过MCP,AI助手可以"看到 "文件系统、"操作 "数据库以及"使用 "各种工具,这极大地扩展了AI的实际应用范围。在稳定性方面,MCP拥有官方服务和社区贡献,已经建立了标准协议、软件开发工具包(SDK)和安全框架,同时也正在形成新的软件范式。举个例子,当您想将一些指定的后缀文件放入到一个新的文件夹时,只需要输入给MCP一行指令,便可以直接帮您省略终端输入命令行的流程。
一、环境准备
step1: 检查环境
目前mcp官方提供了NodeJs 和 Python 两个语言版本的开发SDK,配套工具都很成熟。下面以Node举例。
js
# 检查环境,node版本需要>=16,建议>=20
node --version
# 如果没有node,推荐安装nvm,进行node版本管理。
# 安装 nvm (Node 版本管理器)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
Step 2:创建项目存在以下三种方法
方法一: 使用create-typescript-server 工具快速初始化一个,快速记笔记的小工具(是一个通过mcp-server 链接 local data sources 的例子)
js
# Create a new server in the directory `my-server`
npx @modelcontextprotocol/create-server my-server
# With options
npx @modelcontextprotocol/create-server my-server --name "My MCP Server" --description "A custom MCP server"
推荐使用这个方法。
方法二: 参考quickstart文档。 以下是基于node实现一个图片读取的MCP为例:
1、需要先创建文件:
js
// 创建项目的文件夹
mkdir image-processor
cd image-processor
// 初始化项目(执行完后,项目中会初始化相关的文件)
npm init -y
// 创建开发文件(MCP的逻辑在这个文件编写)
mkdir src
touch src/index.ts
2、 配置 package.json 开发MCP时需要借助于一些外部依赖库的能力进行文件读写,需要在配置package.json后,npm install
安装。
json
{
"name": "@block/image-processor",
"version": "1.0.0",
"description": "A MCP server for image",
"type": "module",
"bin": {
"image-processor": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod +x build/index.js",
"prepublishOnly": "npm run build"
},
"files": [
"build"
],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.9",
"form-data": "^4.0.1",
"fs": "^0.0.1-security",
"os": "^0.1.2",
"path": "^0.12.7",
"zod": "^3.24.1",
"zod-to-json-schema": "^3.24.1"
},
"devDependencies": {
"@types/node": "^22.10.2",
"typescript": "^5.7.2"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"mcp",
"image"
]
}
如果是基于typescript实现,还需配置tsconfig.json
json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
方法三: 从官方的server汇总仓库里fork一个你感兴趣的server,然后在上面二开
二、 开发MCP
MCP的逻辑主要在src/index.ts中实现。以图片阅读MCP为例,编写代码如下,代码逻辑在此不赘述。 关于你想尝试实现的任何功能,都可以利用AI coding完成:
js
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { zodToJsonSchema } from "zod-to-json-schema";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ToolSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import path from "path";
import fs from "fs/promises";
import os from "os";
const ToolInputSchema = ToolSchema.shape.inputSchema;
type ToolInput = z.infer<typeof ToolInputSchema>;
const args = process.argv.slice(2);
if (args.length === 0) {
console.error(
"Usage: mcp-server-filesystem <allowed-directory> [additional-directories...]"
);
process.exit(1);
}
// Normalize all paths consistently
function normalizePath(p: string): string {
return path.normalize(p);
}
function expandHome(filepath: string): string {
if (filepath.startsWith("~/") || filepath === "~") {
return path.join(os.homedir(), filepath.slice(1));
}
return filepath;
}
// Store allowed directories in normalized form
const allowedDirectories = args.map((dir) =>
normalizePath(path.resolve(expandHome(dir)))
);
// Validate that all directories exist and are accessible
await Promise.all(
args.map(async (dir) => {
try {
const stats = await fs.stat(dir);
if (!stats.isDirectory()) {
console.error(`Error: ${dir} is not a directory`);
process.exit(1);
}
} catch (error) {
console.error(`Error accessing directory ${dir}:`, error);
process.exit(1);
}
})
);
/** 读取图片的工具 */
const ReadImageArgsSchema = z.object({
path: z.string(),
});
// Create server instance
const server = new Server(
{
name: "image-processor",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "read-image",
description: "通过图片路径读取图片",
inputSchema: zodToJsonSchema(ReadImageArgsSchema) as ToolInput,
},
],
};
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "read-image") {
const parsed = ReadImageArgsSchema.safeParse(args);
if (!parsed.success) {
throw new Error(`Invalid arguments for read_file: ${parsed.error}`);
}
// 获取图片路径
const imagePath = parsed.data.path;
// 读取图片文件为 Buffer
const imageBuffer = await fs.readFile(imagePath);
// 转换为 base64
const base64String = imageBuffer.toString("base64");
// 获取文件扩展名并确定 MIME 类型
const ext = path.extname(imagePath).toLowerCase();
const mimeType =
{
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
}[ext] || "application/octet-stream";
return {
content: [
{
type: "image",
data: base64String,
mimeType: mimeType,
},
],
};
} else {
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
console.error("Error in request handler:", error);
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors
.map((e) => `${e.path.join(".")}: ${e.message}`)
.join(", ")}`
);
}
throw new Error(`Failed to fetch image`);
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Image Processor Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
三、本地验证
3.1 本地打包构建
本地开发完成后,还需要打包构建,才能接入大模型客户端使用(比如Cursor等) 由于已经在package.json
配置了打包构建的相关信息,执行npm run build
,可以在src的同级目录发现一个新的 build 文件夹,内部包含一个index.js文件。
3.2 验证
方式一: 直接将本地构建的产物接入大模型客户端。在客户端配置文件中增加本地已经构建的MCP入口文件,看是否能正常使用该MCP。(如果仅需要自己本地使用,那么到这一步就可以了,不需要发布流程)
js
// args 1为我本地项目的打包构建后的地址,args 2为用户给这个MCP提供的参数,代表提供权限的路径,使用方式与filesystem MCP一致
"image-processor": {
"command": "node",
"args": [
"/Users/ruixingshi/project/image-processor/build/index.js",
"/Users/ruixingshi/Documents"
]
}
方式二: 使用@modelcontextprotocol/inspector
连接本地开发好的MCP,在本地Server中看是否可以正常读取MCP的tool。在控制台执行命令:
js
/* 全局安装 @modelcontextprotocol/inspector */
npm install -g @modelcontextprotocol/inspector
/* 运行: */
npx @modelcontextprotocol/inspector node path/to/server/index.js args...
// 解释: path/to/server/index.js 是你的mcp工程在你终端的相对路径
// args 是需要传入的参数: 例如你写的MCP需要创建文档到你的电脑文档层 就是: /Users/xxx/Documents。 需要改为你自己电脑的路径
// 举例
npx @modelcontextprotocol/inspector /Users/xxx/project/build/index.js /Users/xxx/Documents
运行成功后会启动一个5173端口的界面,启动的本地页面http://localhost:5173
点击Connect
之后 Run Tool
,就可以进行调试了。
方式三: 在Claude桌面端中调试 目前Claude 注册需要国外手机号验证码,本文作者并未验证,后续更新。
四、发布
可以将本地代码发布为npm包,允许其他用户在大模型客户端上通过配置直接接入,无需手动下载任何代码。
js
npm publish
效果:
写一个记笔记的MCP,效果如下:
思考:
MCP这种以大模型工具为中心的交互范式,未来是有可能颠覆之前大家在各个web页面使用各种工具的范式的,我们需要积极拥抱和探索。
从前端的视角简单类比下:
Claude Desktop < ---------------> 浏览器
MCP server <---------------> 各种站点 & 工具(devtools\mep 等等)
本地数据/远端接口 <---------------> 后端接口