使用NodeJS开发MCP服务实现Yapi的访问

引言

随着 AI 助手(如 Claude、ChatGPT 等)的普及,如何让这些智能助手与我们的业务系统深度集成成为了一个重要课题。Model Context Protocol (MCP) 应运而生,它为 AI 助手提供了一种标准化的方式来访问外部数据源和工具。

本文将以一个实际项目为例,手把手教你如何开发一个 MCP 服务,实现 AI 助手与 YApi 的集成。

什么是 MCP?

Model Context Protocol (MCP) 是由 Anthropic 推出的开放协议,旨在标准化 AI 助手与外部系统的通信方式。通过 MCP,AI 助手可以:

  • 获取上下文信息:访问数据库、API 文档、代码仓库等
  • 执行操作:调用工具、修改数据、触发工作流
  • 保持安全:通过标准化的权限控制确保数据安全

MCP 采用客户端-服务器架构:

  • MCP 客户端:通常是 AI 助手(如 Claude Desktop)
  • MCP 服务器:提供特定功能的服务端实现
  • 传输层:支持 stdio(标准输入输出)或 HTTP with SSE

项目概述

本项目是一个基于 MCP 协议的服务,它让 AI 助手能够通过 YApi 获取接口文档信息。

YApi 是一个流行的接口管理平台,广泛应用于前后端协作场景。

核心功能

  • 提供 yapi_get_interface 工具,根据接口 ID 查询 YApi 接口详情
  • 支持命令行参数配置(YApi 基础地址和 Token)
  • 采用 stdio 传输模式,与 MCP 客户端无缝集成

开发环境准备

技术栈

  • Node.js 20+:推荐使用最新 LTS 版本
  • TypeScript:提供类型安全和更好的开发体验
  • @modelcontextprotocol/sdk:官方提供的 MCP SDK
  • esbuild:用于打包构建

初始化项目

1
2
3
mkdir z-yapi-mcp
cd z-yapi-mcp
npm init -y

安装依赖

MCP官方仓库 Model Context Protocol

维度 @modelcontextprotocol/sdk @modelcontextprotocol/server
角色 客户端 / 消费者 服务端 / 提供者
功能 调用 MCP 服务 实现 MCP 服务
使用者 工具开发者、IDE 插件作者 模型服务提供商、后端工程师
依赖关系 依赖 server 提供的接口 不依赖 sdk,但需遵循 MCP 协议

这里之所以使用TS,是因为MCP的SDK只有TS版的。

1
2
3
4
5
# 核心依赖
npm install @modelcontextprotocol/sdk zod

# 开发依赖
npm install -D typescript @types/node esbuild tsx

配置 TypeScript

创建 tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist",
"rootDir": "./src"
}
}

核心代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const SERVER_NAME = "z-yapi-mcp";
const SERVER_VERSION = "1.0.0";
const USAGE =
"npx -y z-yapi-mcp --stdio --yapi-base-url=<url> --yapi-token=<token>";

const args = process.argv.slice(2);
const getArg = (name: string): string | undefined => {
const prefix = `--${name}=`;
const found = args.find((a) => a.startsWith(prefix));
return found?.slice(prefix.length);
};

const hasStdio = args.includes("--stdio");
const baseUrl = getArg("yapi-base-url");
const token = getArg("yapi-token");

if (!hasStdio || !baseUrl || !token) {
console.error(`用法: ${USAGE}`);
if (!hasStdio) console.error("缺少 --stdio");
if (!baseUrl) console.error("缺少 --yapi-base-url");
if (!token) console.error("缺少 --yapi-token");
process.exit(1);
}

const toolResult = (text: string) => ({
content: [{ type: "text" as const, text }],
isError: false,
});

const mcpServer = new McpServer(
{ name: SERVER_NAME, version: SERVER_VERSION },
{ capabilities: { tools: {} } },
);

mcpServer.registerTool(
"yapi_get_interface",
{
description: "根据接口 ID 调用 YAPI 接口,返回接口详情。",
inputSchema: z.object({ id: z.number().describe("接口 ID") }),
},
async ({ id }) => {
const url = new URL("/api/interface/get", baseUrl);
url.searchParams.set("id", String(id));
url.searchParams.set("token", token);
const resp = await fetch(url);
if (!resp.ok) {
throw new Error(`请求失败: ${resp.status} ${resp.statusText}`);
}
const data = await resp.json();
return toolResult(JSON.stringify(data, null, 2));
},
);

await mcpServer.connect(new StdioServerTransport());
console.error(`✅ ${SERVER_NAME} 已启动 (${baseUrl})`);

构建与打包

配置构建脚本

package.json 中添加构建脚本:

1
2
3
4
5
6
{
"scripts": {
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node18 --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\"",
"prepublishOnly": "npm run build"
}
}

构建参数说明:

  • --bundle:将所有依赖打包到单个文件
  • --platform=node:目标平台为 Node.js
  • --format=esm:输出 ES 模块格式
  • --target=node18:兼容 Node.js 18+
  • --banner:js:添加 Shebang,使输出文件可直接执行

执行构建

1
npm run build

测试 MCP 服务

编写一个简单的测试脚本,验证服务是否正常工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { spawn } from "node:child_process";

// 启动 MCP 服务
const child = spawn(
"node",
[
"dist/index.js",
"--stdio",
"--yapi-base-url=https://api.example.com",
"--yapi-token=yourtoken",
],
{
stdio: ["pipe", "pipe", "inherit"],
}
);

child.stdout.setEncoding("utf8");
child.stdout.on("data", (data) => {
console.log("SERVER >>", data.toString());
});

// 发送 initialize 请求
const initRequest = {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: "2024-10-07",
capabilities: {},
clientInfo: { name: "test-client", version: "1.0.0" },
},
};

child.stdin.write(JSON.stringify(initRequest) + "\n");

// 发送 tools/list 请求
setTimeout(() => {
const listRequest = {
jsonrpc: "2.0",
id: 2,
method: "tools/list",
params: {},
};
child.stdin.write(JSON.stringify(listRequest) + "\n");
}, 200);

// 结束测试
setTimeout(() => {
child.kill();
}, 1000);

运行测试:

1
node test-tools-list.mjs

配置 MCP 客户端

以 Cursor 为例,配置 MCP 服务:

发布前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"mcpServers": {
"z-yapi-mcp": {
"command": "npx",
"args": [
"-y",
".",
"--stdio",
"--yapi-base-url=https://api2.xhkjedu.com/",
"--yapi-token=323b0aff5664acb2e7cafec40eecdf8c8e763ad8e413a00a103835863e8a652e"
]
}
}
}

发布后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"mcpServers": {
"z-yapi-mcp": {
"command": "npx",
"args": [
"-y",
"z-yapi-mcp",
"--stdio",
"--yapi-base-url=https://api2.xhkjedu.com/",
"--yapi-token=323b0aff5664acb2e7cafec40eecdf8c8e763ad8e413a00a103835863e8a652e"
]
}
}
}

配置完成后,Cursor 就可以调用 yapi_get_interface 工具来获取 YApi 接口信息了。

发布到 NPM

配置 package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"name": "z-yapi-mcp",
"version": "1.0.10",
"type": "module",
"description": "A MCP server for YApi integration",
"main": "dist/index.js",
"bin": {
"z-yapi-mcp": "dist/index.js"
},
"files": [
"dist/**/*",
"README.md"
],
"keywords": [
"mcp",
"yapi",
"model-context-protocol",
"api",
"ai"
]
}

发布

1
2
npm login
npm publish

关键技术要点

stdio 传输模式

本项目采用 stdio 模式,这是 MCP 推荐的传输方式之一。它通过标准输入输出与客户端通信,具有以下优势:

  • 简单易用:无需网络配置
  • 安全可靠:进程间通信,无网络暴露风险
  • 跨平台:在所有操作系统上都能正常工作

JSON-RPC 2.0 协议

MCP 基于 JSON-RPC 2.0 构建,所有通信都遵循这一标准:

1
2
3
4
5
6
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}

工具定义规范

工具定义需要遵循 JSON Schema 规范,清晰的描述有助于 AI 助手理解何时以及如何使用工具:

  • name:工具的唯一标识
  • description:详细描述工具功能和用途
  • inputSchema:输入参数的 JSON Schema 定义

错误处理

良好的错误处理是生产环境必备:

1
2
3
if (!resp.ok) {
throw new Error(`请求失败: ${resp.status} ${resp.statusText}`);
}

MCP 会将错误信息传递给 AI 助手,助手可以根据错误信息决定下一步操作。

总结

开发 MCP 服务并不复杂,关键在于理解 MCP 协议的核心概念:

  1. 服务器:使用 @modelcontextprotocol/sdk 创建
  2. 传输层:选择 stdio 或 HTTP 模式
  3. 工具:定义 AI 可调用的功能
  4. 处理器:实现请求处理逻辑

通过本文的实战示例,你应该已经掌握了开发 MCP 服务的基本方法。不妨动手试试,为你的业务系统打造一个专属的 AI 助手集成方案!

参考资源