基于MCP协议构建AI与业务系统集成的实践指南
模型上下文协议(MCP)作为一种标准化协议,旨在解决AI助手与外部工具、数据源之间的集成难题。其核心原理是通过定义统一的资源(Resources)、工具(Tools)和提示词模板(Prompts)接口,实现AI与后端系统的解耦与即插即用。这一协议的技术价值在于,它使得开发者能够为任何业务系统(如销售终端POS)构建一次MCP服务器,即可被所有兼容MCP的AI客户端(如Claude Desktop、
1. 项目概述与核心价值
最近在折腾一个很有意思的项目,叫 orbita-pos/inariwatch-mcp 。乍一看这个标题,可能有点摸不着头脑,它不像常见的 react-app 或者 docker-compose 那样直白。但如果你对现代软件开发,特别是前端、后端与AI工具链的集成感兴趣,那这个项目绝对值得你花时间研究。简单来说,这是一个 模型上下文协议(Model Context Protocol, MCP) 的服务器实现,专门为 InariWatch 这个POS(销售终端)系统提供AI能力。
我为什么会对它感兴趣?因为在当前这个AI工具遍地开花的时代,如何让AI真正“理解”并“操作”你的业务系统,而不是仅仅停留在聊天层面,是一个巨大的挑战。 inariwatch-mcp 项目提供了一个非常具体的答案:它通过MCP协议,将InariWatch POS系统的内部数据(如库存、订单、客户信息)和操作能力(如创建订单、查询商品)暴露给AI助手(比如Claude Desktop、Cursor等),让AI能像一名资深店员一样,在获得授权后,帮你处理实际的业务查询和操作。
想象一下这个场景:你在Claude里问“我们店里还有多少瓶2022年的赤霞珠?”,AI不仅能告诉你库存数量,还能告诉你具体在哪个仓库、货架位置,甚至能帮你创建一个补货提醒。这就是 inariwatch-mcp 要实现的愿景—— 让AI成为业务系统的“超级接口” 。它解决的痛点非常明确:业务人员或开发者无需为了一个简单的数据查询去写复杂的API调用代码,也无需在多个系统间手动切换复制粘贴数据,直接用自然语言与AI对话,就能完成工作。
这个项目适合几类人:一是InariWatch POS系统的开发者或运维人员,希望为其赋予AI能力;二是对MCP协议感兴趣,想学习如何为自己的应用构建MCP服务器的开发者;三是任何想探索AI Agent如何与真实业务系统深度集成的技术爱好者。接下来,我会带你深入拆解这个项目的设计思路、技术实现,并分享从零搭建和调试的完整过程。
2. 核心架构与MCP协议解析
2.1 什么是MCP?为什么是它?
要理解 inariwatch-mcp ,必须先搞懂MCP。Model Context Protocol 是由 Anthropic 公司提出的一种开放协议,你可以把它想象成 AI 世界的“USB标准” 。在物理世界,USB接口让键盘、鼠标、U盘可以即插即用到任何电脑上。在AI世界,MCP的目标是让任何工具(数据库、API、文件系统)都能以一种标准化的方式“即插即用”到任何兼容MCP的AI助手(客户端)中。
在没有MCP之前,如果你想让Claude访问你的数据库,可能需要:
- 写一个专门的插件,深度绑定Claude的特定接口。
- 或者,自己搭建一个中间API服务器,然后通过复杂的提示词工程,教AI如何调用这个API。
这两种方式都耦合严重,扩展性差。MCP的核心思想是 解耦 和 标准化 。它定义了一套简单的JSON-RPC over stdio/HTTP的通信规范,以及几个核心概念:
- 资源(Resources) : 代表AI可以读取的数据,比如一个数据库表、一个API的响应、一个文件的内容。在
inariwatch-mcp里,inventory://current就是一个资源,代表当前库存列表。 - 工具(Tools) : 代表AI可以执行的操作,每个工具都有明确的输入参数和输出格式。比如
create_sales_order就是一个工具,AI调用它时,需要提供customer_id,items等参数。 - 提示词模板(Prompts) : 预定义的对话模板,可以快速引导AI完成特定任务。
inariwatch-mcp 就是一个MCP服务器(Server),它实现了上述能力,将自己(InariWatch系统)的“资源”和“工具”暴露出去。而Claude Desktop、Cursor等则是MCP客户端(Client),它们发现并连接这个服务器,然后就能使用这些资源和工具了。这种架构的优势在于, 一次开发,多处使用 。你为InariWatch写的这个MCP服务器,理论上可以被任何兼容MCP的客户端使用,未来生态会越来越丰富。
2.2 InariWatch MCP 服务器设计思路
了解了MCP,我们再来看 orbita-pos/inariwatch-mcp 的具体设计。它的核心任务是在InariWatch的业务领域和MCP的标准协议之间架起一座桥梁。
首先,它需要认证与安全。 InariWatch作为业务系统,肯定有访问控制。MCP服务器启动时,通常会通过环境变量或配置文件读取InariWatch后端的API地址( INARI_API_BASE_URL )和认证密钥( INARI_API_KEY )。所有通过MCP发起的请求,都会由这个服务器带着有效的凭证去调用真实的InariWatch API,这样就保证了业务安全,AI客户端本身不持有敏感密钥。
其次,它需要做领域建模与协议适配。 开发者需要仔细思考:InariWatch的哪些数据和操作对AI最有价值?如何将它们映射成MCP的资源(Resources)和工具(Tools)?
- 资源映射 : 比如,将“今日销售简报”映射为一个只读资源
report://daily_sales,将“热门商品列表”映射为products://top_selling。AI可以“读取”这些资源来获取信息。 - 工具映射 : 比如,将“创建销售订单”这个API封装成一个名为
create_sales_order的工具,并明确定义其输入参数(JSON Schema)。AI在需要时会“调用”这个工具。
最后,是协议实现与通信。 项目需要选择一个MCP的SDK来简化开发。从项目名和常见技术栈推断,它很可能使用的是 TypeScript/JavaScript 生态,基于官方或社区的 @modelcontextprotocol/sdk 来构建。服务器需要实现SDK要求的生命周期(初始化、连接、关闭),并注册上述定义好的资源和工具。通信层面,MCP服务器通常以 独立进程 方式运行,通过标准输入输出(stdio)或HTTP与客户端通信,这使得它非常轻量和便携。
注意: 在设计工具时, 权限粒度 和 操作幂等性 至关重要。给AI一个“删除所有数据”的工具是极其危险的。通常只提供查询、创建等低风险操作,并且对于创建类操作,要做好重复提交的防护(例如,让工具调用在传入相同参数时返回已存在的订单,而非创建新订单)。
3. 环境准备与项目初探
3.1 前置条件与工具链
在开始动手之前,你需要准备好以下环境,这和你日常做Node.js或TypeScript项目差不多:
-
Node.js 运行环境 : 推荐使用LTS版本(如18.x, 20.x)。你可以使用
nvm(Node Version Manager) 来管理多个版本。这是运行服务器的基础。# 检查Node.js和npm版本 node --version npm --version -
代码编辑器或IDE : VS Code 是绝佳选择,它对TypeScript和JavaScript的支持非常好,而且有丰富的插件生态。务必安装 ESLint 和 Prettier 插件来保持代码风格统一。
-
Git : 用于克隆项目和版本控制。
git --version -
InariWatch 系统的访问权限 : 这是最关键的一环。你需要知道你的InariWatch后端的API地址,并且拥有一个有效的API密钥(通常具有读取库存、订单等权限)。没有这个,MCP服务器就只是一个空壳,无法获取真实数据。这部分信息通常需要从你的InariWatch系统管理员那里获取。
-
一个MCP客户端用于测试 : 你需要一个能连接MCP服务器的客户端来验证功能。最常用的就是 Claude Desktop 。你需要在其配置文件中添加你即将启动的MCP服务器信息。另一个选择是 Cursor IDE ,它也内置了MCP客户端支持。
3.2 获取与初始化项目
假设项目托管在GitHub上,我们首先克隆代码到本地:
git clone https://github.com/orbita-pos/inariwatch-mcp.git
cd inariwatch-mcp
进入项目后,第一件事是查看 package.json 文件。这个文件是项目的“说明书”,它会告诉我们项目的依赖、启动脚本等信息。
// 这是一个推测的 package.json 内容示例
{
"name": "inariwatch-mcp-server",
"version": "0.1.0",
"description": "MCP server for InariWatch POS system",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"test": "jest"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.4.0", // 核心MCP SDK
"axios": "^1.6.0", // 用于调用InariWatch API
"dotenv": "^16.3.0" // 用于加载环境变量
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"ts-node": "^10.9.0",
"jest": "^29.0.0"
}
}
从 package.json 可以看出,这是一个TypeScript项目,依赖了官方的MCP SDK、用于HTTP请求的axios以及环境变量管理工具dotenv。
接下来,安装项目依赖:
npm install
然后,我们需要创建环境变量配置文件。在项目根目录下创建一个 .env 文件(如果项目已有 .env.example ,可以复制一份):
cp .env.example .env
编辑 .env 文件,填入你的InariWatch系统凭证:
# InariWatch API 配置
INARI_API_BASE_URL=https://your-inari-instance.com/api/v1
INARI_API_KEY=your_super_secret_api_key_here
# MCP 服务器配置(可选)
MCP_SERVER_NAME=inariwatch
LOG_LEVEL=info
重要提示:
.env文件包含敏感信息, 绝对不能 提交到Git仓库中。请确保.gitignore文件中包含.env。
3.3 项目结构解析
让我们看看一个典型的 inariwatch-mcp 项目目录结构可能是什么样的:
inariwatch-mcp/
├── src/
│ ├── index.ts # 服务器主入口,初始化MCP服务器
│ ├── resources/ # 资源定义模块
│ │ ├── inventory.ts # 库存资源
│ │ ├── products.ts # 商品资源
│ │ └── reports.ts # 报表资源
│ ├── tools/ # 工具定义模块
│ │ ├── sales.ts # 销售相关工具(创建订单)
│ │ └── queries.ts # 查询类工具
│ ├── clients/ # InariWatch API客户端封装
│ │ └── inariClient.ts # 基于axios的API客户端,处理认证和请求
│ └── types/ # TypeScript类型定义
│ └── index.ts
├── dist/ # TypeScript编译后的输出目录
├── package.json
├── tsconfig.json # TypeScript配置文件
├── .env # 环境变量(本地,勿提交)
└── README.md
这种结构清晰地将MCP协议层(资源、工具)、业务逻辑层(API客户端)和类型定义分离开,是构建可维护MCP服务器的良好实践。
4. 核心模块实现深度剖析
4.1 InariWatch API 客户端封装
这是所有数据和操作的源头,一个健壮、易用的API客户端是基石。我们通常在 src/clients/inariClient.ts 中实现。
// src/clients/inariClient.ts
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import dotenv from 'dotenv';
dotenv.config(); // 加载 .env 文件中的变量
// 定义从InariWatch API返回的通用响应结构(根据实际API调整)
export interface InariApiResponse<T = any> {
success: boolean;
data: T;
message?: string;
}
// 定义一些业务实体类型
export interface InventoryItem {
id: string;
product_id: string;
product_name: string;
sku: string;
quantity: number;
location?: string;
low_stock_threshold: number;
}
export interface SalesOrderInput {
customer_id: string;
items: Array<{
product_id: string;
quantity: number;
unit_price?: number; // 可选,系统可能自动定价
}>;
notes?: string;
}
export class InariClient {
private client: AxiosInstance;
constructor() {
const baseURL = process.env.INARI_API_BASE_URL;
const apiKey = process.env.INARI_API_KEY;
if (!baseURL || !apiKey) {
throw new Error('INARI_API_BASE_URL and INARI_API_KEY must be set in environment variables.');
}
this.client = axios.create({
baseURL,
timeout: 10000, // 10秒超时
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
// 可选:添加响应拦截器进行统一错误处理
this.client.interceptors.response.use(
(response: AxiosResponse<InariApiResponse>) => {
// 如果API自身有 success: false 的标识,也抛出错误
if (response.data && response.data.success === false) {
return Promise.reject(new Error(response.data.message || 'API request failed'));
}
return response;
},
(error) => {
// 网络错误或HTTP状态码非2xx
console.error(`[InariClient] API Error: ${error.message}`);
return Promise.reject(error);
}
);
}
// 获取当前库存列表
async getInventory(): Promise<InventoryItem[]> {
const response = await this.client.get<InariApiResponse<InventoryItem[]>>('/inventory');
return response.data.data; // 返回 data 字段中的数组
}
// 根据ID获取单个商品详情
async getProduct(productId: string): Promise<any> { // 类型可细化
const response = await this.client.get<InariApiResponse>(`/products/${productId}`);
return response.data.data;
}
// 创建销售订单
async createSalesOrder(order: SalesOrderInput): Promise<{ order_id: string; total: number }> {
const response = await this.client.post<InariApiResponse<{ order_id: string; total: number }>>('/sales/orders', order);
return response.data.data;
}
// 可以继续添加其他API方法:getCustomers, getDailyReport等
}
关键点解析:
- 环境变量验证 :在构造函数中立即检查关键配置是否存在,及早失败,避免运行时出现神秘错误。
- Axios实例配置 :统一设置基础URL、超时和认证头,避免在每个请求中重复。
- 响应拦截器 :这是提升健壮性的关键。它可以将API返回的业务逻辑错误(如
success: false)和HTTP错误统一转化为JavaScript Error,便于上层(资源/工具层)用try...catch一致处理。 - 清晰的接口 :每个方法对应一个明确的业务操作,返回强类型(或至少是
any但注明)的结果。这为后续编写资源(Resource)和工具(Tool)提供了坚实基础。
4.2 资源(Resources)定义与实现
资源是AI获取信息的窗口。MCP中的资源有一个唯一的URI(如 inventory://current ),并且有对应的 mimeType (如 application/json )。服务器需要提供一个 readResource 方法来处理客户端的资源读取请求。
让我们实现库存资源,在 src/resources/inventory.ts 中:
// src/resources/inventory.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { InariClient, InventoryItem } from '../clients/inariClient.js';
// 定义资源URI的常量,避免硬编码
export const INVENTORY_RESOURCE_URI = 'inari://inventory/current';
export function setupInventoryResources(server: Server, inariClient: InariClient) {
// 1. 声明服务器提供了哪些资源(用于客户端发现)
server.setRequestHandler('resources/list', async () => {
return {
resources: [
{
uri: INVENTORY_RESOURCE_URI,
name: 'Current Inventory Levels',
description: '实时查看所有商品的当前库存数量及位置信息。',
mimeType: 'application/json',
},
// 可以在此添加更多资源,如 `inari://inventory/low-stock`
],
};
});
// 2. 处理客户端对特定资源的读取请求
server.setRequestHandler('resources/read', async (request) => {
if (request.params.uri === INVENTORY_RESOURCE_URI) {
try {
const inventoryData = await inariClient.getInventory();
// 将数据转换为MCP资源要求的格式
// 内容通常是字符串,对于JSON,我们将其序列化
return {
contents: [
{
uri: request.params.uri,
mimeType: 'application/json',
// 可以在这里对数据进行一些处理,使其对AI更友好
// 例如,只保留关键字段,或添加总结性文字
text: JSON.stringify({
summary: `当前共有 ${inventoryData.length} 种商品在库存中。`,
items: inventoryData.map(item => ({
name: item.product_name,
sku: item.sku,
quantity: item.quantity,
location: item.location || '主仓库',
isLow: item.quantity <= item.low_stock_threshold,
})),
timestamp: new Date().toISOString(),
}, null, 2), // 美化JSON输出,方便AI阅读
},
],
};
} catch (error: any) {
// 优雅地处理错误,返回给客户端
console.error(`Failed to fetch inventory: ${error.message}`);
return {
contents: [
{
uri: request.params.uri,
mimeType: 'text/plain',
text: `无法获取库存数据:${error.message}`,
},
],
};
}
}
// 如果请求的URI不是本服务器处理的,可以返回空或抛出错误
return { contents: [] };
});
}
设计要点与心得:
- URI设计 :采用
scheme://path的形式,如inari://inventory/current,清晰且有命名空间,避免冲突。 - 数据格式化 :直接返回原始API数据可能对AI来说信息过载或结构不佳。在
text字段中,我们构造了一个更清晰的结构,包含总结、物品列表和时间戳。 让数据对AI友好 是资源设计的重要原则。 - 错误处理 :不要因为后端API出错就让整个MCP请求崩溃。捕获异常并返回一个包含错误信息的纯文本资源,AI助手能够理解并告知用户“暂时无法获取数据”。
- 资源列表 :
resources/list处理程序相当于一个目录,告诉客户端“我这里有什么可以看的”。保持描述准确、有用。
4.3 工具(Tools)定义与实现
工具是AI的“手”,让AI可以执行操作。工具需要定义清晰的输入参数模式(JSON Schema)和一个执行函数。
以创建销售订单工具为例,在 src/tools/sales.ts 中:
// src/tools/sales.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { InariClient, SalesOrderInput } from '../clients/inariClient.js';
export function setupSalesTools(server: Server, inariClient: InariClient) {
// 声明服务器提供的工具列表
server.setRequestHandler('tools/list', async () => {
return {
tools: [
{
name: 'create_sales_order',
description: '在InariWatch系统中创建一个新的销售订单。需要客户ID和商品列表。',
inputSchema: {
type: 'object',
properties: {
customer_id: {
type: 'string',
description: '客户在系统中的唯一标识符(ID)。',
},
items: {
type: 'array',
description: '订单中包含的商品列表。',
items: {
type: 'object',
properties: {
product_id: {
type: 'string',
description: '商品的唯一标识符(ID)。',
},
quantity: {
type: 'number',
description: '购买数量,必须大于0。',
minimum: 1,
},
unit_price: {
type: 'number',
description: '商品单价(可选)。如果省略,将使用系统默认价格。',
minimum: 0,
},
},
required: ['product_id', 'quantity'],
},
},
notes: {
type: 'string',
description: '订单备注(可选)。',
},
},
required: ['customer_id', 'items'],
},
},
// 可以在此添加更多工具,如 `search_products`, `update_inventory` 等
],
};
});
// 处理工具调用请求
server.setRequestHandler('tools/call', async (request) => {
if (request.params.name === 'create_sales_order') {
const args = request.params.arguments as unknown as SalesOrderInput; // 信任Schema验证后的类型
// 在实际调用前,可以进行一些业务逻辑验证
if (!args.items || args.items.length === 0) {
return {
content: [
{
type: 'text',
text: '错误:订单商品列表不能为空。',
},
],
isError: true,
};
}
try {
console.log(`[Tool Call] Creating sales order for customer: ${args.customer_id}`);
const result = await inariClient.createSalesOrder(args);
// 返回一个对AI和用户都友好的成功消息
return {
content: [
{
type: 'text',
text: `✅ 销售订单创建成功!\n订单号:${result.order_id}\n订单总额:¥${result.total.toFixed(2)}\n\n系统已处理该订单,库存已相应更新。`,
},
],
};
} catch (error: any) {
console.error(`[Tool Call Failed] create_sales_order: ${error.message}`);
// 根据错误类型返回更具体的提示
let errorMessage = `创建订单失败:${error.message}`;
if (error.response?.status === 404) {
errorMessage = '创建失败:未找到指定的客户或商品,请检查ID是否正确。';
} else if (error.response?.status === 422) {
errorMessage = '创建失败:商品库存不足或数据验证错误。';
}
return {
content: [
{
type: 'text',
text: `❌ ${errorMessage}`,
},
],
isError: true,
};
}
}
// 如果调用的工具名不匹配,返回工具未找到错误
return {
content: [
{
type: 'text',
text: `错误:未知工具 "${request.params.name}"。`,
},
],
isError: true,
};
});
}
工具设计的核心经验:
- 详尽的输入模式(Input Schema) :这是AI能正确调用工具的关键。
description字段要尽可能清晰,告诉AI这个参数是什么、从哪里获取(例如,“客户在系统中的唯一标识符”)。使用required、minimum等约束条件,能帮助AI在调用前进行自我检查,减少无效调用。 - 前置验证 :即使在Schema验证通过后,在工具执行函数内部进行额外的业务逻辑验证也是好习惯(如检查商品列表非空)。这比让API调用失败后再处理更清晰。
- 友好的输出 :工具返回的
text是直接呈现给用户的(通过AI转述)。因此,信息要完整、清晰、可读。使用表情符号(如 ✅, ❌)、换行和格式化,能极大提升用户体验。 - 细致的错误处理 :捕获API调用错误,并根据HTTP状态码或错误信息,返回对用户有指导意义的错误提示,而不是堆栈跟踪。
isError: true的标记能让AI客户端知道这是一个错误结果。 - 日志记录 :在工具调用关键节点(开始、成功、失败)添加日志,对于后期调试和监控至关重要。
5. 服务器集成、启动与客户端配置
5.1 主服务器入口集成
现在我们需要把资源、工具和客户端整合起来,在 src/index.ts 中创建MCP服务器实例并启动它。
// src/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { InariClient } from './clients/inariClient.js';
import { setupInventoryResources } from './resources/inventory.js';
import { setupSalesTools } from './tools/sales.js';
// 导入其他资源和工具模块...
async function main() {
// 1. 初始化InariWatch API客户端
const inariClient = new InariClient();
console.error('[InariWatch MCP] API客户端初始化成功。'); // 使用 stderr 输出日志,避免干扰 stdio 通信
// 2. 创建MCP服务器实例
const server = new Server(
{
name: process.env.MCP_SERVER_NAME || 'inariwatch-mcp',
version: '0.1.0',
},
{
capabilities: {
// 声明服务器具备哪些能力
resources: {}, // 提供资源
tools: {}, // 提供工具
// prompts: {}, // 如果提供提示词模板,也需声明
},
}
);
// 3. 设置资源、工具等处理器
setupInventoryResources(server, inariClient);
setupSalesTools(server, inariClient);
// 设置其他模块...
// 4. 处理连接错误和关闭信号
server.onerror = (error) => {
console.error('[MCP Server Error]', error);
};
process.on('SIGINT', async () => {
await server.close();
process.exit(0);
});
// 5. 创建传输层并连接(使用标准输入输出)
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('[InariWatch MCP] 服务器已启动,正在通过 stdio 等待客户端连接...');
}
main().catch((error) => {
console.error('[Fatal Error] Failed to start server:', error);
process.exit(1);
});
5.2 编译与运行
由于我们使用TypeScript开发,需要先编译再运行,或者使用 ts-node 在开发时直接运行。
生产/测试运行:
# 编译TypeScript到JavaScript
npm run build
# 运行编译后的代码
npm start
# 或者直接运行
node dist/index.js
开发模式运行(使用ts-node,方便调试):
npm run dev
如果 package.json 中 dev 脚本配置为 ts-node src/index.ts ,这将直接执行TypeScript源码。
当服务器成功启动后,你会在控制台看到日志,然后它就会挂起,等待MCP客户端通过标准输入输出与其建立连接。
5.3 配置 Claude Desktop 客户端
要让Claude Desktop能使用我们的服务器,需要编辑其配置文件。配置文件的位置因操作系统而异:
- macOS :
~/Library/Application Support/Claude/claude_desktop_config.json - Windows :
%APPDATA%\Claude\claude_desktop_config.json - Linux :
~/.config/Claude/claude_desktop_config.json
如果文件不存在,就创建它。然后添加以下配置:
{
"mcpServers": {
"inariwatch": {
"command": "node",
"args": [
"/ABSOLUTE/PATH/TO/YOUR/inariwatch-mcp/dist/index.js"
],
"env": {
"INARI_API_BASE_URL": "https://your-inari-instance.com/api/v1",
"INARI_API_KEY": "your_super_secret_api_key_here",
"LOG_LEVEL": "debug"
}
}
}
}
配置详解与注意事项:
-
command: 是启动服务器的命令。因为我们编译成了Node.js可执行文件,所以是node。 -
args: 是传递给命令的参数。 这里必须使用dist/index.js的绝对路径 。相对路径在Claude Desktop的上下文中可能无法正确解析。你可以使用pwd命令获取项目的绝对路径。 -
env: 这里设置的环境变量会覆盖项目.env文件中的设置。 这是一种更安全的做法 ,尤其是对于API密钥,你可以将其保留在Claude的配置中,而不是项目代码目录里。注意,在配置文件中明文存储密钥仍有风险,请确保系统安全。 - 配置完成后, 需要完全重启Claude Desktop 才能加载新的MCP服务器配置。
重启Claude后,当你新建一个对话,理论上在输入框附近或设置里,应该能看到一个“连接的工具”或类似提示,显示 inariwatch 已可用。你也可以直接问Claude:“你现在可以使用哪些工具?” 或者 “你能访问InariWatch的库存信息吗?”,它应该能列出你定义的工具和资源。
6. 调试、测试与问题排查实录
即使一切配置看起来都正确,第一次运行时也难免会遇到问题。下面是我在搭建和调试过程中遇到的一些典型情况及解决方法。
6.1 常见问题速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| Claude Desktop 完全看不到MCP服务器 | 1. 配置文件路径或格式错误。 2. 服务器启动命令失败。 3. Claude Desktop 未重启。 |
1. 检查配置文件JSON语法,确保无错误。 2. 在终端手动运行配置中的 command 和 args ,看服务器能否独立启动并打印日志。 3. 务必彻底关闭并重启Claude Desktop 。 |
服务器启动后立即退出,报错 Error: INARI_API_BASE_URL and INARI_API_KEY must be set |
环境变量未正确设置。 | 1. 检查 .env 文件是否存在且格式正确(无空格,无引号)。 2. 如果通过Claude配置的 env 设置,确保键名正确。 3. 在服务器代码启动处临时添加 console.log(process.env) 打印所有环境变量进行调试。 |
| Claude 能看到工具/资源列表,但调用时失败,提示“无法连接”或超时 | 1. InariWatch API 地址不可达。 2. API 密钥无效或权限不足。 3. 服务器代码中存在未处理的异常导致崩溃。 |
1. 使用 curl 或 Postman 直接测试 INARI_API_BASE_URL 的端点,验证网络和认证。 2. 在 InariClient 的构造函数或方法中添加详细日志,查看请求是否发出、响应是什么。 3. 确保服务器代码有全面的 try...catch ,并将错误信息通过MCP协议返回,而不是让进程崩溃。 |
| 工具调用时,AI传递的参数格式不对 | 1. 工具的 inputSchema 定义不够严格或描述不清。 2. AI对业务逻辑理解有偏差。 |
1. 仔细检查 inputSchema 的 required 字段和每个属性的 description 。描述应尽可能指导AI如何获取该参数(如“客户的ID,可以从客户列表资源中获取”)。 2. 在工具的实现函数开头打印接收到的 args ,确认实际传入的数据结构。 |
| 资源可以读取,但AI无法理解内容 | 资源返回的 text 格式对AI不友好。 |
优化资源返回的数据结构。避免嵌套过深的JSON。可以添加一个 summary 字段用自然语言总结,或将列表数据以更清晰的键值对形式呈现。目标是让AI能轻松提取关键信息。 |
| 服务器日志正常,但Claude中无反应 | MCP通信协议版本不匹配或序列化问题。 | 1. 检查使用的 @modelcontextprotocol/sdk 版本是否与Claude Desktop兼容。查看SDK和Claude的更新日志。 2. 确保服务器返回的JSON严格符合MCP协议规范。使用简单的 console.error 输出原始请求和响应对象进行对比分析。 |
6.2 高级调试技巧:启用详细日志
在开发阶段,开启详细日志是定位问题的利器。除了在代码中手动添加 console.error (注意MCP服务器建议将日志输出到 stderr ),还可以利用环境变量控制日志级别。
修改你的 InariClient 和主服务器文件,引入一个简单的日志函数:
// src/utils/logger.ts
const LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLowerCase();
export function debug(...args: any[]) {
if (LOG_LEVEL === 'debug') {
console.error('[DEBUG]', new Date().toISOString(), ...args);
}
}
export function info(...args: any[]) {
if (LOG_LEVEL === 'debug' || LOG_LEVEL === 'info') {
console.error('[INFO]', new Date().toISOString(), ...args);
}
}
export function error(...args: any[]) {
console.error('[ERROR]', new Date().toISOString(), ...args);
}
然后在客户端和工具调用处使用:
import { debug, info } from '../utils/logger.js';
// 在 InariClient 请求前
debug(`Making API call to: ${url}`);
// 在 tools/call 处理程序中
info(`Tool called: ${request.params.name} with args:`, JSON.stringify(args));
在Claude配置或启动命令中设置 LOG_LEVEL=debug ,你就能在控制台看到所有详细的请求和响应信息,这对于理解AI与服务器的交互流程非常有帮助。
6.3 安全与生产环境考量
当这个MCP服务器准备投入生产环境使用时,有几个关键点必须考虑:
- 权限最小化 :授予MCP服务器使用的API密钥 绝对最小 的权限。通常只赋予“读取”库存、商品和“创建”订单的权限,绝不要赋予“删除”或“修改系统设置”等高风险权限。
- 输入验证与净化 :虽然MCP SDK和工具的JSON Schema会做基础验证,但在调用真实业务API前,务必再次验证数据。例如,检查商品ID是否存在、数量是否为正整数等。
- 速率限制与防滥用 :考虑在MCP服务器层面添加简单的速率限制(例如,使用
express-rate-limit如果走HTTP传输,或自定义计数器),防止被恶意工具调用轰炸。 - 配置管理 :生产环境的API密钥、URL等不应写在代码或普通配置文件中。应使用安全的密钥管理服务(如AWS Secrets Manager, HashiCorp Vault)或至少是容器环境变量。
- 进程监控与守护 :确保MCP服务器进程在异常退出时能自动重启。可以使用
systemd,pm2或容器编排平台(如Kubernetes)的健康检查来实现。
7. 扩展思路与最佳实践
一个基础的MCP服务器运行起来后,你可以从以下几个方向扩展它,使其更加强大和实用。
7.1 实现更复杂的工具链
- 链式工具调用 :设计工具时,考虑它们的组合性。例如,一个
find_customer_by_phone工具返回客户ID,然后这个ID可以直接用于create_sales_order工具。在工具描述中明确说明这一点。 - 查询类工具 :实现
search_products工具,允许AI根据名称、SKU或分类模糊搜索商品,并返回一个列表供用户或AI选择。 - 只读操作工具 :实现
get_daily_sales_summary工具,它可能内部调用多个API,聚合数据后返回一份格式精美的文本摘要,比单纯的资源读取更智能。
7.2 设计对AI更友好的资源
- 聚合资源 :创建一个
dashboard://overview资源,它内部调用库存、当日销售、低库存预警等多个API,整合成一个全面的“仪表盘”视图,让AI能一次性获取业务全景。 - 动态资源URI :支持带参数的资源URI,如
inari://product/{id}。在resources/read处理程序中解析URI中的变量,然后动态查询对应商品的信息。 - 资源更新通知 :虽然MCP协议本身是请求-响应式,但你可以通过设计,让资源的内容摘要反映出“最后更新时间”,提示AI信息的时效性。
7.3 性能优化
- 缓存策略 :对于不常变化的数据(如商品分类、门店列表),可以在MCP服务器内存中设置短期缓存(例如,使用
node-cache),减少对后端API的重复调用,提升AI助手的响应速度。 - 批量操作 :如果AI经常需要同时查询多个商品的信息,可以考虑实现一个
get_multiple_products的工具或资源,接受ID数组,服务器内部批量调用API,比多次单个查询更高效。
7.4 测试策略
- 单元测试 :为
InariClient和工具函数编写单元测试,使用jest和axios-mock-adapter来模拟API响应,确保业务逻辑正确。 - 集成测试 :编写一个简单的测试脚本,模拟MCP客户端向你的服务器发送标准的JSON-RPC请求(如
tools/list,tools/call),验证端到端的流程。 - 端到端测试 :在安全的环境下,配置好Claude Desktop,进行真实的人工对话测试,覆盖各种正常和边缘用例。
构建 inariwatch-mcp 这样的项目,最大的成就感来自于看到冰冷的业务系统通过一个协议,突然拥有了自然语言的交互界面。它不仅仅是技术集成,更是工作流的一次革新。从我的经验来看, 起步时功能不必求全,但每个暴露给AI的工具或资源都必须极其可靠和安全 。先实现一个最小可行集(如查询库存和创建订单),稳定运行并收集用户反馈,再逐步迭代扩展,这样能最有效地控制风险并持续交付价值。
更多推荐



所有评论(0)