以 kingbase-mcp-server 为例,涵盖 MCP 协议原理、架构设计、工具注册、传输层实现、测试、CI/CD 和发布全流程。
目录
一、MCP 协议概述
1.1 什么是 MCP
MCP(Model Context Protocol,模型上下文协议)是 Anthropic 提出的开放标准,用于在 AI 助手(如 Claude)和外部数据源/工具之间建立标准化的通信桥梁。
MCP 的核心定位:让 LLM 以安全、标准化的方式调用外部工具和获取上下文信息。
┌──────────────┐ MCP 协议 ┌──────────────┐
│ │ ◄──────────────► │ │
│ MCP Client │ JSON-RPC 2.0 │ MCP Server │
│ (AI 助手) │ │ (你的服务) │
│ │ 工具调用 / 结果 │ │
└──────────────┘ └──────────────┘
│
┌─────┴─────┐
│ 数据库/API │
│ 文件系统等 │
└───────────┘
1.2 核心概念
MCP 定义了三种服务端向客户端暴露的能力:
| 概念 | 说明 | 控制方 |
|---|---|---|
| Tools(工具) | 可被 LLM 调用的函数,带有输入 schema 和返回值 | LLM 决定何时调用 |
| Resources(资源) | 向 LLM 暴露数据/文件内容(类似 GET) | 应用程序控制 |
| Prompts(提示词模板) | 预定义的提示词模板,可由用户选择触发 | 用户控制 |
本项目主要使用 Tools 能力,将数据库操作封装为 11 个工具暴露给 LLM。
1.3 通信模型
MCP 基于 JSON-RPC 2.0 协议,支持两种传输方式:
1. stdio(标准输入输出)
- 客户端启动 MCP Server 进程
- 通过 stdin/stdout 交换 JSON-RPC 消息
- 适用于本地使用,一对一的进程关系
Client ──stdin──► Server Process ──stdout──► Client
2. Streamable HTTP
- 使用 HTTP POST/GET/DELETE 与 Server-Sent Events (SSE)
- 支持远程部署,多客户端共享一个服务端
- 通过
mcp-session-idHeader 区分会话
Client ──HTTP POST /mcp──► Express Server ──JSON-RPC Response──► Client
Client ──HTTP GET /mcp──► Express Server ──SSE Stream──────────► Client
Client ──HTTP DELETE /mcp─► Express Server ──Session Terminated──► Client
1.4 协议版本
当前使用的协议版本为 2025-03-26。客户端在 initialize 请求中发送协议版本号,服务端在响应中确认。这个握手过程由 SDK 自动处理。
二、项目初始化
2.1 技术栈选型
| 组件 | 选型 | 说明 |
|---|---|---|
| 语言 | TypeScript 5.7+ | 类型安全,MCP SDK 官方支持 |
| 运行时 | Node.js >= 20 | 长期支持版本 |
| MCP SDK | @modelcontextprotocol/sdk ^1.6.1 | 官方 TypeScript SDK |
| Schema 校验 | zod ^3.23 | MCP SDK 原生集成 Zod |
| HTTP 框架 | express ^5.2 | HTTP 传输模式使用 |
| 数据库驱动 | pg ^8.13 | PostgreSQL 协议驱动 |
| 构建工具 | tsc(TypeScript Compiler) | 简单直接,无需 bundler |
| 测试框架 | vitest ^4.0 | 快速、兼容 ESM |
| Schema 转换 | zod-to-json-schema ^3.25 | 将 Zod schema 转为 JSON Schema(用于 HTTP 工具发现端点) |
2.2 项目结构
kingbase-mcp-server/
├── src/
│ ├── index.ts # 主文件:Server、工具注册、传输层、HTTP 端点
│ └── utils.ts # 纯工具函数(可独立测试)
├── tests/
│ └── utils.test.ts # 单元测试
├── scripts/
│ └── generate-docs.ts # 文档自动生成脚本
├── dist/ # 编译输出(gitignore)
├── docs/ # 文档目录
├── tsconfig.json
├── package.json
├── .env.example
├── TOOLS.md # 自动生成的工具参考文档
├── CLAUDE.md # AI 助手的代码导航文件
└── .github/workflows/
├── ci.yml # CI 流水线
└── publish.yml # npm 发布流水线
设计原则:尽量简单。整个 Server 在一个文件(src/index.ts)中实现,便于理解和维护。只有纯工具函数被提取到 src/utils.ts,原因是 index.ts 在 import 时就会启动 Server,无法直接用于单元测试。
整体框架:

2.3 TypeScript 配置
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
关键点:
module: "Node16"+moduleResolution: "Node16":使用 Node.js 原生 ESM 模块系统target: "ES2022":支持 top-level await 等现代语法strict: true:开启所有严格类型检查declaration: true:生成.d.ts类型声明(npm 包需要)
2.4 package.json 关键配置
{
"name": "kingbase-mcp-server",
"type": "module",
"main": "dist/index.js",
"bin": {
"kingbase-mcp-server": "dist/index.js"
},
"files": ["dist", "README.md", "LICENSE", "TOOLS.md"],
"engines": { "node": ">=20" },
"scripts": {
"start": "node dist/index.js",
"start:http": "TRANSPORT=http node dist/index.js",
"dev": "npx tsx watch src/index.ts",
"build": "npm run docs:generate && tsc",
"test": "vitest run",
"prepublishOnly": "npm run build"
}
}
关键点:
"type": "module":声明为 ESM 包,import 路径必须带.js扩展名"bin":注册 CLI 命令,安装后可直接kingbase-mcp-server执行"files":控制 npm 发布内容,只包含编译输出"prepublishOnly":发布前自动构建src/index.ts首行的#!/usr/bin/env nodeshebang 确保可作为可执行文件运行
三、架构设计
3.1 整体架构
┌─────────────────────────────────────────┐
│ src/index.ts │
│ │
┌─────────────┐ stdio │ ┌──────────────┐ ┌───────────────┐ │
│ MCP Client │◄────────────►│ │ McpServer │ │ Tool Registry │ │
│ (Claude等) │ │ │ (SDK 实例) │ │ (元数据存储) │ │
└─────────────┘ │ └──────┬───────┘ └───────────────┘ │
│ │ │
┌─────────────┐ HTTP │ ┌──────┴───────┐ │
│ MCP Client │◄────────────►│ │ 11 Tools │ │
│ (远程) │ /mcp │ │ (工具实现) │ │
└─────────────┘ │ └──────┬───────┘ │
│ │ │
┌─────────────┐ REST │ ┌──────┴───────┐ ┌──────────────┐ │
│ HTTP Client │◄────────────►│ │ Express App │ │ Session Mgmt │ │
│ (浏览器等) │ /tools │ │ (HTTP 端点) │ │ (会话管理) │ │
└─────────────┘ /health │ └──────────────┘ └──────────────┘ │
└──────────┬──────────────────────────────┘
│
┌──────────┴──────────┐
│ src/utils.ts │
│ (纯工具函数 + 常量) │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ pg.Pool │
│ (数据库连接池) │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ KingBase / PG │
│ (目标数据库) │
└─────────────────────┘
3.2 代码分层
src/index.ts 按从上到下的顺序组织:
1. Imports & Constants — 导入依赖、定义常量
2. Tool Registry — 工具注册表(用于 HTTP 端点)
3. Database Connection — createPool() 数据库连接池
4. Helpers — executeQuery() 查询执行封装
5. Server Factory — createServer() + registerTools()
6. Tool Definitions (×11) — 每个工具:Schema → 注册 → Handler
7. Main — 环境校验 → 连接验证 → 传输分发
8. Session Management — HTTP 模式的会话管理
9. HTTP Server — Express 应用 + 各端点定义
3.3 双传输模式设计
通过环境变量 TRANSPORT 在运行时选择模式:
const transportMode = (process.env.TRANSPORT || "stdio").toLowerCase();
if (transportMode === "http") {
await startHttpServer(); // HTTP 模式
} else {
// stdio 模式
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
}
stdio 模式:创建单个 McpServer 实例 + StdioServerTransport,一对一服务。
HTTP 模式:创建单个共享的 McpServer + 单个共享的 StreamableHTTPServerTransport,由 SDK 的 transport 内部通过 mcp-session-id 路由不同客户端的请求。activeSessions Map 仅用于监控和超时清理,不参与请求路由。
四、核心开发
4.1 Server 创建与工厂模式
使用工厂函数封装 Server 创建和工具注册:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
function createServer(): McpServer {
const server = new McpServer({
name: "kingbase-mcp-server", // 服务名称(协议握手时返回)
version: "1.0.0", // 服务版本
});
registerTools(server); // 注册所有工具
return server;
}
McpServer 是 MCP SDK 提供的高层封装,内部处理了:
- JSON-RPC 消息的序列化/反序列化
initialize握手与能力协商- 工具列表响应(
tools/list) - 工具调用分发(
tools/call)
4.2 工具注册(Tool Registration)
这是开发 MCP Server 的核心环节。每个 Tool 由三部分组成:
server.registerTool(
name, // 1. 工具名称(唯一标识符)
config, // 2. 配置对象(title, description, inputSchema, annotations)
handler, // 3. 异步处理函数
);
完整示例:
registerToolWithMetadata(
server,
"kb_query",
{
title: "Execute Query", // 人类可读标题
description: `Execute a read-only SQL...`, // 详细描述(LLM 据此决定何时调用)
inputSchema: QueryInputSchema, // Zod schema 定义输入参数
annotations: {
// 工具行为标注
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
},
async (params) => {
// params 已经过 Zod 校验,类型安全
const result = await executeQuery(params.sql, params.params);
return {
content: [{ type: "text", text: formatRows(result.rows) }],
};
},
);
重点:description 字段极其重要!LLM 完全依赖这个描述来判断何时以及如何调用工具。需要包含:
- 工具的功能说明
- 参数含义与示例
- 返回值说明
- 使用限制和注意事项
4.3 输入校验(Zod Schema)
MCP SDK 原生集成 Zod,用于定义工具的输入参数:
import { z } from "zod";
const QueryInputSchema = z
.object({
sql: z
.string()
.min(1, "SQL query is required")
.describe("SELECT query to execute"), // describe() 会出现在 JSON Schema 中
params: z
.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))
.optional()
.describe("Optional parameterized query values ($1, $2, ...)"),
schema: z
.string()
.optional()
.describe("Schema name for unqualified table names"),
})
.strict(); // .strict() 拒绝未定义的额外字段
开发要点:
- 每个字段都要加
.describe()— 这是 LLM 理解参数含义的关键 - 使用
.strict()防止 LLM 传入未定义的字段 .default()为可选参数提供合理默认值.min()/.max()添加业务约束
SDK 内部会将 Zod schema 自动转换为 JSON Schema,在 tools/list 响应中返回给客户端。
4.4 MCP Annotations(工具元数据注解)
Annotations 是 MCP 协议定义的工具行为标注,帮助客户端理解工具特性:
annotations: {
readOnlyHint: boolean, // true = 工具不修改任何状态
destructiveHint: boolean, // true = 工具可能执行不可逆操作
idempotentHint: boolean, // true = 相同输入多次调用结果相同
openWorldHint: boolean, // true = 工具与外部世界交互(如发邮件)
}
| 工具类型 | readOnly | destructive | idempotent | openWorld |
|---|---|---|---|---|
| 只读查询 | true | false | true | false |
| INSERT/UPDATE/DELETE | false | true | false | false |
| DDL (CREATE/ALTER/DROP) | false | true | false | false |
| EXPLAIN | false | false | true | false |
这些注解帮助 MCP 客户端做出安全决策,例如 Claude Desktop 可能会在调用 destructiveHint: true 的工具前额外提示用户确认。
4.5 工具处理函数的返回格式
工具处理函数必须返回 MCP 规定的格式:
// 成功响应
return {
content: [{ type: "text", text: "查询结果..." }],
};
// 错误响应
return {
isError: true,
content: [{ type: "text", text: "Error: syntax error at position 42" }],
};
content 是一个数组,支持多种类型:
{ type: "text", text: "..." }— 纯文本{ type: "image", data: "base64...", mimeType: "image/png" }— 图片{ type: "resource", resource: { uri: "...", text: "..." } }— 资源引用
本项目所有工具统一返回 text 类型,使用 Markdown 格式化的表格。
4.6 数据库连接层
使用 pg.Pool 连接池管理数据库连接:
import pg from "pg"; // ESM 默认导入
const { Pool } = pg; // 解构获取 Pool 类
function createPool(): pg.Pool {
return new Pool({
host: process.env.DB_HOST || "localhost",
port: parseInt(process.env.DB_PORT || "54321", 10),
user: process.env.DB_USER || "system",
password: process.env.DB_PASSWORD || "",
database: process.env.DB_NAME || "kingbase",
max: 5, // 最大连接数
idleTimeoutMillis: 30000, // 空闲连接超时 30s
connectionTimeoutMillis: 10000, // 连接超时 10s
});
}
ESM/CJS 互操作注意:pg 是 CommonJS 包,在 ESM 项目中必须用 import pg from "pg" 默认导入后再解构,不能直接 import { Pool } from "pg"。
查询执行封装确保连接正确释放:
async function executeQuery(
sql: string,
params?: unknown[],
): Promise<pg.QueryResult> {
const client = await pool.connect();
try {
return await client.query(sql, params);
} finally {
client.release(); // 无论成功失败都释放连接回池
}
}
五、传输层实现
5.1 stdio 传输
这是最简单的模式,5 行代码即可启动:
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("KingBase MCP Server running via stdio");
注意:
- 诊断输出必须走
console.error(stderr),因为 stdout 是 MCP 通信通道 - 这也是
src/index.ts首行#!/usr/bin/env node的原因 —— 让它可作为可执行脚本运行
5.2 Streamable HTTP 传输
HTTP 模式需要配合 Express 构建完整的 HTTP 服务:
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import express from "express";
const app = express();
app.use(express.json());
// 创建共享的 Transport 和 Server(单实例架构)
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessionclosed: (sessionId) => {
activeSessions.delete(sessionId);
},
});
const server = createServer();
await server.connect(transport);
MCP Streamable HTTP 需要三个端点:
// POST /mcp — 接收 JSON-RPC 请求(包括 initialize 和普通调用)
app.post("/mcp", async (req, res) => {
await transport.handleRequest(req, res, req.body);
});
// GET /mcp — SSE 流,用于服务端主动推送消息
app.get("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
// DELETE /mcp — 终止会话
app.delete("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
架构选择说明:本项目采用单实例架构(一个 McpServer + 一个 Transport),而非"每个会话一个 Server 实例"的模式。SDK 的 StreamableHTTPServerTransport 内部通过 mcp-session-id Header 路由请求到对应会话,无需开发者手动管理。这种架构更简单、资源占用更少。
5.3 会话管理
会话追踪仅用于运维监控和超时清理:
interface SessionInfo {
transport: StreamableHTTPServerTransport;
server: McpServer;
createdAt: Date;
lastActivityAt: Date;
requestCount: number;
}
const activeSessions = new Map<string, SessionInfo>();
超时清理:每分钟扫描一次,清理超过 30 分钟无活动的会话:
const cleanupTimer = setInterval(() => {
cleanupStaleSessions(activeSessions);
}, SESSION_CLEANUP_INTERVAL_MS);
容量控制:限制最大并发会话数,超过时返回 503:
if (activeSessions.size >= MAX_SESSIONS) {
res.status(503).json({
jsonrpc: "2.0",
error: { code: -32000, message: "Server capacity reached" },
id: null,
});
}
优雅关闭:SIGINT 时依次关闭 transport → 清理会话 → 关闭 HTTP server → 关闭数据库连接池:
process.on("SIGINT", async () => {
clearInterval(cleanupTimer);
await transport.close();
activeSessions.clear();
httpServer.close(() => {
pool.end().then(() => process.exit(0));
});
});
六、安全设计
6.1 权限分级
通过 ACCESS_MODE 环境变量实现 4 级递增权限:
type AccessMode = "readonly" | "readwrite" | "full" | "admin";
const ACCESS_MODE_LEVELS: Record<AccessMode, number> = {
readonly: 0, // SELECT / 查看 schema 结构
readwrite: 1, // + INSERT / UPDATE
full: 2, // + DELETE
admin: 3, // + DDL (CREATE / ALTER / DROP / TRUNCATE)
};
function hasAccess(required: AccessMode): boolean {
return ACCESS_MODE_LEVELS[getAccessMode()] >= ACCESS_MODE_LEVELS[required];
}
在每个写操作工具的处理函数入口检查权限:
// kb_execute 中
const dmlType = classifyDML(params.sql);
const requiredMode: AccessMode = dmlType === "DELETE" ? "full" : "readwrite";
if (!hasAccess(requiredMode)) {
return {
isError: true,
content: [{ type: "text", text: "Access denied..." }],
};
}
// kb_execute_ddl 中
if (!hasAccess("admin")) {
return {
isError: true,
content: [{ type: "text", text: "Access denied..." }],
};
}
默认为 readonly,即使 MCP 客户端尝试调用写操作工具也会被拒绝。
6.2 二次确认机制
DML 和 DDL 工具内置两阶段确认,防止 LLM 误操作:
// 第一阶段:预览
if (!params.confirmed) {
return {
content: [
{
type: "text",
text: `⚠️ Confirmation required for ${dmlType} operation.\n\nSQL:\n${sql}\n\nCall again with confirmed: true to execute.`,
},
],
};
}
// 第二阶段:confirmed: true 时真正执行
const result = await executeQuery(sql, params.params);
这个设计确保 LLM 在执行任何写操作前必须向用户展示预览并获得确认。
6.3 SQL 安全
- 只读检查:
isReadOnly()通过关键字前缀判断 SQL 是否为只读 - 危险 DDL 检测:
isDangerousDDL()检测 DROP/TRUNCATE/CASCADE - 参数化查询:所有工具都支持
$1, $2, ...参数化查询,防止 SQL 注入 - schema 自动限定:防止跨 schema 访问(详见 7.2 节)
七、辅助功能
7.1 工具发现端点(Tool Discovery)
除了 MCP 协议内置的 tools/list,本项目额外提供了 HTTP REST 端点,方便非 MCP 客户端浏览工具信息:
// 全局工具注册表
const TOOL_REGISTRY: Map<string, ToolMetadata> = new Map();
// 包装注册函数,同时写入注册表
function registerToolWithMetadata(server, name, config, handler) {
TOOL_REGISTRY.set(name, {
name,
title,
description,
inputSchema,
annotations,
});
server.registerTool(name, config, handler);
}
// REST 端点
app.get("/tools", (req, res) => {
const tools = Array.from(TOOL_REGISTRY.values()).map((t) => ({
name: t.name,
title: t.title,
description: t.description.split("\n")[0], // 仅返回第一行摘要
annotations: t.annotations,
}));
res.json({ count: tools.length, tools });
});
app.get("/tools/:toolName", (req, res) => {
const tool = TOOL_REGISTRY.get(toolName);
// 使用 zod-to-json-schema 将 Zod schema 转为标准 JSON Schema
res.json(toolMetadataToJson(tool));
});
这使得 HTTP 模式下可以通过 GET /tools 快速查看所有可用工具,GET /tools/kb_query 查看某个工具的完整 JSON Schema。
7.2 Schema 自动限定
自动为未限定的表名添加 schema 前缀,简化 LLM 的 SQL 生成:
// 输入: "SELECT * FROM users" + schema="myapp"
// 输出: "SELECT * FROM myapp.users"
// 输入: "SELECT * FROM public.users" (已有 schema 前缀)
// 输出: "SELECT * FROM public.users" (不修改)
function qualifyTableNames(sql: string, schema: string): string {
const keywords = ["FROM", "JOIN", "INTO", "UPDATE"];
// 对每个关键字后的未限定表名添加 schema. 前缀
// 使用 negative lookahead 避免重复限定
}
7.3 输出截断
防止大结果集导致 Token 爆炸:
const CHARACTER_LIMIT = 50000;
if (result.length > CHARACTER_LIMIT) {
result =
result.substring(0, CHARACTER_LIMIT) +
"\n\n... [Output truncated. Use LIMIT or add WHERE conditions to reduce results.]";
}
7.4 自动文档生成
scripts/generate-docs.ts 通过正则解析 src/index.ts 中的工具注册代码,自动生成 TOOLS.md:
// 正则匹配 registerToolWithMetadata 调用
const toolPattern = /registerToolWithMetadata\(server,\s*"([^"]+)",.../gi;
// 提取: name, title, description, annotations
// 按 Read-Only / Write 分类
// 生成 Markdown 文档
集成到构建流程:
{
"scripts": {
"build": "npm run docs:generate && tsc",
"docs:generate": "npx tsx scripts/generate-docs.ts"
}
}
每次构建时自动更新文档,确保文档与代码始终同步。
八、测试
调试 MCP Server
官方提供了一个 Inspector 调试工具,它是一款用于测试和调试 MCP 服务器的交互式开发者工具,详细介绍和使用方法如下:
启动调试参考命令:
sudo npx @modelcontextprotocol/inspector node dist/index.js
启动后会自动打开网页,配置上必要的参数即可连接测试

8.1 测试策略
由于 src/index.ts 在 import 时就会启动 Server(连接数据库、绑定端口),直接对其进行单元测试是不可行的。因此采取的策略是:
- 提取纯函数到
src/utils.ts—— 不依赖任何副作用,可独立测试 - 对纯函数编写详尽的单元测试(49 个 test cases)
- 集成测试通过手动验证 + CI 构建验证
8.2 纯函数提取与单元测试
src/utils.ts 导出的 9 个函数 + 常量:
| 函数 | 职责 | 测试用例数 |
|---|---|---|
isReadOnly(sql) | 判断 SQL 是否只读 | 10 |
classifyDML(sql) | 分类 DML 类型 | 6 |
isDangerousDDL(sql) | 检测危险 DDL | 6 |
formatRows(rows) | 格式化结果为 ASCII 表 | 5 |
handleDbError(error) | 提取 pg 错误详情 | 4 |
qualifyTableNames(sql, schema) | Schema 自动限定 | 6 |
getAccessMode() | 获取当前权限级别 | 5 |
hasAccess(required) | 权限检查 | 4 |
getDefaultSchema() | 获取默认 schema | 3 |
测试使用 Vitest 框架:
import { describe, it, expect, afterEach } from "vitest";
describe("isReadOnly", () => {
it("returns true for SELECT", () => {
expect(isReadOnly("SELECT * FROM users")).toBe(true);
});
it("returns false for INSERT", () => {
expect(isReadOnly("INSERT INTO users (name) VALUES ('a')")).toBe(false);
});
// ...
});
对于依赖环境变量的函数,使用 afterEach 恢复原始值:
describe("getAccessMode", () => {
const originalEnv = process.env.ACCESS_MODE;
afterEach(() => {
if (originalEnv === undefined) delete process.env.ACCESS_MODE;
else process.env.ACCESS_MODE = originalEnv;
});
// ...
});
九、CI/CD 与发布
9.1 CI 流水线
.github/workflows/ci.yml:每次 push/PR 到 main 时自动运行:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22, 24] # 多版本矩阵测试
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: rm -f package-lock.json && npm install
- run: npx tsc # 类型检查 + 编译
- run: npm test # 单元测试
注意 rm -f package-lock.json && npm install 而非 npm ci:这是因为本项目没有固定 lock file(依赖含平台相关的 native 模块如 rollup,npm ci 在跨平台时可能失败)。
9.2 npm 发布流水线
.github/workflows/publish.yml:当推送 v* tag 时自动发布到 npm:
name: Publish to npm
on:
push:
tags: ["v*"]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # OIDC Trusted Publishing 所需
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
cache: npm
- run: rm -f package-lock.json && npm install
- run: npx tsc
- run: npm test
- run: npm publish # 使用 OIDC Trusted Publishing,无需 NPM_TOKEN
OIDC Trusted Publishing:使用 GitHub Actions 的 OIDC token 直接认证 npm,无需在仓库 Secrets 中存储 NPM_TOKEN。需要在 npm 网站上预先配置 Trusted Publisher。Node版本需要>=24
先去www.npmjs.com网站找到发布的包,打开settings,在Trusted Publisher设置中选择GitHub Actions,然后填写源码仓库的信息以及发布的CI文件名。其中id-token的配置以及Node的版本要求缺一不可。

流水线执行完成后即可查看包的信息

查看包信息(官方仓库有延时,可能搜不到),下面命令是不受延时限制
npm view kingbase-mcp-server
kingbase-mcp-server@1.0.1 | MIT | deps: 6 | versions: 2
MCP server for KingBase (PostgreSQL-compatible) database operations
https://github.com/NekoTarou/kingbase-mcp-server#readme
keywords: mcp, mcp-server, kingbase, database, postgresql, model-context-protocol, ai, llm
bin: kingbase-mcp-server
dist
.tarball: https://registry.npmjs.org/kingbase-mcp-server/-/kingbase-mcp-server-1.0.1.tgz
.shasum: dded23279686cfcca773cbbcd1dfca8e7c925a75
.integrity: sha512-9Wj02l2bdOeA70OBDRPbYtGupxcojMrFiosTiqU9w4JTmXoDXwft3sPh5WxKL4PkenVqQEUA8vcpKlsFqINbTA==
.unpackedSize: 122.0 kB
dependencies:
@modelcontextprotocol/sdk: ^1.6.1 dotenv: ^17.2.4 express: ^5.2.1 pg: ^8.13.1 zod-to-json-schema: ^3.25.1 zod: ^3.23.8
maintainers:
- nekoutarou <nekoutarou@gmail.com>
dist-tags:
latest: 1.0.1
published an hour ago by GitHub Actions <npm-oidc-no-reply@github.com>
9.3 版本发布流程
# 1. 更新 package.json 中的 version
npm version patch # 或 minor / major
# 2. 推送代码和 tag
git push && git push --tags
# 3. CI 自动:编译 → 测试 → 发布到 npm
发布后用户即可使用(https://www.npmjs.com/package/kingbase-mcp-server):
npx kingbase-mcp-server # 直接运行
npm install -g kingbase-mcp-server # 全局安装
十、客户端配置
10.1 stdio 模式配置
适用于 Claude Desktop、Claude Code 等本地 MCP 客户端。
方式一:npx 运行(推荐,无需安装)
在 ~/.claude.json 或项目根目录的 .mcp.json 中:
{
"mcpServers": {
"kingbase": {
"command": "npx",
"args": ["-y", "kingbase-mcp-server"],
"env": {
"DB_HOST": "localhost",
"DB_PORT": "54321",
"DB_USER": "system",
"DB_PASSWORD": "your_password",
"DB_NAME": "kingbase",
"DB_SCHEMA": "public",
"ACCESS_MODE": "readonly"
}
}
}
}
opencode配置:
vim ~/.config/opencode/opencode.json
"mcp": {
"kingbase": {
"type": "local",
"command": ["npx","-y", "kingbase-mcp-server"],
"environment": {
"DB_HOST": "localhost",
"DB_PORT": "54321",
"DB_USER": "system",
"DB_PASSWORD": "your_password",
"DB_NAME": "kingbase",
"DB_SCHEMA": "public",
"ACCESS_MODE": "readonly"
"TRANSPORT": "stdio"
}
}
}
方式二:本地编译运行
{
"mcpServers": {
"kingbase": {
"command": "node",
"args": ["/path/to/kingbase-mcp-server/dist/index.js"],
"env": {
"DB_HOST": "localhost",
"DB_PASSWORD": "your_password"
}
}
}
}
配置之后记得重启客户端,查看mcp列表是否连接
Claude查看效果如下:

opencode查看如下:

此时可以验证

10.2 HTTP 模式配置
服务端启动:
TRANSPORT=http MCP_PORT=3000 DB_HOST=192.168.1.100 DB_PASSWORD=xxx node dist/index.js
客户端配置(简洁,只需一个 URL):
{
"mcpServers": {
"kingbase": {
"url": "http://your-server:3000/mcp"
}
}
}
验证连接:
# 健康检查
curl http://localhost:3000/health
# 查看工具列表
curl http://localhost:3000/tools
# 测试 MCP 初始化
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
10.3 服务器部署(systemd)
创建环境变量文件 /etc/kingbase-mcp-server.env:
TRANSPORT=http
MCP_PORT=3000
MCP_HOST=0.0.0.0
ACCESS_MODE=readonly
DB_HOST=192.168.1.100
DB_PORT=54321
DB_USER=system
DB_PASSWORD=your_password
DB_NAME=mydb
DB_SCHEMA=public
创建 systemd 服务 /etc/systemd/system/kingbase-mcp.service:
[Unit]
Description=KingBase MCP Server
After=network.target
[Service]
Type=simple
EnvironmentFile=/etc/kingbase-mcp-server.env
WorkingDirectory=/opt/kingbase-mcp-server
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable kingbase-mcp
sudo systemctl start kingbase-mcp
sudo journalctl -u kingbase-mcp -f # 查看日志
十一、开发中的重点与踩坑总结
协议与 SDK 层面
- ESM 模块路径必须带
.js后缀:import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"— 即使源码是.ts,import 路径也要写.js pg的 ESM/CJS 互操作:必须import pg from "pg"再const { Pool } = pg,不能直接解构导入- stdout 是 MCP 通道:stdio 模式下所有日志/诊断输出必须走
stderr(console.error) description是工具的灵魂:LLM 完全依赖描述来理解工具,要写得清晰、完整、有示例- Zod
.strict()很重要:防止 LLM 传入未定义的字段导致意外行为
架构层面
- 单文件 vs 多文件:小型 MCP Server 用单文件更易理解。仅在需要测试时提取纯函数
- 单实例 vs 多实例 HTTP:SDK 的
StreamableHTTPServerTransport内部管理会话路由,开发者用单实例即可 - 工厂模式:
createServer()+registerTools()分离让代码组织更清晰 - 连接池大小:MCP Server 通常不需要大连接池,
max: 5足够
安全层面
- 默认最小权限:
ACCESS_MODE默认readonly,需要写权限时显式配置 - 二次确认是刚需:LLM 可能"自作主张"执行 DELETE/DROP,确认机制是最后防线
- HTTP 无内置认证:生产环境必须加反向代理 + 认证
发布层面
#!/usr/bin/env node:bin入口文件必须有files字段控制包内容:避免发布src/、tests/、.env等不需要的文件prepublishOnly:确保每次发布前自动构建- OIDC Trusted Publishing:比 NPM_TOKEN 更安全,需 Node.js 24+ 支持
- CI 中
rm -f package-lock.json:解决跨平台 native 依赖不兼容的问题
开发体验
npm run dev:使用tsx watch实现热重载开发.env支持:dotenv/configimport 让本地开发不用每次在命令行传环境变量- CLAUDE.md:为 AI 助手提供代码导航指南,加速协同开发
- 自动文档生成:
build时自动更新TOOLS.md,文档永远与代码同步
最后:MCP并不能完成所有事情,却是agent获取外部资源重要手段,在日常开发过程中搭配SKILLS才能更好发挥其作用。
References
OpenCode关于Mcp说明
Inspector 调试
Trusted publishing for npm packages
openid-connect
Connect Claude Code to tools via MCP