欢迎光临
我们一直在努力

从0写一个 MCP 服务器:用 Node/TS 把 SQL Server 里的 ERP 数据变成 AI 助手能查的工具

从0写一个 MCP 服务器:用 Node/TS 把 SQL Server 里的 ERP 数据变成 AI 助手能查的工具

📖 摘要:本文以电商 ERP 场景为例,用 Node/TS + MCP SDK 从零搭一个 MCP 服务器,把 SQL Server 里的商品、订单、成本利润表暴露成 AI 助手(Claude/Codex)可直接调用的工具。你会学到 MCP 的核心机制、用 mssql 安全连库、用参数化查询防注入,以及如何把业务数据"喂"进 Agent 工作流。读完能独立把任意数据库接进大模型。

🏷️ 关键词:MCP,Node.js,TypeScript,SQL Server,AI Agent

目录

  • 一、背景与痛点
  • 二、MCP 到底是什么
    • 2.1 核心概念
    • 2.2 它解决什么问题
  • 三、实战:从0搭一个 ERP MCP 服务器
    • 3.1 环境准备
    • 3.2 项目初始化与依赖
    • 3.3 连接 SQL Server
    • 3.4 定义三个业务工具
    • 3.5 启动与注册到客户端
  • 四、踩坑与优化
    • 4.1 防 SQL 注入
    • 4.2 连接池与超时
  • 五、总结

一、背景与痛点

假设我们要搭建一套电商 ERP 系统,数据库是 SQL Server(示例地址 your-server-ip,1433,库名 demo_db),里面有 tb_products(商品)、tb_orders(订单)、成本利润汇总表等十几张表。下面所有库名、表名、IP 都是示意,请替换成你自己的环境。

痛点很现实:业务数据锁在数据库里,AI 助手根本碰不到。我们想让 Claude/Codex 直接回答"上个月利润最高的 10 个 SKU 是哪些"“这个商品最近的退货率”,但它只能干瞪眼——它不会连 SQL Server,也不该直接给它裸 SQL 权限。

MCP(Model Context Protocol,模型上下文协议)就是为这事而生的:它让你把"查数据库"封装成一个受控的工具(Tool),AI 调用工具、你控制边界。

💡 小提示:文中的库名、表名、服务器地址均为示例,你可以直接换成自己的业务库。

二、MCP 到底是什么

2.1 核心概念

MCP 是一个客户端-服务器协议。你的 AI 客户端(Claude Desktop / Codex / Cursor)是 Host,你的 MCP 服务器是一个独立进程,通过 stdio 或 SSE 通信。服务器向客户端"声明"自己能提供哪些工具(Tools)、资源(Resources)、提示(Prompts)。

2.2 它解决什么问题

没有 MCP 时,每接一个数据源就要给每个 AI 客户端写一套适配。有了 MCP,服务器写一次,所有支持 MCP 的客户端都能用——这正是它 2026 年冲上 CSDN 热榜第 1 的原因。

三、实战:从0搭一个 ERP MCP 服务器

3.1 环境准备

  • Node.js ≥ 18(建议 22)
  • 一台可达的 SQL Server(示例地址 your-server-ip,1433)
  • 一个能跑 MCP 的客户端(Claude Desktop 或 Codex CLI)

3.2 项目初始化与依赖

mkdir erp-mcp && cd erp-mcp
npm init -y
npm install @modelcontextprotocol/sdk mssql zod
npm install -D tsx typescript @types/node @types/mssql

tsx 让我们可以直接跑 TypeScript,省去编译步骤。

3.3 连接 SQL Server

用 mssql(底层是 tedious),封装一个带连接池的配置:

// src/db.ts
import sql from "mssql";

export const sqlConfig: sql.config = {
user: process.env.ERP_SA || "sa",
password: process.env.ERP_PWD || "your_strong_password",
server: "your-server-ip",
port: 1433,
database: "demo_db",
options: { encrypt: false, trustServerCertificate: true },
pool: { max: 10, min: 0, idleTimeoutMillis: 30000 },
};

export async function getPool() {
return sql.connect(sqlConfig);
}

⚠️ 注意:密码走环境变量(ERP_PWD),不要硬编码进仓库。生产环境务必为 MCP 单独建一个只读账号。

3.4 定义三个业务工具

// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { getPool } from "./db.js";

const server = new McpServer({ name: "erp-mcp", version: "1.0.0" });

// 工具1:按关键词/分类查商品
server.tool(
"query_products",
"按关键词或分类查询 ERP 商品表(tb_products)",
{ keyword: z.string().optional().describe("商品名模糊匹配"),
category: z.string().optional().describe("商品分类") },
async ({ keyword, category }) => {
const pool = await getPool();
const req = pool.request();
let sqlText =
"SELECT TOP 50 ProductId, ProductName, Sku, Price, Category FROM tb_products WHERE 1=1";
if (keyword) { req.input("kw", sql.NVarChar, `%${keyword}%`); sqlText += " AND ProductName LIKE @kw"; }
if (category) { req.input("cat", sql.NVarChar, category); sqlText += " AND Category = @cat"; }
const r = await req.query(sqlText);
return { content: [{ type: "text", text: JSON.stringify(r.recordset, null, 2) }] };
}
);

// 工具2:查指定日期范围的订单
server.tool(
"query_orders",
"查询指定日期范围内的订单( tb_orders )",
{ start: z.string().describe("起始日期 YYYY-MM-DD"),
end: z.string().describe("结束日期 YYYY-MM-DD") },
async ({ start, end }) => {
const pool = await getPool();
const req = pool.request();
req.input("s", sql.Date, start);
req.input("e", sql.Date, end);
const r = await req.query(
"SELECT OrderId, ProductId, Qty, Amount, Status FROM tb_orders WHERE CreateAt BETWEEN @s AND @e"
);
return { content: [{ type: "text", text: JSON.stringify(r.recordset, null, 2) }] };
}
);

// 工具3:成本利润汇总(安全聚合,只读)
server.tool(
"profit_summary",
"按 SKU 汇总某月成本与利润",
{ month: z.string().describe("月份 YYYY-MM") },
async ({ month }) => {
const pool = await getPool();
const req = pool.request();
req.input("m", sql.NVarChar, `${month}%`);
const r = await req.query(
`SELECT ProductId, SUM(Amount) AS Revenue, SUM(Cost) AS Cost,
SUM(Amount – Cost) AS Profit
FROM tb_orders WHERE CreateAt LIKE @m GROUP BY ProductId ORDER BY Profit DESC
`

);
return { content: [{ type: "text", text: JSON.stringify(r.recordset, null, 2) }] };
}
);

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("ERP MCP server running on stdio");

3.5 启动与注册到客户端

跑起来:

export ERP_PWD="你的密码" # 或写进 .env,不要硬编码进仓库
npx tsx src/index.ts

注册到 Claude Desktop(claude_desktop_config.json):

{
"mcpServers": {
"erp": {
"command": "npx",
"args": ["tsx", "/path/to/erp-mcp/src/index.ts"],
"env": { "ERP_PWD": "你的密码" }
}
}
}

重启客户端后,直接问:“帮我查利润最高的 5 个 SKU(2026-06)”,AI 会自动调用 profit_summary 工具并返回数据。

四、踩坑与优化

4.1 防 SQL 注入

⚠️ 千万不要用字符串拼接用户输入! 早期我图省事写了 WHERE ProductName LIKE '%${keyword}%',一旦有人让 AI 传入 ' OR '1'='1,全表就裸了。正确做法是用 req.input() 参数化(见上文代码),让 mssql 走预编译参数。

4.2 连接池与超时

  • 配置 pool 上限,避免高并发把 SQL Server 连接打满。
  • 工具里用 await getPool() 复用连接,别每次 sql.connect 新建。
  • 客户端超时会杀进程,长查询加 SET LOCK_TIMEOUT 或限制 TOP N。

五、总结

MCP 把"数据库能力"变成 AI 可调用、你可控边界的工具,是今年非常值得上手的技术之一。本文用一个示例 ERP + SQL Server 场景走完了全流程:建服务器 → 连库 → 定义工具 → 防注入 → 注册客户端。下一步你可以加 Resources(把表结构暴露给 AI 做 schema 理解)或 Prompts(固化"月度利润复盘"话术)。

觉得有用点个赞/收藏,评论区告诉我你还想接什么数据源,下期写 PostgreSQL / MySQL 版本。

赞(0)
未经允许不得转载:171主机测评 » 从0写一个 MCP 服务器:用 Node/TS 把 SQL Server 里的 ERP 数据变成 AI 助手能查的工具
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址