一、背景:AI 编辑器的能力边界在哪里?
当前主流 AI 编辑器(如 GitHub Copilot、通义灵码)虽能生成代码、解释逻辑,但无法直接操作本地文件系统或调用外部服务。例如:
- "把
public/mock
目录打包上传到 GitHub Release" - "从最新 Release 下载测试数据并解压到
assets/
"
这类需求在团队协作、跨环境调试中非常常见,但传统做法依赖手动操作或脚本,效率低且易出错。
Model Context Protocol(MCP) 正是为解决这一问题而生------它定义了一套标准协议,允许 AI 模型通过"工具调用"(Tool Calling)与外部服务安全交互。
二、MCP 是什么?为什么值得尝试?
MCP(Model Context Protocol)是一个轻量级、语言无关的协议,核心思想是:
AI 提出需求 → 工具执行操作 → 返回结构化结果
其优势包括:
- 安全隔离:工具运行在用户本地,AI 仅传递参数
- 可扩展:很多 CLI 工具均可封装为 MCP Server
- 标准化:统一输入/输出格式,便于多编辑器集成
目前,VS Code Copilot、通义灵码、Trae 等均已支持 MCP,生态正在快速成长。
三、实战目标:让 AI 自动同步开发资源
我们以一个典型场景为例:
"通过自然语言指令,将指定目录打包上传至 GitHub Release,或从 Release 下载解压"
你可以基于相同模式实现数据库备份、日志分析、API 压测等。
四、MCP 工具开发四要素
开发一个健壮的 MCP 工具,需关注以下几个核心环节:
1. 定义自己的 MCP Server
使用官方 SDK 快速搭建 Server:
ts
#!/usr/bin/env node
// src/index.ts
// 导入必要的类
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import z from "zod/v3";
import packageJson from "../package.json" with { type: "json" };
export const server = new McpServer({
name: "mcp-shipit",
title: "MCP Shipit",
version: packageJson.version,
description: "GitHub Release Upload Tool"
});
2. 定义清晰的输入 Schema
使用 zod
严格校验 AI 传入的参数,避免路径穿越或非法操作:
ts
// 工具输入定义
// src/index.ts
...
inputSchema: {
projectRootDir: z
.string()
.describe("The absolute path to the project root directory."),
targetDir: z
.string()
.describe(
"The relative path to the target directory from the project root."
)
}
...
✅ 最佳实践 :所有路径必须基于
projectRootDir
,禁止使用..
或绝对路径。
3. 注册工具到MCP Server
ts
// src/index.ts
server.registerTool(
"upload_to_github_release",
{
title: "Upload to GitHub Release",
description:
"Uploads a specified directory to a GitHub Release as a zip file.",
inputSchema: {
projectRootDir: z
.string()
.describe("The absolute path to the project root directory."),
targetDir: z
.string()
.describe(
"The relative path to the target directory from the project root."
)
}
},
async ({ projectRootDir, targetDir }) => {
// 动态导入 mcpShipit 函数
const { mcpShipit, setLoggingCallback } = await import(
"./utils/index.js"
);
// 设置日志回调函数,将日志通过 MCP 协议发送给客户端
setLoggingCallback((message, level) => {
// 将 "warn" 映射为 "warning" 以匹配 MCP SDK 的 LoggingLevel 类型
const mcpLevel = level === "warn" ? "warning" : level;
server.sendLoggingMessage({
level: mcpLevel,
data: message
});
});
try {
const { downloadUrl, zipFilename } = await mcpShipit(
projectRootDir,
targetDir
);
return {
content: [
{
type: "text",
text: `Successfully uploaded ${targetDir} to GitHub Release. \nDownload URL: \t${downloadUrl}\nZip Filename: \t${zipFilename}`
}
]
};
} catch (error: any) {
server.sendLoggingMessage({
level: "error",
data: `Failed to upload directory: ${error.message}`
});
return {
content: [
{
type: "text",
text: `Failed to upload ${targetDir} to GitHub Release. Error: ${error.message}`
}
]
};
}
}
);
4. 实现原子化、幂等的操作逻辑
以"上传目录"为例,流程需保证可重试、无副作用:
ts
// src/utils/mcp-shipit.ts
export async function mcpShipit(projectRootDir: string, targetDir: string) {
try {
logMessage(
`Starting mcpShipit process for target directory: ${targetDir}`,
"info"
);
// 步骤1: 检查环境变量
logMessage("Checking environment variables", "debug");
checkEnvironmentVariables(projectRootDir);
logMessage("Environment variables check passed", "debug");
// 步骤2: 验证目标路径
logMessage(`Validating target path: ${targetDir}`, "debug");
const resolvedPath = validateTargetPath(projectRootDir, targetDir);
logMessage("Target path validation passed", "debug");
// 步骤3: 生成文件名和路径
const nanoid = customAlphabet("1234567890abcdef", 5);
const projectRootBaseName = path.basename(projectRootDir);
const normalizedTargetDir = targetDir.replace(/[/\\]/g, "_");
const dateTime = new Date()
.toLocaleDateString("zh-CN", {
year: "2-digit",
month: "2-digit",
day: "2-digit"
})
.replace(/\//g, "");
const zipFilename = `mcp-upload-${projectRootBaseName}_${normalizedTargetDir}-${dateTime}-${nanoid()}.zip`;
logMessage(`Generated zip filename: ${zipFilename}`, "debug");
const projectTmpDir = ensureTempDirectory(projectRootDir);
const tmpZipPath = path.join(projectTmpDir, zipFilename);
logMessage(`Temporary zip path: ${tmpZipPath}`, "debug");
try {
// 步骤4: 压缩目录
logMessage(`Compressing directory: ${targetDir}`, "info");
await zipDirectory(resolvedPath, tmpZipPath);
logMessage(`Directory compression completed: ${targetDir}`, "info");
// 步骤5: 获取或创建GitHub Release
logMessage("Getting or creating GitHub Release", "info");
const release = await getOrCreateRelease();
logMessage(`GitHub Release ready: ${release.id}`, "info");
// 步骤6: 上传到GitHub Release
logMessage(`Uploading to GitHub Release: ${zipFilename}`, "info");
const downloadUrl = await uploadToRelease(
release.id,
tmpZipPath,
zipFilename
);
logMessage(
`Upload completed. Download URL: ${downloadUrl}`,
"info"
);
// 步骤7: 返回下载URL
return { downloadUrl, zipFilename };
} catch (error: any) {
logMessage(`Error during operation: ${error.message}`, "error");
throw error;
} finally {
// 步骤8: 清理临时文件
logMessage(`Cleaning up temporary file: ${tmpZipPath}`, "debug");
if (fs.existsSync(tmpZipPath)) {
fs.unlinkSync(tmpZipPath);
logMessage("Temporary file cleaned up", "debug");
}
}
} catch (error: any) {
logMessage(`mcpShipit process failed: ${error.message}`, "error");
throw error;
}
}
✅ 关键点:
- 压缩包这类临时文件放在项目
tmp/
目录下- 上传时校检文件路径
5. 安全访问外部服务(以 GitHub 为例)
通过环境变量注入敏感信息,并支持代理:
ts
// 初始化 Octokit(支持代理)
const octokit = new Octokit({
auth: process.env.SHIPIT_GITHUB_TOKEN,
request: process.env.SHIPIT_PROXY
? { fetch: createProxyFetch(process.env.SHIPIT_PROXY) }
: undefined
});
🔐 权限建议:
- 上传/管理 Release:需
repo
权限- 仅下载公有仓库:
public_repo
即可- 切勿在代码中硬编码 Token!
6. 启动服务
ts
// src/index.ts
const transport = new StdioServerTransport();
server.connect(transport);
7. 测试
官方提供了web inspector 来在网页中查看 MCP 的运行结果。
bash
npx @modelcontextprotocol/inspector
or
pnpm dlx @modelcontextprotocol/inspector
五、如何集成到 AI 编辑器 (以 VS Code Copilot 为例)
可以把代码编译成js后本地运行,或者发布到npm后使用 npx 运行。
MCP 工具通过 stdio(标准输入输出) 与编辑器通信。
json
// .vscode/mcp.json
{
"servers": {
"mcp-shipit": {
"type": "stdio",
"command": "npx",
"args": ["@your-scope/your-mcp-tool-package"],
"env": {
"SHIPIT_GITHUB_TOKEN": "your-token",
"SHIPIT_GITHUB_OWNER": "your-owner",
"SHIPIT_GITHUB_REPO": "your-repo",
"SHIPIT_PROXY": "your-proxy"
}
}
}
}
如果是本地运行,则将 command
设置为 node
,args 设置为 [path/to/your/mcp-tool.js]
。
🌐 兼容性:同一工具可无缝用于 Copilot、通义灵码、Trae 等支持 MCP 的平台。
六、使用效果:自然语言驱动自动化
配置完成后,你可以在编辑器中直接说:
"请将
public/mock
目录打包上传到 GitHub Release"
AI 会自动调用你的工具,返回下载链接。整个过程无需离开编辑器,且操作可审计、可回溯。
七、可复用的经验总结
通过本次实践,我们提炼出 MCP 工具开发的通用模式:
环节 | 关键点 |
---|---|
输入设计 | 用 zod 严格校验,限制路径范围 |
错误处理 | 返回结构化错误信息(非 throw),便于 AI 理解 |
分发方式 | 发布为 npm 包,支持 npx 即开即用 |
集成方式 | 通过 stdio 与编辑器通信,支持 VS Code、Copilot 等 |
💡 延伸思考:你还可以用 MCP 实现------
- 数据库快照备份
- 日志分析
- API 压测
八、结语
MCP 不是一个"玩具协议",而是连接 AI 与真实开发工作流的桥梁。通过封装原子化、安全的工具,我们可以让 AI 从"代码生成器"升级为"开发协作者"。
本文的完整实现参考了一个开源示例(GitHub 链接),但核心价值在于方法论------你可以基于此模式,构建属于自己的 MCP 工具生态。
📚 延伸阅读