用 MCP 扩展 AI 编辑器:从零开发一个自己的MCP服务

一、背景: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 工具生态。

📚 延伸阅读

相关推荐
kaizq2 小时前
AI-MCP-SQLite-SSE本地服务及CherryStudio便捷应用
python·sqlite·llm·sse·mcp·cherry studio·fastmcp
太空眼睛5 小时前
【MCP】使用SpringBoot基于Streamable-HTTP构建MCP-Server
spring boot·sse·curl·mcp·mcp-server·spring-ai·streamable
康de哥13 小时前
MCP Unity + Claude Code 配置关键步骤
unity·mcp·claude code
田井中律.15 小时前
MCP协议
mcp
通义灵码1 天前
Qoder 支持通过 DeepLink 添加 MCP Server
人工智能·github·mcp
酩酊仙人2 天前
fastmcp构建mcp server和client
python·ai·mcp
kwg1262 天前
本地搭建 OPC UA MCP 服务
python·agent·mcp
小小工匠3 天前
LLM - 从通用对话到自治智能体:Agent / Skills / MCP / RAG 三层架构实战
agent·rag·skill·mcp
小小工匠3 天前
LLM - 将业务 SOP 变成 AI 能力:用 Skill + MCP 驱动 Spring AI 应用落地不完全指南
人工智能·skill·spring ai·mcp
Esun_R3 天前
当 LLM 开始连接真实世界:MCP 的原理、通信与工程落地
node.js·openai·mcp