10分钟构建MCP Server:让AI编程助手连接你的数据库

📅 2026/8/10 5:33:32
10分钟构建MCP Server:让AI编程助手连接你的数据库
1. 为什么MCP Server是AI编程助手的“外挂大脑”如果你最近在用Cursor或者Claude Desktop这类AI编程工具可能会发现一个现象当你想让它帮你分析项目里某个数据库表的结构或者让它基于现有数据生成一段SQL查询时它常常会“一本正经地胡说八道”。它可能会凭空捏造一个不存在的字段或者写出一段语法正确但逻辑完全跑偏的代码。这背后的根本原因是这些AI模型的知识库存在“信息差”——它们无法实时、准确地访问到你项目里最核心的私有数据源比如数据库、API接口、内部文档或者特定的工具链。这就是MCPModel Context Protocol协议要解决的核心痛点。你可以把它理解为一个标准化的“插件接口”或“数据通道协议”。它由Anthropic公司牵头设计目标就是让AI模型能够安全、可控地连接到外部工具和数据源从而极大地扩展其能力边界。一个实现了MCP协议的服务器我们称之为MCP Server它的角色就是AI模型的“外挂大脑”或“专属信息库”。想象一下你有一个本地运行的PostgreSQL数据库里面存放着你项目的用户表和订单表。传统的做法是你需要在对话中手动把表结构DDL复制粘贴给AI既繁琐又容易出错。而通过MCP你可以编写一个轻量的MCP Server让它主动将数据库的Schema甚至是在你授权下的部分数据以结构化的方式“喂”给Cursor。之后当你在Cursor中提问“我们的users表有哪些字段”或者“帮我写一个查询上个月活跃用户的SQL”时Cursor背后的AI模型就能通过你写的这个Server获取到百分百准确、实时的信息并给出极其精准的回答和代码。今天我们就用TypeScript在10分钟内动手构建一个能够连接你个人数据库的MCP Server并把它无缝集成到Cursor中。这个实践不仅能让你立刻体验到AI编程助手能力的质变更能让你理解未来AI Agent智能体如何与真实世界交互的基本范式。2. 环境准备从零搭建TypeScript MCP项目骨架在开始敲代码之前我们需要一个干净、高效的开发环境。TypeScript是我们的首选因为它能提供良好的类型提示这对于实现一个需要遵循特定协议的Server来说至关重要能避免很多低级错误。2.1 初始化项目与核心依赖安装首先创建一个新的项目目录并初始化。这里我们使用pnpm作为包管理器因为它速度更快磁盘空间利用更高效。当然使用npm或yarn也完全没问题。mkdir mcp-database-server cd mcp-database-server pnpm init -y接下来安装TypeScript和必要的类型定义。我们需要types/node因为我们的Server将运行在Node.js环境中。pnpm add -D typescript types/node tsx pnpm add modelcontextprotocol/sdk这里解释一下这几个包的作用typescript TypeScript编译器本身。types/node 提供了Node.js核心模块如fspath的TypeScript类型定义。tsx 一个极佳的工具它允许我们直接运行.ts文件无需先手动编译成.js极大地简化了开发流程。在package.json的scripts中我们会用到它。modelcontextprotocol/sdk 这是Anthropic官方提供的MCP SDK for JavaScript/TypeScript。它封装了MCP协议底层复杂的通信细节如JSON-RPC over stdio提供了高层次的、类型安全的类和方法让我们可以像写普通后端服务一样专注于实现工具Tools和资源Resources的逻辑。这是本项目的核心依赖。2.2 配置TypeScript与项目结构初始化TypeScript配置。运行以下命令生成tsconfig.json文件npx tsc --init生成后我们需要对其进行一些关键调整以适配现代Node.js和模块开发。打开tsconfig.json确保或修改以下配置{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true }, include: [src/**/*], exclude: [node_modules] }关键配置解读target: ES2022 使用较新的ECMAScript标准以获得更好的性能和语言特性支持。module: NodeNext和moduleResolution: NodeNext 这是为了正确支持Node.js的ES模块ESM和CommonJSCJS的混合生态。MCP SDK可能以ESM形式发布这样配置能确保模块导入无误。outDir和rootDir 明确指定源代码目录和编译输出目录保持项目结构清晰。resolveJsonModule: true 允许直接导入.json文件方便我们后续可能读取配置。现在创建项目的基本结构mkdir src touch src/index.ts最后编辑package.json添加启动脚本和明确项目类型。在package.json的scripts区块中添加{ scripts: { dev: tsx watch src/index.ts, build: tsc, start: node dist/index.js }, type: module }dev: tsx watch src/index.ts 这是我们开发时的主要命令。tsx watch会监视src/index.ts文件的变动并实时重新运行实现热重载效果非常适合调试。build和start 分别用于构建生产版本和运行构建后的代码。type: module 声明本项目使用ES模块规范。这对于正确使用MCP SDK和一些现代Node.js库很重要。至此一个类型安全、开发体验流畅的TypeScript MCP项目骨架就搭建完毕了。你可以运行pnpm dev来测试环境是否正常虽然现在index.ts还是空的但应该不会报错。3. 构建你的第一个MCP Server核心概念与初始化理解了MCP的基本理念也搭好了开发环境现在让我们深入MCP Server的内部看看它究竟由哪些核心部件构成并写出第一行有实际意义的代码。3.1 解剖MCP ServerTools, Resources 与 Server一个MCP Server的核心功能是向AI客户端如Cursor提供两类东西工具Tools和资源Resources。你可以把它们类比为函数和变量或者API和数据端点。工具Tools 这是AI可以主动调用的“函数”。每个工具都有名称、描述、输入参数Schema和实际的执行逻辑。例如一个名为query_database的工具AI可以调用它并传入一个SQL字符串Server执行查询后返回结果。工具代表了AI的“主动能力”。资源Resources 这是AI可以被动读取的“数据”或“文档”。每个资源有一个唯一的URI如db://schema/users和对应的文本或结构化内容。AI在需要了解某些信息时可以“读取”这些资源。例如你可以提供一个资源来展示数据库的ER图或者一个API的Swagger文档。资源代表了AI的“知识背景”。Server 这是由modelcontextprotocol/sdk提供的Server类实例。它负责管理所有已注册的工具和资源并处理与客户端之间基于JSON-RPC over stdio标准输入输出的通信协议。我们不需要关心网络SocketMCP协议设计为通过标准流通信这使得集成非常简单。3.2 初始化Server并定义第一个工具让我们在src/index.ts中开始编码。首先导入必要的模块并创建一个Server实例。import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; // 1. 创建Server实例 const server new Server( { name: my-database-server, // 你的Server名称 version: 0.1.0, // 版本号 }, { capabilities: { // 声明本Server提供的能力 tools: {}, // 表示支持工具 resources: {}, // 表示支持资源我们先留空 }, } );接下来我们定义一个最简单的工具作为“Hello World”。这个工具不连接数据库只用来验证整个链路是否通畅。// 2. 定义一个工具 server.setRequestHandler(tools/list, async () { return { tools: [ { name: get_server_info, // 工具名称AI将通过这个名字调用它 description: 获取当前MCP服务器的基本信息用于测试连通性。, inputSchema: { type: object, properties: {}, // 这个工具不需要输入参数 additionalProperties: false, }, }, ], }; }); // 3. 处理工具的调用请求 server.setRequestHandler(tools/call, async (request) { if (request.params.name get_server_info) { // 这里是工具的实际执行逻辑 return { content: [ { type: text, text: ✅ MCP数据库服务器运行正常\n名称my-database-server\n版本0.1.0\n时间${new Date().toISOString()}, }, ], }; } // 如果收到未知的工具调用请求抛出错误 throw new Error(未知的工具: ${request.params.name}); });最后我们需要启动Server并告诉它使用标准输入输出作为传输层。这是MCP Server的标准运行方式。// 4. 启动Server async function runServer() { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP Database Server 已启动并等待连接...); } runServer().catch((error) { console.error(Server启动失败:, error); process.exit(1); });代码逻辑梳理当客户端Cursor连接时它会发送tools/list请求。我们的handler会返回一个包含get_server_info工具定义的列表。当用户在Cursor中要求AI调用这个工具时例如用户说“请调用get_server_info工具”客户端会发送tools/call请求并指定工具名称为get_server_info。我们的第二个handler会匹配到这个名称然后执行里面的逻辑——返回一段包含服务器信息的文本。console.error用于输出日志因为MCP协议约定标准输出stdout用于协议通信日志应该输出到标准错误stderr避免干扰。现在你可以运行pnpm dev。你会看到控制台打印出“MCP Database Server 已启动并等待连接...”然后程序挂起等待客户端连接。我们的第一个MCP Server已经就绪了。在下一节我们将把它和Cursor连接起来进行第一次“对话”。4. 连接Cursor配置与首次握手MCP Server本身是一个独立的进程我们需要通过配置让Cursor知道它的存在并与之建立连接。Cursor支持通过一个本地配置文件来声明和管理MCP Server。4.1 定位Cursor的MCP配置文件Cursor的配置通常位于用户的家目录下。具体路径因操作系统而异macOS / Linux:~/.cursor/mcp.jsonWindows:%USERPROFILE%\.cursor\mcp.json如果这个文件或目录不存在你需要手动创建它。4.2 编写MCP配置文件MCP配置文件是一个JSON文件其中mcpServers字段是一个对象键是你在Cursor内部引用这个Server的别名可以自定义值是该Server的配置。对于我们刚刚写的这个TypeScript Server配置如下。创建一个名为mcp.json的文件内容如下{ mcpServers: { my-local-db: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/PROJECT/mcp-database-server/dist/index.js ], env: { NODE_ENV: development } } } }重要配置项解析my-local-db 这是你给这个Server起的名字在Cursor的上下文中会用到。command: node 指定运行Server的解释器或可执行文件。这里我们用Node.js来运行编译后的JavaScript文件。args 传递给node命令的参数。这里有一个至关重要的坑你必须提供编译后的.js文件的绝对路径。在上一步我们是用tsx在内存中直接运行.ts文件但Cursor启动时需要一个稳定的可执行入口。因此我们需要先运行一次构建命令pnpm build。这会在dist目录下生成index.js文件。将上述配置中的/ABSOLUTE/PATH/TO/YOUR/PROJECT替换为你项目的绝对路径例如在macOS上可能是/Users/yourname/Projects/mcp-database-server。env 可以设置Server进程的环境变量。实操心得路径与构建的坑这是新手配置时最容易出错的地方。很多人直接用tsx或ts-node的路径作为command或者用相对路径往往导致Cursor启动Server失败。最稳妥的方式就是1先pnpm build生成dist2在配置中使用Node命令绝对路径指向dist里的js文件。这样能最大程度保证兼容性。另外每次修改了src/index.ts后记得重新运行pnpm build并重启Cursor才能使新代码生效。4.3 重启Cursor并验证连接保存好mcp.json配置文件后你需要完全关闭并重新启动Cursor。这是因为Cursor只在启动时读取这个配置文件。启动后打开Cursor新建或进入一个聊天会话。现在你可以尝试让AI调用我们的测试工具了。输入如下提示请调用 get_server_info 这个工具。如果一切配置正确Cursor的AI通常是Claude模型会识别到可用的工具并执行调用。你应该能在回复中看到类似这样的信息✅ MCP数据库服务器运行正常 名称my-database-server 版本0.1.0 时间2024-05-27T10:30:00.000Z恭喜这意味着你的自定义MCP Server已经成功被Cursor加载并且完成了首次通信。AI现在拥有了一个由你提供的、可以远程调用的“函数”。虽然这个函数目前只是返回服务器信息但我们已经打通了从本地代码到AI助手能力的完整链路。接下来我们将注入真正的数据库能力。5. 接入真实数据库实现查询工具与Schema资源测试工具跑通后我们要动真格了连接一个真实的数据库。这里以PostgreSQL为例其他数据库如MySQL SQLite原理类似只需更换客户端库。我们将实现两个核心功能1. 一个执行SQL查询的工具2. 一个提供数据库表结构Schema的资源。5.1 安装数据库驱动并设计工具首先安装PostgreSQL的Node.js驱动pg以及它的类型定义。pnpm add pg pnpm add -D types/pg为了安全和管理方便我们使用环境变量来存储数据库连接信息。在项目根目录创建.env文件DATABASE_URLpostgresql://username:passwordlocalhost:5432/your_database_name安全警告永远不要将包含密码的.env文件提交到版本控制系统如Git。确保它在.gitignore文件中。接下来修改src/index.ts引入数据库驱动和环境变量支持。我们需要安装dotenv来加载.env文件。pnpm add dotenv现在更新src/index.ts的顶部导入部分和Server初始化部分import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { Client } from pg; // 导入PostgreSQL客户端 import * as dotenv from dotenv; // 加载环境变量 dotenv.config(); // 初始化数据库连接池使用单个客户端简化示例 const dbClient new Client({ connectionString: process.env.DATABASE_URL, }); // 在Server启动前连接数据库 async function initializeDatabase() { try { await dbClient.connect(); console.error(✅ 数据库连接成功); } catch (error) { console.error(❌ 数据库连接失败:, error); process.exit(1); // 数据库连接失败终止Server } } // ... 原有的Server创建代码 ... const server new Server(...);5.2 实现SQL查询工具我们将工具get_server_info升级或替换为一个真正有用的run_sql_query工具。修改tools/list处理器server.setRequestHandler(tools/list, async () { return { tools: [ { name: run_sql_query, description: 在连接的PostgreSQL数据库上执行一个只读的SQL SELECT查询。请提供清晰、正确的SQL语句。, inputSchema: { type: object, properties: { query: { type: string, description: 要执行的SQL SELECT查询语句。, }, }, required: [query], additionalProperties: false, }, }, ], }; });然后更新tools/call处理器来执行查询server.setRequestHandler(tools/call, async (request) { if (request.params.name run_sql_query) { const { query } request.params.arguments as { query: string }; // 简单的安全校验只允许SELECT查询根据需求调整 const sanitizedQuery query.trim().toUpperCase(); if (!sanitizedQuery.startsWith(SELECT)) { throw new Error(出于安全考虑本工具仅支持执行SELECT查询。); } try { console.error(正在执行查询: ${query}); const result await dbClient.query(query); // 将查询结果格式化为易读的文本 let output 查询成功返回 ${result.rowCount} 行数据。\n\n; if (result.rows.length 0) { const headers Object.keys(result.rows[0]).join( | ); output | ${headers} |\n; output |${ --- |.repeat(Object.keys(result.rows[0]).length)}\n; for (const row of result.rows.slice(0, 10)) { // 限制预览前10行 const values Object.values(row).map(v v null ? NULL : String(v) ).join( | ); output | ${values} |\n; } if (result.rows.length 10) { output \n... 以及另外 ${result.rows.length - 10} 行。; } } else { output 结果集为空。; } return { content: [{ type: text, text: output }], }; } catch (error: any) { // 将数据库错误清晰地返回给AI return { content: [{ type: text, text: ❌ 查询执行失败: ${error.message}\n请检查SQL语法或表名/字段名是否正确。, }], isError: true, }; } } throw new Error(未知的工具: ${request.params.name}); });5.3 实现数据库Schema资源除了主动查询我们还可以被动地提供数据库结构信息作为资源。这样AI在编写查询前就能先“了解”数据库里有什么。在Server的初始化选项中我们需要声明支持资源const server new Server( { name: my-database-server, version: 0.1.0, }, { capabilities: { tools: {}, resources: {}, // 现在声明支持资源 prompts: {}, // 可选本文暂不涉及 }, } );然后我们需要处理两个新的请求resources/list列出所有可用资源和resources/read读取特定资源的内容。// 声明一个资源数据库表列表 server.setRequestHandler(resources/list, async () { return { resources: [ { uri: db://schema/tables, name: 数据库表清单, description: 列出当前数据库中的所有用户表。, mimeType: text/plain, }, { uri: db://schema/table/users, // 示例users表的结构 name: Users表结构, description: 显示users表的列定义、类型和约束。, mimeType: text/plain, }, ], }; }); // 处理资源读取请求 server.setRequestHandler(resources/read, async (request) { const { uri } request.params; if (uri db://schema/tables) { try { // 查询PostgreSQL系统表来获取用户表列表 const result await dbClient.query( SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN (pg_catalog, information_schema) AND table_type BASE TABLE ORDER BY table_schema, table_name; ); let text 当前数据库共有 ${result.rowCount} 张用户表\n\n; for (const row of result.rows) { text - ${row.table_schema}.${row.table_name}\n; } return { contents: [{ uri, mimeType: text/plain, text }] }; } catch (error: any) { return { contents: [{ uri, mimeType: text/plain, text: 获取表列表失败: ${error.message} }] }; } } // 这里可以扩展根据不同的URI返回不同表的结构例如解析 db://schema/table/users if (uri.startsWith(db://schema/table/)) { const tableName uri.split(/).pop(); try { const result await dbClient.query( SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema NOT IN (pg_catalog, information_schema) AND table_name $1 ORDER BY ordinal_position; , [tableName]); if (result.rows.length 0) { return { contents: [{ uri, mimeType: text/plain, text: 未找到表: ${tableName} }] }; } let text 表 ${tableName} 的结构\n\n; text | 列名 | 数据类型 | 允许空值 | 默认值 |\n; text | :--- | :--- | :--- | :--- |\n; for (const row of result.rows) { text | ${row.column_name} | ${row.data_type} | ${row.is_nullable} | ${row.column_default || NULL} |\n; } return { contents: [{ uri, mimeType: text/plain, text }] }; } catch (error: any) { return { contents: [{ uri, mimeType: text/plain, text: 获取表结构失败: ${error.message} }] }; } } return { contents: [{ uri, mimeType: text/plain, text: 未找到资源: ${uri} }] }; });最后别忘了在runServer函数中调用我们新增的数据库初始化函数async function runServer() { await initializeDatabase(); // 先连接数据库 const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP Database Server 已启动并等待连接...); }5.4 重建、配置与测试构建运行pnpm build编译最新的TypeScript代码。重启Cursor完全关闭Cursor再重新打开以加载最新的Server配置如果你的mcp.json中args路径已经指向dist/index.js则无需修改。开始对话你可以先让AI“读取数据库表清单资源”请查看 db://schema/tables 这个资源。AI会调用资源读取并返回你的数据库表列表。接着你可以让AI根据看到的表结构编写一个查询基于users表的结构帮我写一个查询总用户数的SQL并执行它。AI可能会先读取db://schema/table/users资源了解结构然后生成SELECT COUNT(*) FROM users;的SQL最后调用run_sql_query工具来执行它。至此你的Cursor已经从一个“闭门造车”的代码助手进化成了一个能直接与你项目数据库对话的“数据分析伙伴”。你可以让它统计数据、生成报表、甚至基于真实数据验证业务逻辑。这种能力的提升是颠覆性的。6. 进阶优化与安全加固一个能跑通的Demo只是开始。要让这个MCP Server真正可靠、安全地用于日常开发我们还需要考虑以下几个进阶问题。6.1 连接池管理与错误恢复上面的示例使用了单个Client实例。在生产环境中这可能导致连接数耗尽或连接意外中断。更好的做法是使用连接池pg.Pool。import { Pool } from pg; const dbPool new Pool({ connectionString: process.env.DATABASE_URL, max: 5, // 最大连接数 idleTimeoutMillis: 30000, // 连接空闲超时时间 }); // 修改 initializeDatabase 测试连接而非持有连接 async function initializeDatabase() { let client; try { client await dbPool.connect(); console.error(✅ 数据库连接池初始化成功); client.release(); // 立即释放连接回池 } catch (error) { console.error(❌ 数据库连接失败:, error); process.exit(1); } } // 在 tools/call 处理器中从连接池获取客户端 if (request.params.name run_sql_query) { const { query } request.params.arguments as { query: string }; const client await dbPool.connect(); try { const result await client.query(query); // ... 格式化结果 ... client.release(); // 关键一定要释放连接 } catch (error: any) { client.release(); // 即使在错误中也要释放连接 // ... 错误处理 ... } }6.2 查询安全与权限控制允许AI执行任意SQL是极其危险的即使是只读SELECT。我们必须实施严格的安全策略SQL语法白名单 如上例只允许SELECT开头。但这很容易被绕过如SELECT * FROM users; DROP TABLE users;。更安全的方法是使用正则表达式进行严格校验或者使用SQL解析器如ts-safeql/sql进行语法树分析确保只有单条SELECT语句。查询超时与行数限制 防止复杂查询拖垮数据库。const result await client.query({ text: query, rowMode: array, // 可选减少内存占用 timeout: 5000, // 5秒超时 }); // 在格式化结果时强制限制返回给AI的行数例如最多100行。基于上下文的权限 更高级的做法是根据当前对话的上下文比如项目文件路径动态决定可以访问哪些表或视图。这需要更复杂的身份映射逻辑。环境隔离 开发环境的MCP Server连接测试数据库生产环境的配置则完全不同。务必通过环境变量严格区分。6.3 提升AI交互体验动态资源与智能提示目前的资源是静态定义的。我们可以让它更智能动态资源列表resources/list的返回可以基于数据库实时内容生成而不是硬编码。这样每张表都会自动成为一个资源。实现resources/search MCP协议支持搜索资源。你可以实现一个搜索处理器让AI通过表名或列名模糊查找相关资源。设计提示词Prompts MCP还支持prompts能力。你可以预定义一些提示词模板比如“分析订单数据趋势”当用户选择这个提示时AI会自动组合调用查询工具和资源完成一个多步骤的分析任务。6.4 日志、监控与调试结构化日志 使用pino或winston等日志库将Server的运行日志、收到的请求、执行的查询、发生的错误都记录下来方便排查问题。性能监控 记录每个工具调用的耗时对于慢查询进行告警。调试技巧 在开发时可以暂时将Server的传输层从StdioServerTransport换成WebSocketServerTransport并使用官方的MCP Inspector工具进行图形化调试可视化地查看所有的请求和响应。7. 举一反三MCP Server的无限可能数据库连接只是一个起点。MCP协议的强大之处在于其通用性。你可以遵循相同的模式为Cursor集成几乎任何本地或网络服务内部API集成 创建一个MCP Server将你们团队内部的RESTful或GraphQL API暴露给AI。AI可以帮你测试接口、根据API文档生成调用代码、甚至分析接口返回的数据。项目管理工具 连接Jira、Linear、Trello。让AI帮你创建任务、查询Bug状态、生成周报摘要。云服务管理 在严格的权限控制下连接AWS CLI或SDK。让AI帮你描述S3桶的内容、查看CloudWatch日志、甚至安全地执行一些预定义的运维脚本如重启某个测试环境的ECS实例。本地文件系统增强 虽然AI能读当前文件但你可以创建一个Server让它能按特定模式搜索整个项目历史结合git或分析特定目录下的图片/文档元数据。自定义知识库 将公司内部的Wiki、设计文档、会议纪要通过资源Resources的形式提供给AI让它成为你团队知识的“活字典”。我个人在实际构建和使用了几个MCP Server后的体会是最大的价值不在于某个工具本身有多复杂而在于它如何将AI的“通用智力”与你工作流中的“特定上下文”和“专用工具”无缝衔接。它消除了手动复制粘贴和信息检索的摩擦让AI从“一个聪明的实习生”变成了“一个深度融入你技术栈的资深搭档”。开始可能会觉得配置有点繁琐但一旦跑通第一个后面复制模式就非常快了。从今天这个10分钟的数据库Server开始尝试去自动化你工作中下一个最重复、最需要上下文信息的任务吧。