基于MCP协议构建AI数据连接器:从原理到SQL查询服务器实践
1. 项目概述一个连接AI与数据源的“翻译官”最近在折腾AI应用开发特别是想让大语言模型LLM能直接、安全地访问我自己的数据库、API或者文件系统时遇到了一个普遍难题怎么让AI理解并操作这些外部数据源直接给模型开放数据库连接安全和可控性简直是噩梦。自己为每个数据源写一套复杂的提示词和函数调用维护成本高且难以复用。就在这个当口我深入研究了basegridio/mcp-server这个项目它本质上是一个实现了Model Context Protocol (MCP)的服务端。你可以把它理解为一个专为AI模型设计的“标准化数据连接器”或“翻译官”。MCP是由Anthropic提出的一种开放协议旨在为LLM提供一个标准化的方式来发现、调用外部工具和数据源。而mcp-server就是具体实现这个协议的服务端程序。它的核心价值在于将复杂的数据源访问逻辑封装成一个个标准的“工具”Tools和“资源”Resources然后通过统一的协议暴露给AI客户端比如Claude Desktop、Cursor等支持MCP的AI应用。这意味着开发者只需编写一次mcp-server任何兼容MCP的AI客户端就都能安全、可控地调用它背后的数据能力。举个例子我写了一个mcp-server连接到公司内部的PostgreSQL数据库并定义了“查询本周销售数据”、“根据产品ID查找库存”等工具。那么无论是在Claude Desktop的聊天窗口里还是在Cursor的AI编程助手中我都可以直接让AI“帮我查一下上周的销售Top 10”AI会通过MCP协议调用我写的server拿到结构化数据后再组织成自然语言回复我。整个过程AI模型本身并不直接接触数据库连接字符串它只是在调用一个被严格定义和权限控制的“工具”这极大地提升了安全性和可控性。这个项目适合任何想要扩展AI能力边界、让其与私有或特定数据源交互的开发者、数据分析师和运维人员。无论你是想给内部知识库加一个AI问答入口还是想让AI辅助进行日常的数据查询与报告生成mcp-server都提供了一个优雅且未来的解决方案。2. MCP协议核心思想与项目架构拆解要理解basegridio/mcp-server的价值必须先搞懂MCP协议到底在解决什么问题。在传统AI应用开发中我们常通过Function Calling函数调用的方式让模型使用外部工具。但这通常需要开发者1在应用代码里硬编码工具列表和调用逻辑2精心设计提示词来描述这些工具3处理复杂的身份验证和错误处理。这种方式耦合度高且工具定义无法在不同AI应用间共享。MCP协议的核心思想是“关注点分离”和“标准化接口”。它将工具提供者Server和工具消费者Client完全解耦。2.1 协议的核心组件Server服务端即basegridio/mcp-server这类实现。它的职责是声明能力告诉客户端“我有哪些工具Tools可用”。每个工具都有名称、描述、输入参数JSON Schema定义。暴露资源告诉客户端“我有哪些静态或动态资源Resources可读”。资源可以是一个文本文件、一个网页内容或者一个数据库查询的模板。执行调用当客户端请求调用某个工具时服务端执行相应的业务逻辑如查询数据库、调用API并返回结构化的结果。推送通知当服务端管理的资源发生变化时如一个日志文件更新了可以主动通知客户端。Client客户端如Claude Desktop、集成MCP的IDE等。它的职责是发现与连接发现可用的MCP服务器并建立连接通常通过SSE或Stdio。呈现与决策将服务器提供的工具和资源信息整合到上下文中供AI模型决策何时调用。请求与转发根据AI模型的决策向服务器发起工具调用请求并将结果返回给模型。Transport传输层定义Server和Client如何通信。MCP主要支持两种方式Stdio标准输入输出最常见的方式。Server作为一个子进程启动通过stdin/stdout与Client进行JSON-RPC消息交换。部署简单适合本地工具。SSEServer-Sent Events基于HTTP允许远程连接更适合网络化部署。2.2 basegridio/mcp-server 的项目定位basegridio/mcp-server项目提供了一个用TypeScript/JavaScript编写MCP服务器的脚手架、工具库和最佳实践示例。它不是一个开箱即用、配置一下就能连数据库的产品而是一个开发框架。它帮你处理了与MCP协议底层通信的复杂性JSON-RPC消息的序列化/反序列化、生命周期管理让你可以专注于实现你自己的工具逻辑。它的架构通常包含以下几个关键部分协议适配层封装了与MCP Client通信的细节你不需要直接处理原始的JSON-RPC消息。工具注册与管理器提供优雅的API让你定义和注册工具函数。资源注册与管理器用于定义可读的资源及其内容。示例与模板提供多个连接不同数据源如文件系统、SQLite、HTTP API的示例代码是快速上手的关键。注意不要指望clone下来就能直接运行一个万能服务器。你需要基于它提供的范例编写符合你自己业务逻辑的代码。这是“基础设施”代码而非“应用”软件。3. 从零开始构建一个SQL查询MCP服务器理论讲得再多不如动手做一个。假设我们有一个简单的需求构建一个MCP服务器让AI能安全地查询我们产品数据库中的users表仅包含id, name, email字段和orders表包含id, user_id, amount, status字段。我们将使用basegridio/mcp-server的模板和SQLite来模拟。3.1 环境准备与项目初始化首先确保你的环境有Node.js建议18和npm。然后我们可以直接使用项目提供的模板来加速创建过程。虽然项目本身是一个库但通常我们可以参考其源码结构或使用类似的脚手架。# 创建一个新目录作为我们的服务器项目 mkdir my-sql-mcp-server cd my-sql-mcp-server # 初始化npm项目 npm init -y # 安装核心依赖modelcontextprotocol/sdk 是Anthropic官方的MCP SDK是必须的。 # 同时安装SQLite3和必要的类型定义。 npm install modelcontextprotocol/sdk sqlite3 npm install --save-dev typescript types/node types/sqlite3 tsx # 初始化TypeScript配置 npx tsc --init修改生成的tsconfig.json确保设置正确{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src/**/*], exclude: [node_modules] }3.2 构建核心服务器逻辑在src目录下创建入口文件index.ts。我们将在这里创建服务器实例、定义工具并启动服务。// src/index.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListToolsRequestSchema } from modelcontextprotocol/sdk/types.js; import sqlite3 from sqlite3; import { open } from sqlite; // 1. 初始化SQLite数据库模拟真实数据源 async function initDatabase() { const db await open({ filename: ./demo.db, driver: sqlite3.Database }); // 创建示例表并插入一些测试数据 await db.exec( DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS orders; CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL ); CREATE TABLE orders ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, amount REAL NOT NULL, status TEXT CHECK(status IN (pending, paid, shipped, cancelled)), FOREIGN KEY (user_id) REFERENCES users (id) ); INSERT INTO users (id, name, email) VALUES (1, Alice, aliceexample.com), (2, Bob, bobexample.com), (3, Charlie, charlieexample.com); INSERT INTO orders (id, user_id, amount, status) VALUES (101, 1, 99.99, paid), (102, 2, 149.99, shipped), (103, 1, 79.99, pending), (104, 3, 199.99, paid); ); return db; } // 2. 创建MCP服务器实例 const server new Server( { name: my-sql-demo-server, version: 0.1.0, }, { capabilities: { tools: {}, // 声明我们支持工具 }, } ); // 3. 定义工具查询用户列表 server.setRequestHandler(ListToolsRequestSchema, async () { return { tools: [ { name: query_users, description: 根据条件查询用户列表。可以通过姓名进行模糊搜索。, inputSchema: { type: object, properties: { name_filter: { type: string, description: 用于过滤用户姓名的关键词支持模糊匹配。例如“Ali” 可以匹配 “Alice”。, }, limit: { type: number, description: 返回结果的最大数量默认是10。, default: 10, } }, }, }, { name: query_orders, description: 查询订单信息。可以按用户ID、订单状态或金额范围过滤。, inputSchema: { type: object, properties: { user_id: { type: number, description: 筛选特定用户的订单。, }, status: { type: string, description: 按订单状态筛选可选值pending, paid, shipped, cancelled。, enum: [pending, paid, shipped, cancelled] }, min_amount: { type: number, description: 订单最小金额。, }, max_amount: { type: number, description: 订单最大金额。, } }, }, }, { name: get_user_order_summary, description: 获取指定用户的订单汇总信息包括订单总数和总金额。, inputSchema: { type: object, properties: { user_id: { type: number, description: 用户ID, required: true } }, required: [user_id] }, } ], }; }); // 4. 处理工具调用请求 server.setRequestHandler(CallToolRequestSchema, async (request) { const db await initDatabase(); // 在实际应用中数据库连接应该复用 const { name, arguments: args } request.params; try { switch (name) { case query_users: { let query SELECT id, name, email FROM users WHERE 11; const params: any[] []; if (args?.name_filter) { query AND name LIKE ?; params.push(%${args.name_filter}%); } query LIMIT ?; params.push(args?.limit || 10); const users await db.all(query, params); return { content: [{ type: text, text: JSON.stringify(users, null, 2) }], }; } case query_orders: { let query SELECT o.id, o.amount, o.status, u.name as user_name FROM orders o JOIN users u ON o.user_id u.id WHERE 11 ; const params: any[] []; if (args?.user_id) { query AND o.user_id ?; params.push(args.user_id); } if (args?.status) { query AND o.status ?; params.push(args.status); } if (args?.min_amount ! undefined) { query AND o.amount ?; params.push(args.min_amount); } if (args?.max_amount ! undefined) { query AND o.amount ?; params.push(args.max_amount); } const orders await db.all(query, params); return { content: [{ type: text, text: JSON.stringify(orders, null, 2) }], }; } case get_user_order_summary: { if (!args || typeof args.user_id ! number) { throw new Error(参数 user_id 为必填且必须为数字); } const summary await db.get( SELECT COUNT(*) as total_orders, SUM(amount) as total_amount, AVG(amount) as avg_amount FROM orders WHERE user_id ?, [args.user_id] ); return { content: [{ type: text, text: 用户 #${args.user_id} 的订单汇总 - 订单总数${summary.total_orders} - 总金额${summary.total_amount?.toFixed(2) || 0.00} - 平均订单金额${summary.avg_amount?.toFixed(2) || 0.00} }], }; } default: throw new Error(未知的工具: ${name}); } } catch (error) { console.error(执行工具 ${name} 时出错:, error); return { content: [{ type: text, text: 错误: ${error instanceof Error ? error.message : String(error)} }], isError: true, }; } finally { await db.close(); } }); // 5. 启动服务器使用Stdio传输层这是与Claude Desktop等客户端通信的标准方式 async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP SQL Server 已启动并运行在 stdio 上); } main().catch((error) { console.error(服务器启动失败:, error); process.exit(1); });3.3 编译与运行在package.json中添加启动脚本{ scripts: { build: tsc, start: node dist/index.js, dev: tsx watch src/index.ts } }现在你可以使用开发模式运行npm run dev服务器会启动并监听标准输入输出。但这还不够我们需要一个MCP客户端来测试它。最方便的方式是配置Claude Desktop。4. 配置Claude Desktop连接自定义MCP服务器Claude Desktop是目前对MCP支持最友好的客户端之一。要让Claude识别并使用我们刚写的服务器需要进行配置。4.1 定位Claude Desktop配置目录macOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.jsonLinux:~/.config/Claude/claude_desktop_config.json如果文件或目录不存在手动创建即可。4.2 编写配置文件编辑claude_desktop_config.json添加我们的mcp-server配置。这里的关键是告诉Claude如何启动我们的服务器进程通过命令行。{ mcpServers: { my-sql-demo: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/PROJECT/my-sql-mcp-server/dist/index.js ], env: { // 可以在这里传递环境变量比如数据库连接字符串 NODE_ENV: production } } } }重要提示command是启动你服务器的命令。因为我们用Node.js写的所以是node。args数组里的第一个元素必须是你编译后的JavaScript文件的绝对路径。不能使用相对路径./dist/index.jsClaude在启动子进程时工作目录可能不同。如果你在开发中想直接运行TypeScript源码可以配置为npx和tsx但生产环境建议使用编译后的JS文件以提高启动速度和稳定性。4.3 验证连接保存配置文件。完全重启Claude Desktop应用重要修改配置后必须重启。重启后新建一个对话。如果配置成功你通常不会看到明显的提示但当你输入类似“你能用什么工具”或直接提问时Claude会意识到它多了一些可用的工具。你可以尝试提问“query_users这个工具是做什么的”或者直接下达指令“请使用query_orders工具帮我查一下所有状态为‘paid’的订单。”如果一切正常Claude会理解你的指令在后台通过MCP协议调用你的服务器执行SQL查询并将返回的JSON数据解读后以友好的格式呈现给你。实操心得在配置args路径时最容易出错。在macOS/Linux上可以使用pwd命令获取项目绝对路径。另外确保你的Node脚本在独立运行时没有问题node dist/index.js。调试时可以暂时在脚本开头加一句console.error(“MCP Server Starting...”)这些日志会输出到Claude Desktop的日志中对于排查启动失败问题非常有帮助。5. 高级功能实现与安全加固上面的例子是一个最简单的演示。在实际生产环境中我们需要考虑更多。5.1 实现资源Resources提供除了工具ToolsMCP另一个核心概念是资源Resources。工具用于“执行操作”而资源用于“提供内容”。例如你可以将一份产品手册、一个API文档模板或一个常用的SQL查询语句定义为资源AI可以直接读取这些资源的内容作为上下文。在index.ts中我们可以在定义工具的同时定义资源// 在 setRequestHandler(ListToolsRequestSchema, ...) 附近添加资源列表处理器 import { ListResourcesRequestSchema, ReadResourceRequestSchema } from modelcontextprotocol/sdk/types.js; server.setRequestHandler(ListResourcesRequestSchema, async (request) { // 可以动态生成资源列表例如基于数据库表结构 return { resources: [ { uri: resource://my-sql-demo/schema/users, mimeType: application/json, name: 用户表结构, description: users表的字段定义说明。 }, { uri: resource://my-sql-demo/schema/orders, mimeType: application/json, name: 订单表结构, description: orders表的字段定义说明。 }, { uri: resource://my-sql-demo/docs/query_guide, mimeType: text/plain, name: 查询指南, description: 如何使用本服务器工具的简要说明。 } ], }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) { const { uri } request.params; switch (uri) { case resource://my-sql-demo/schema/users: return { contents: [{ uri: uri, mimeType: application/json, text: JSON.stringify({ table: users, fields: [ { name: id, type: INTEGER, description: 主键用户唯一标识 }, { name: name, type: TEXT, description: 用户姓名 }, { name: email, type: TEXT, description: 用户邮箱唯一 } ] }, null, 2) }]; }; case resource://my-sql-demo/docs/query_guide: return { contents: [{ uri: uri, mimeType: text/plain, text: # SQL查询服务器使用指南\n\n可用工具\n1. query_users: 查询用户。\n2. query_orders: 查询订单。\n3. get_user_order_summary: 获取用户订单汇总。\n\n提示在请求时请清晰描述你的过滤条件。 }]; }; default: throw new Error(资源未找到: ${uri}); } });这样当AI客户端如Claude连接到你的服务器时它不仅能知道有哪些工具可用还能知道有哪些静态资源可以读取。AI可以在需要时主动读取query_guide来了解如何更好地使用你的工具。5.2 安全与权限控制这是企业级应用中最关键的一环。我们的服务器绝不能成为数据泄露的后门。输入验证与SQL注入防护上面的示例使用了参数化查询?占位符这是防止SQL注入的底线。永远不要用字符串拼接的方式将用户输入拼接到SQL语句中。工具权限粒度控制不是所有连接上来的AI客户端都应该有所有工具的权限。我们可以在工具调用处加入身份验证逻辑。MCP协议本身不规定认证方式一种常见模式是通过env环境变量传递令牌。修改Claude配置传递一个密钥env: { MCP_SERVER_API_KEY: your-secret-token-here }在服务器代码中校验// 在 server.setRequestHandler(CallToolRequestSchema, ...) 的开头 const apiKeyFromEnv process.env.MCP_SERVER_API_KEY; // 可以从请求的某个地方获取客户端传来的令牌MCP标准未定义需自定义 // 例如可以约定通过某个特殊的初始化参数传递这里仅为示例 const clientKey request.params._auth?.key; // 假设的字段 if (apiKeyFromEnv apiKeyFromEnv ! clientKey) { throw new Error(未授权的访问); }查询范围与行数限制务必在所有查询工具中强制加上LIMIT子句防止AI无意中发起一个SELECT * FROM huge_table的请求拖垮数据库。示例中我们已经做了这个限制。敏感数据脱敏在返回数据前对邮箱、手机号等敏感字段进行脱敏处理如aliceexample.com-a***eexample.com。连接池与性能示例中每次调用都打开和关闭数据库连接这在生产环境中是低效的。应该使用数据库连接池并在服务器初始化时建立在整个生命周期内复用。5.3 错误处理与日志健壮的错误处理能提升体验。除了在工具调用内部try-catch还应该设置全局的未捕获异常处理器并记录结构化日志。// 全局错误处理 process.on(uncaughtException, (error) { console.error(未捕获的异常:, error); // 根据策略决定是否退出进程 }); // 在工具调用返回错误时提供更友好的信息 // 我们在上面的代码中已经返回了 isError: true 和错误信息。 // 可以进一步分类错误比如参数错误、数据库错误、权限错误等。6. 部署与运维考量开发完成后你需要考虑如何部署这个MCP服务器。打包与分发对于团队使用最简单的方式是将整个项目打包并编写一个简单的安装脚本帮助同事配置Claude Desktop。你可以将编译后的dist目录、package.json和配置说明一起打包。远程SSE服务器Stdio模式适合本地。如果你希望多个用户或远程服务能连接同一个MCP服务器需要实现SSE传输层。这意味着你需要构建一个HTTP服务器。modelcontextprotocol/sdk也提供了SSEServerTransport你可以基于Express.js或Fastify快速搭建。import express from express; import { SSEServerTransport } from modelcontextprotocol/sdk/server/sse.js; const app express(); const sseTransport new SSEServerTransport(/mcp, server); app.use(sseTransport.handleRequest.bind(sseTransport)); app.listen(3000);然后Claude Desktop的配置需要从command模式改为url模式{ mcpServers: { my-remote-sql-demo: { url: http://localhost:3000/mcp } } }注意远程部署必须考虑HTTPS、身份认证如API Key、跨域(CORS)等网络安全问题。监控与健康检查为你的SSE服务器添加一个健康检查端点如/health方便运维监控。版本管理当你更新服务器工具或资源时考虑版本兼容性。可以在服务器声明中增加版本号并在Claude配置中指定允许的版本范围。7. 常见问题与排查技巧实录在实际开发和集成过程中我踩过不少坑这里总结一下最常见的问题和解决方法。问题现象可能原因排查步骤与解决方案Claude Desktop重启后没有发现新工具。1. 配置文件路径错误。2. 配置文件格式错误JSON语法。3. Claude Desktop未完全重启。1. 检查claude_desktop_config.json的路径是否正确。2. 使用JSON验证工具如jsonlint检查配置文件。3. 彻底退出Claude Desktop包括任务栏/托盘图标再重新启动。配置正确但Claude提示“无法连接到MCP服务器”或工具列表为空。1. Node脚本启动失败。2. 脚本路径错误或权限不足。3. 服务器代码存在语法错误提前退出。1.查看日志这是最重要的步骤。在终端直接运行你的服务器命令node /path/to/index.js看是否有错误输出。2. 确保Node版本符合要求。3. 在服务器代码入口处添加console.error(‘启动…’)在Claude的日志中搜索此信息确认进程是否真的启动了。工具调用后AI返回“调用工具XXX时出错”。1. 工具处理函数抛出未捕获的异常。2. 返回的数据格式不符合MCP协议。1. 在服务器的catch块中打印详细错误console.error(error)。2. 确保返回的content字段是数组且内部对象格式正确type和text。3. 检查数据库连接是否正常查询语句是否正确。AI无法正确理解或选择我提供的工具。1. 工具描述description不够清晰。2. 输入参数模式inputSchema定义模糊。1.优化描述用自然语言清晰说明工具的用途、适用场景。例如“查询订单”不如“根据用户ID、状态或金额范围过滤查询订单列表返回订单ID、金额、状态及对应用户姓名”。2.细化参数为每个参数提供清晰的description和example在schema中。使用enum限制可选值。服务器运行一段时间后崩溃或无响应。1. 资源泄露如数据库连接未关闭。2. 未处理进程信号。1. 使用连接池并确保在服务器关闭时正确释放资源。2. 添加进程信号监听优雅关闭process.on(‘SIGINT’, () { server.close(); db.close(); });一个关键的调试技巧你可以暂时修改Claude Desktop的配置将服务器的command改为/bin/bash或cmd.exeargs改为[“-c”, “node /path/to/server.js 21”]Unix或[“/C”, “node \path\to\server.js 21”]Windows这样服务器的标准错误输出会直接打印到Claude的日志中非常利于调试启动问题。构建basegridio/mcp-server这样的项目最大的成就感来自于看到AI从一个纯粹的文本生成器变成了一个能安全、精准操作你私有数据的智能助手。它不再空谈而是能真正“做事”。这个过程的关键在于清晰的定义工具和资源、严谨的实现安全与错误处理和耐心的调试客户端集成。当你对接到第三个、第四个数据源时你会发现这套模式的威力——一次编写处处可用。无论是连接内部CRM、分析日志文件还是控制智能家居MCP协议提供了一条将AI能力与真实世界连接起来的标准化高速公路而你的mcp-server就是这条路上的关键枢纽。

相关新闻

最新新闻

日新闻

周新闻

月新闻