如何实现一个MCP服务器

项目结构

bash 复制代码
src/
├── index.ts             # 入口文件
├── server.ts            # 服务器启动逻辑
├── common/              # 公共模块
│   └── index.ts         # 配置和工具函数
├── tools/               # MCP工具实现
│   ├── index.ts         # 工具注册
│   └── manage/          # 业务功能
│       ├── upload-rule.ts
│       └── download.ts
└── utils/               # 工具函数
    ├── common.ts
    ├── uploadCommon.ts
    └── workspaceScanner.ts

核心依赖

json 复制代码
{
  "@modelcontextprotocol/sdk": "^1.10.2",
  "axios": "^1.10.0",
  "form-data": "^4.0.4"
}

1. 项目初始化

package.json配置

json 复制代码
{
  "name": "@company/mcp-server-demo",
  "type": "module",
  "main": "./dist/index.js",
  "bin": {
    "mcp-server-demo": "./dist/index.js"
  }
}

tsconfig.json配置

json 复制代码
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext", 
    "moduleResolution": "node",
    "outDir": "./dist",
    "esModuleInterop": true
  }
}

2. 入口文件实现

src/index.ts

typescript 复制代码
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { startServer } from "./server.js";
import { registerBusinessTool } from "./tools/index.js";

async function main() {
    const server = new Server({
        name: packageInfo.name,
        version: packageInfo.version,
    }, {
        capabilities: {
            tools: {
                listChanged: true,
            },
        },
    });

    // 注册工具
    registerBusinessTool(server);
    
    // 启动服务
    await startServer(server);
}

main();

3. 服务器启动逻辑

src/server.ts

typescript 复制代码
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";

async function startStdioMode(server: Server) {
    const transport = new StdioServerTransport();
    await server.connect(transport);
}

export async function startServer(server: Server) {
    try {
        await startStdioMode(server);
    } catch (error) {
        process.exit(1);
    }
}

4. 工具注册与实现

src/tools/index.ts

typescript 复制代码
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";

export function registerBusinessTool(server: Server) {
    // 注册工具列表
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
        tools: [
            UPLOAD_RULE_TOOL,
            DOWNLOAD_PROJECT_RULES_TOOL,
        ],
    }));

    // 注册工具调用处理器
    server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
        switch (request.params.name) {
            case "upload_rule":
                return await handleUploadRule(request);
            case "get_download_rules":
                return await handleDownloadRules(request);
            default:
                return {
                    content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
                    isError: true
                };
        }
    });
}

5. 工具定义

上传工具定义

typescript 复制代码
export const UPLOAD_RULE_TOOL = {
    name: 'upload',
    description: '上传文件',
    inputSchema: {
        type: 'object',
        properties: {
            filePaths: {
                type: 'array',
                items: { type: 'string' },
                description: '要上传的文件路径列表(必须是绝对路径)'
            },
            teamSelection: {
                type: 'string',
                description: '团队选择,如:frontend、backend、testing等'
            },
            ruleTypeName: {
                type: 'string',
                enum: ['requirement_rules', 'project_rules', 'common_rules'],
                description: '规则类型'
            },
        },
        required: ['filePaths', 'teamSelection', 'ruleTypeName']
    }
};

下载工具定义

typescript 复制代码
export const DOWNLOAD_PROJECT_RULES_TOOL = {
    name: "get_download_rules",
    description: '下载文件',
    inputSchema: {
        type: "object",
        properties: {
            projectNameList: {
                type: "array",
                description: "项目名集合",
                items: { type: "string" }
            },
            workspaceProjectList: {
                type: "array", 
                description: "工作区项目名集合",
                items: { type: "string" }
            }
        },
        required: ["projectNameList", "workspaceProjectList"]
    },
};

6. 业务逻辑实现

上传功能核心代码

typescript 复制代码
export async function handleUploadRule(request: any, filePaths: string[], teamSelection: string, ruleTypeName: string) {
    try {
        // 1. 验证文件路径
        const invalidPaths = filePaths.filter(path => !isAbsolute(path));
        if (invalidPaths.length > 0) {
            return createErrorResponse("文件路径不是绝对路径", invalidPaths.join(', '));
        }

        // 2. 读取文件内容
        const formData = new FormData();
        for (const filePath of filePaths) {
            const content = await readFile(filePath, 'utf-8');
            const fileName = basename(filePath);
            formData.append('file', Buffer.from(content), {
                filename: fileName,
                contentType: getMimeType(extname(filePath))
            });
        }

        // 3. 添加其他参数
        formData.append('team', teamId);
        formData.append('ruleTypeId', ruleTypeId);
        formData.append('creator', getCurrentUserEmail());

        // 4. 发送请求
        const response = await axios.post(`${baseUrl}/api/rules/upload`, formData, {
            headers: { 'Content-Type': 'multipart/form-data' },
            timeout: 30000
        });

        return {
            content: [{ type: "text", text: "上传成功!" }],
            isError: false
        };
    } catch (error) {
        return createErrorResponse("上传失败", error.message);
    }
}

下载功能核心代码

typescript 复制代码
export async function handleDownloadRules(request: any, projectNameList: string[], workspaceProjectList: string[]) {
    try {
        // 1. 构建请求参数
        const params = new URLSearchParams();
        projectNameList.forEach(name => params.append('projectNameList', name));

        // 2. 发起下载请求
        const response = await fetch(`${baseUrl}/api/rules/download?${params}`);
        const data = await response.json();

        // 3. 保存文件到本地
        const savedFiles: string[] = [];
        for (const fileInfo of data.files) {
            const targetPath = await determineFilePath(fileInfo.filePath, projectNameList);
            await ensureDirectoryExists(dirname(targetPath));
            await writeFile(targetPath, fileInfo.content, 'utf-8');
            savedFiles.push(targetPath);
        }

        return {
            content: [{
                type: "text",
                text: `下载完成!保存了 ${savedFiles.length} 个文件`
            }],
            isError: false
        };
    } catch (error) {
        return createErrorResponse("下载失败", error.message);
    }
}

7. 工具函数

错误处理

typescript 复制代码
export function createErrorResponse(title: string, message: string) {
    return {
        content: [{
            type: "text",
            text: `❌ **${title}**\n\n${message}`
        }],
        isError: true
    };
}

文件路径处理

typescript 复制代码
export async function determineFilePath(remotePath: string, projectList: string[]): Promise<string> {
    const hasCursorRules = remotePath.includes('.cursor/rules');
    
    if (hasCursorRules) {
        // 保存到 .cursor/rules 目录
        const projectName = extractProjectName(remotePath);
        const projectPath = await findProjectPath(projectName, projectList);
        return join(projectPath, '.cursor', 'rules', getRelativePath(remotePath));
    } else {
        // 保存到 ProjectRules 目录
        const parentDir = dirname(process.cwd());
        return join(parentDir, 'ProjectRules', remotePath);
    }
}

8. 构建和部署

构建脚本

json 复制代码
{
  "scripts": {
    "build": "tsc && chmod 755 dist/index.js",
    "dev": "NODE_OPTIONS=\"--loader ts-node/esm\" node src/index.ts",
    "start": "node dist/index.js"
  }
}

postbuild处理

javascript 复制代码
// scripts/postbuild.mjs
import { readFileSync, writeFileSync } from 'fs';

// 添加shebang到入口文件
const indexPath = './dist/index.js';
let content = readFileSync(indexPath, 'utf8');
if (!content.startsWith('#!/usr/bin/env node')) {
    content = '#!/usr/bin/env node\n' + content;
    writeFileSync(indexPath, content);
}

9. 关键实现要点

MCP通信协议

  • 使用stdio模式进行标准输入输出通信
  • 支持工具列表查询和工具调用
  • 返回标准化的响应格式

文件处理策略

  • 支持多文件批量操作
  • 智能识别项目结构
  • 自动创建目录结构

错误处理机制

  • 统一的错误响应格式
  • 详细的错误信息提示
  • 优雅的异常处理

工作区集成

  • 自动检测工作区项目
  • 支持单项目和多项目模式
  • 智能文件路径解析

10. 测试和调试

本地测试

bash 复制代码
# 开发模式
npm run dev

# 构建测试
npm run build
npm start

MCP客户端集成测试

json 复制代码
{
  "mcpServers": {
    "rule-server": {
      "command": "node",
      "args": ["/path/to/dist/index.js"]
    }
  }
}

通过以上步骤,即可完成一个功能完整的MCP服务器实现。

相关推荐
电商API大数据接口开发Cris1 小时前
构建异步任务队列:高效批量化获取淘宝关键词搜索结果的实践
前端·数据挖掘·api
喝咖啡的女孩1 小时前
React useState 解读
前端
渴望成为python大神的前端小菜鸟1 小时前
浏览器及其他 面试题
前端·javascript·ajax·面试题·浏览器
1024肥宅1 小时前
手写 new 操作符和 instanceof:深入理解 JavaScript 对象创建与原型链检测
前端·javascript·ecmascript 6
吃肉的小飞猪1 小时前
uniapp 下拉刷新终极方案
前端
关关长语1 小时前
Vue本地部署包快速构建为Docker镜像
前端·vue.js·docker
jump6801 小时前
react的事件优先级
前端
soda_yo1 小时前
浅拷贝与深拷贝: 克隆一只哈基米
前端·javascript·面试
冴羽2 小时前
Nano Banana Pro 零基础快速上手
前端·人工智能·aigc