一、背景:AI 编辑器的能力边界在哪里?

当前主流 AI 编辑器(如 GitHub Copilot、通义灵码)虽能生成代码、解释逻辑,但无法直接操作本地文件系统或调用外部服务。例如:

  • “把 public/mock 目录打包上传到 GitHub Release”
  • “从最新 Release 下载测试数据并解压到 assets/

这类需求在团队协作、跨环境调试中非常常见,但传统做法依赖手动操作或脚本,效率低且易出错。

Model Context Protocol(MCP) 正是为解决这一问题而生——它定义了一套标准协议,允许 AI 模型通过“工具调用”(Tool Calling)与外部服务安全交互。


二、MCP 是什么?为什么值得尝试?

MCP(Model Context Protocol)是一个轻量级、语言无关的协议,核心思想是:

其优势包括:

  • 安全隔离:工具运行在用户本地,AI 仅传递参数
  • 可扩展:很多 CLI 工具均可封装为 MCP Server
  • 标准化:统一输入/输出格式,便于多编辑器集成

目前,VS Code Copilot、通义灵码、Trae 等均已支持 MCP,生态正在快速成长。


三、实战目标:让 AI 自动同步开发资源

我们以一个典型场景为例:

你可以基于相同模式实现数据库备份、日志分析、API 压测等。


四、MCP 工具开发四要素

开发一个健壮的 MCP 工具,需关注以下几个核心环节:

1. 定义自己的 MCP Server

使用官方 SDK 快速搭建 Server:

#!/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 传入的参数,避免路径穿越或非法操作:

// 工具输入定义
// 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."
        )
}
...

3. 注册工具到MCP Server

// 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. 实现原子化、幂等的操作逻辑

以“上传目录”为例,流程需保证可重试、无副作用

// 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;
    }
}

5. 安全访问外部服务(以 GitHub 为例)

通过环境变量注入敏感信息,并支持代理:

// 初始化 Octokit(支持代理)
const octokit = new Octokit({
    auth: process.env.SHIPIT_GITHUB_TOKEN,
    request: process.env.SHIPIT_PROXY
        ? { fetch: createProxyFetch(process.env.SHIPIT_PROXY) }
        : undefined
});

6. 启动服务

// src/index.ts
const transport = new StdioServerTransport();
server.connect(transport);

7. 测试

官方提供了web inspector 来在网页中查看 MCP 的运行结果。

npx @modelcontextprotocol/inspector
or
pnpm dlx @modelcontextprotocol/inspector

五、如何集成到 AI 编辑器 (以 VS Code Copilot 为例)

可以把代码编译成js后本地运行,或者发布到npm后使用 npx 运行。

MCP 工具通过 stdio(标准输入输出) 与编辑器通信。

// .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]


六、使用效果:自然语言驱动自动化

配置完成后,你可以在编辑器中直接说:

AI 会自动调用你的工具,返回下载链接。整个过程无需离开编辑器,且操作可审计、可回溯。


七、可复用的经验总结

通过本次实践,我们提炼出 MCP 工具开发的通用模式:

环节关键点
输入设计用 zod 严格校验,限制路径范围
错误处理返回结构化错误信息(非 throw),便于 AI 理解
分发方式发布为 npm 包,支持 npx 即开即用
集成方式通过 stdio 与编辑器通信,支持 VS Code、Copilot 等

八、结语

MCP 不是一个“玩具协议”,而是连接 AI 与真实开发工作流的桥梁。通过封装原子化、安全的工具,我们可以让 AI 从“代码生成器”升级为“开发协作者”。

本文的完整实现参考了一个开源示例(GitHub 链接),但核心价值在于方法论——你可以基于此模式,构建属于自己的 MCP 工具生态。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]