从调用到创造:手把手教你构建自定义MCP Server,连接AI与数据库
1. 从“调用者”到“创造者”为什么你需要亲手写一个MCP Server如果你正在用LangChain或者Cursor这类AI编程工具大概率已经接触过MCPModel Context Protocol了。现在很多教程都在教你“怎么用”别人写好的MCP Server比如怎么把Tavily搜索、文件系统、数据库连接这些工具装进你的AI Agent里。这当然很有用能让你的Agent瞬间获得超能力。但今天我想聊点不一样的为什么以及如何从“用别人的工具”跨越到“自己写工具”。这个转变远不止是多学一个技能那么简单。它意味着你对AI Agent工作流的理解从“用户层”深入到了“架构层”。当你只会调用现成的MCP Server时你面对的是一个黑盒。工具为什么报错返回的数据格式为什么不符合预期两个工具之间如何优雅地传递状态这些问题常常让你抓狂。而一旦你掌握了编写MCP Server的能力这些黑盒就变成了透明的乐高积木。你可以清晰地看到数据流动的每一个接口可以定制化地处理业务逻辑甚至可以将你公司内部那些“祖传”的、没有API的古老系统封装成AI能理解的标准工具。从网络上的热搜词就能看出大家的痛点登录失败:failed to start login server、error: 500 internal server error、token exchange failed。这些问题很多时候根源在于现成的Server与你的特定环境网络、权限、依赖版本不兼容。自己写Server你就能完全掌控启动、认证、错误处理的每一个环节。另一个高频需求是搜索类 mcp 服务器添加进codex的详细步骤这背后反映的是大家渴望连接特定数据源公司内网知识库、私有数据库、特定API的强烈需求而通用Server往往无法满足。所以这篇内容不是另一个“Hello World”式的MCP教程。我会假设你已经知道MCP是什么一个让LLM安全、标准化地使用外部工具和数据的协议也用过一两个现成的Server。我们将直接切入实战通过构建一个有实际用途、涉及典型难点的MCP Server案例带你走通从设计、开发、调试到集成的全流程。你会学到的不只是代码怎么写更是在面对真实业务需求时如何思考、拆解和实现一个可靠的工具服务。让我们开始吧。2. 实战蓝图设计一个“智能SQL查询分析师”MCP Server在动手写代码之前明确我们要建造什么至关重要。为了覆盖MCP开发的核心环节我们设计一个比简单“查天气”更有深度的项目一个能够连接数据库、理解自然语言查询、并安全执行SQL的MCP Server。我把它叫做“SQL查询分析师”。它的核心功能是AI Agent比如一个数据分析助手可以向它发送诸如“帮我查一下上个月销售额最高的前五个产品”这样的自然语言指令这个Server能将其转换为安全的SQL语句在指定的数据库执行并将结果以清晰、结构化的格式返回给Agent。这听起来像是RAG检索增强生成或Text-to-SQL但通过MCP我们将其封装成了一个标准化、可复用的“工具”。为什么选这个案例因为它几乎触及了MCP Server开发的所有关键点资源Resources我们需要让Server能“看到”数据库的表结构Schema这是LLM生成准确SQL的基础。工具Tools核心工具是“执行查询”。但这个工具需要参数自然语言问题内部有复杂的逻辑自然语言转SQL、SQL执行、结果格式化。提示词管理Prompts我们可以提供预置的提示词模板帮助Agent更好地使用这个工具比如“当你需要复杂数据分析时可以这样问我...”。安全与边界这是重中之重。我们不能让AI随意执行DROP TABLE或DELETE语句。需要设计严格的权限控制和SQL审查机制。基于这个蓝图我们的Server将提供以下MCP组件一个Resourcedatabase://schema以文本形式提供当前连接数据库的简要表结构描述。一个Toolexecute_sql_query接收一个question字符串参数返回查询结果。一个Promptsql_analyst_guidance指导AI如何有效地提出数据分析问题。接下来我们进入具体的环境搭建和项目初始化环节。2.1 环境搭建与项目初始化避开依赖的“暗礁”自己开发MCP Server首先得把舞台搭好。这里我推荐使用Python因为它有成熟的mcpSDK社区活跃对我们快速原型开发非常友好。别小看环境搭建很多“登录失败”、“启动报错”的坑都是从这里开始的。首先确保你有一个干净的Python环境3.9以上。我强烈建议使用venv或conda创建虚拟环境这是避免包冲突的黄金法则。# 创建并激活虚拟环境 python -m venv mcp-env source mcp-env/bin/activate # Linux/macOS # 或 .\mcp-env\Scripts\activate # Windows然后安装核心依赖。这里有个关键点mcp库本身是一个客户端SDK而我们要开发Server需要安装mcp[cli]它包含了运行Server所需的命令行工具。同时为了我们的SQL案例我们安装sqlalchemy作为ORM以及openai或你选择的LLM SDK用于自然语言转SQL。pip install mcp[cli] sqlalchemy openai注意如果你看到error: externally-managed-environment这类错误说明你的主Python环境比如某些Linux发行版或macOS用Homebrew安装的Python被系统保护了。这是好事它强制你使用虚拟环境。请务必在虚拟环境中操作。安装完成后验证一下mcp命令行工具是否可用mcp --version现在创建我们的项目目录结构。一个好的结构能让后续开发和维护清晰很多。smart-sql-mcp-server/ ├── server.py # MCP Server主程序 ├── database.py # 数据库连接与操作封装 ├── sql_generator.py # 自然语言转SQL的核心逻辑 ├── requirements.txt # 项目依赖 └── README.md在requirements.txt中固化我们的依赖mcp[cli]1.0.0 sqlalchemy2.0.0 openai1.0.0使用pip install -r requirements.txt安装。这一步看似简单但却是保证任何协作者包括未来的你能复现环境的基础。我遇到过太多因为依赖版本不匹配导致的诡异问题比如某个SDK的新版本修改了API导致整个Server崩溃。固定版本号能有效避免这类问题。3. 核心实现一步步构建Server的骨骼与肌肉环境就绪我们开始编写核心代码。MCP Server的核心是与标准输入输出stdio进行通信遵循JSON-RPC协议。幸运的是mcpSDK的Server类帮我们处理了大部分协议解析和通信的脏活我们只需要关注三件事初始化、声明能力、处理请求。3.1 主框架搭建Server的生命周期让我们先创建server.py搭建起最基本的框架。import asyncio import sys import json from mcp import Server, types from database import DatabaseManager from sql_generator import SQLGenerator class SmartSQLServer: def __init__(self, db_url: str, llm_api_key: str): # 初始化核心组件 self.db_manager DatabaseManager(db_url) self.sql_gen SQLGenerator(llm_api_key) # 初始化MCP Server self.server Server(smart-sql-server) # 注册Server提供的各种能力下一步会填充 self._register_resources() self._register_tools() self._register_prompts() def _register_resources(self): 注册Resources数据资源 # 我们将提供一个数据库schema资源 self.server.list_resources() async def handle_list_resources(): # 返回一个资源列表这里我们定义一个资源 return [ types.Resource( uridatabase://schema, namedatabase-schema, description当前连接数据库的表结构描述, mimeTypetext/plain, ) ] self.server.read_resource() async def handle_read_resource(uri: str) - str: if uri database://schema: # 调用数据库管理器获取schema描述 schema_text await self.db_manager.get_schema_description() return schema_text raise ValueError(fUnknown resource URI: {uri}) def _register_tools(self): 注册Tools可调用工具 self.server.list_tools() async def handle_list_tools(): return [ types.Tool( nameexecute_sql_query, description根据自然语言问题生成并执行SQL查询返回结果。, inputSchema{ type: object, properties: { question: { type: string, description: 用自然语言描述你的数据查询问题例如上个月销售额最高的前五个产品是什么 } }, required: [question] } ) ] self.server.call_tool() async def handle_call_tool(name: str, arguments: dict) - list: if name execute_sql_query: question arguments.get(question) if not question: raise ValueError(Missing required argument: question) # 这里是核心处理逻辑 result await self._execute_query(question) return [types.TextContent(typetext, textresult)] raise ValueError(fUnknown tool: {name}) async def _execute_query(self, question: str) - str: 处理查询的核心逻辑 # 1. 获取数据库schema为LLM提供上下文 schema await self.db_manager.get_schema_description() # 2. 调用LLM将自然语言问题转换为SQL sql_query await self.sql_gen.generate_sql(question, schema) # 3. 安全检查与执行 if not self._is_safe_sql(sql_query): return 错误生成的SQL语句包含潜在的危险操作如DROP, DELETE, UPDATE已被阻止执行。 # 4. 执行SQL并获取结果 query_result await self.db_manager.execute_query(sql_query) # 5. 格式化结果并返回 return self._format_result(query_result) def _register_prompts(self): 注册Prompts提示词模板 self.server.list_prompts() async def handle_list_prompts(): return [ types.Prompt( namesql_analyst_guidance, description指导如何向SQL分析师有效提问的提示词。, arguments[] ) ] self.server.get_prompt() async def handle_get_prompt(name: str, arguments: dict) - types.GetPromptResult: if name sql_analyst_guidance: guidance_text 你正在与一个智能SQL查询分析师对话。为了获得最佳结果请遵循以下指南 1. **明确对象**提及具体的表名或字段名如果知道例如“在orders表中...”。 2. **具体时间**使用“上个月”、“2023年Q2”、“最近7天”等具体范围。 3. **清晰指标**说明你想要“计算总和”、“找出平均值”、“列出前N项”。 4. **示例问题** - “计算products表中每个类别的库存总量。” - “找出sales表中上周销售额超过10000元的销售员。” - “比较users表在过去两个季度的新增注册数。” return types.GetPromptResult( descriptionSQL分析师使用指南, messages[types.PromptMessage(roleuser, contentguidance_text)] ) raise ValueError(fUnknown prompt: {name}) def _is_safe_sql(self, sql: str) - bool: 简单的SQL安全检查 dangerous_keywords [DROP, DELETE, UPDATE, INSERT, ALTER, TRUNCATE] upper_sql sql.upper() # 这里只是一个简单示例真实环境需要更复杂的解析和审计 for keyword in dangerous_keywords: if keyword in upper_sql: # 可以加入更精细的判断比如是否在字符串或注释中 return False return True def _format_result(self, result) - str: 格式化查询结果 if isinstance(result, list): if not result: return 查询成功但未返回任何数据。 # 简单格式化为表格形式 headers result[0].keys() if result else [] rows [list(item.values()) for item in result] # 这里可以做得更美观比如用tabulate库 formatted \t.join(headers) \n for row in rows: formatted \t.join(str(cell) for cell in row) \n return formatted else: return str(result) async def run(self): 启动Server监听stdio async with self.server.run_over_stdio() as (read_stream, write_stream): await self.server.wait_for_disconnect() if __name__ __main__: # 从环境变量或配置文件读取敏感信息 import os DB_URL os.getenv(DB_URL, sqlite:///./example.db) # 默认用SQLite示例 LLM_API_KEY os.getenv(OPENAI_API_KEY, ) if not LLM_API_KEY: print(错误请设置 OPENAI_API_KEY 环境变量。, filesys.stderr) sys.exit(1) server SmartSQLServer(DB_URL, LLM_API_KEY) asyncio.run(server.run())这段代码构建了Server的完整骨架。SmartSQLServer类是我们的总控中心。在__init__中我们初始化了数据库管理器和SQL生成器稍后实现并创建了MCPServer实例。随后我们通过_register_*系列方法向这个Server实例注册了它所能提供的一切资源、工具和提示词。关键点解析装饰器注册self.server.list_resources()、self.server.call_tool()这些装饰器是mcpSDK的核心它们将我们的处理函数与标准的MCP请求类型绑定。类型提示使用types.Resource、types.Tool等来自mcp.types的类来构建标准响应这确保了我们的Server与协议兼容。异步编程整个框架基于asyncio这是处理I/O密集型操作如LLM API调用、数据库查询的最佳实践能有效提升Server的并发能力。框架搭好了接下来我们填充两个核心组件DatabaseManager和SQLGenerator。3.2 实现DatabaseManager安全连接与操作创建database.py负责所有与数据库打交道的逻辑。import asyncio from sqlalchemy import MetaData, create_engine, text from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class DatabaseManager: def __init__(self, database_url: str): 初始化数据库管理器。 注意SQLAlchemy的异步引擎需要asyncpg(PostgreSQL)或aiosqlite(SQLite)等异步驱动。 这里以SQLite为例使用aiosqlite。 # 确保URL是异步格式 if database_url.startswith(sqlite): if not database_url.startswith(sqliteaiosqlite): database_url database_url.replace(sqlite://, sqliteaiosqlite://, 1) self.database_url database_url self.engine None self.async_session_factory None async def initialize(self): 初始化异步引擎和会话工厂。必须在运行前调用。 try: self.engine create_async_engine(self.database_url, echoFalse, futureTrue) self.async_session_factory sessionmaker( self.engine, class_AsyncSession, expire_on_commitFalse ) logger.info(f数据库引擎初始化成功: {self.database_url}) except Exception as e: logger.error(f数据库引擎初始化失败: {e}) raise async def get_schema_description(self) - str: 获取数据库的表结构描述用于提供给LLM作为上下文。 if not self.engine: await self.initialize() schema_text # 数据库表结构\n\n async with self.engine.begin() as conn: # 使用SQLAlchemy的反射功能获取元数据 metadata MetaData() await conn.run_sync(metadata.reflect) for table_name, table in metadata.tables.items(): schema_text f## 表名: {table_name}\n schema_text f- **描述**: (可在此处添加表注释)\n schema_text - **字段列表**:\n for column in table.columns: # 获取字段类型、是否可为空、默认值等信息 col_type str(column.type) nullable 是 if column.nullable else 否 pk (主键) if column.primary_key else schema_text f - {column.name}: {col_type}{pk}, 可空: {nullable}\n # 添加外键信息简化 fks [fk for fk in table.foreign_keys] if fks: schema_text - **外键关系**:\n for fk in fks: schema_text f - 指向 {fk.column.table.name}.{fk.column.name}\n schema_text \n if len(metadata.tables) 0: schema_text 数据库中没有找到任何表。 return schema_text async def execute_query(self, sql: str): 执行安全的SELECT查询并返回结果列表。 if not self.engine: await self.initialize() # 再次进行安全检查虽然主逻辑已检查但此处是第二道防线 if not self._is_readonly_query(sql): raise ValueError(只允许执行SELECT查询语句。) async with self.async_session_factory() as session: try: result await session.execute(text(sql)) # 将结果转换为字典列表便于处理 rows result.mappings().all() # 返回RowMapping列表 return [dict(row) for row in rows] except Exception as e: logger.error(fSQL执行错误: {e}, SQL: {sql}) raise ValueError(fSQL执行失败: {str(e)}) def _is_readonly_query(self, sql: str) - bool: 更严格的只读查询检查通过SQL解析此处简化 # 这是一个简化版。生产环境应使用SQL解析器如sqlparse进行精确判断。 trimmed_sql sql.strip().upper() return trimmed_sql.startswith(SELECT)实现要点与避坑指南异步驱动这是最大的坑之一。SQLAlchemy的异步支持需要特定的异步数据库驱动。对于SQLite你需要aiosqlite对于PostgreSQL需要asyncpg。务必通过pip install aiosqlite安装并在数据库URL中使用sqliteaiosqlite://前缀。连接管理我们使用了SQLAlchemy的异步引擎和会话工厂。async with self.async_session_factory() as session确保了会话在使用后会被正确关闭避免连接泄漏。Schema描述get_schema_description方法生成的描述文本是LLM理解数据库结构、生成准确SQL的“眼睛”。这里的格式清晰与否直接影响后续LLM的表现。你可以根据需要增加更多信息比如字段的样本值注释。双重安全检查在execute_query中我们再次进行了只读检查。这是一个深度防御策略。主逻辑中的_is_safe_sql是关键词黑名单这里的_is_readonly_query是更严格的语句类型白名单只允许SELECT。在实际项目中你可能需要结合SQL解析库来实现更精确的AST抽象语法树分析。3.3 实现SQLGenerator连接自然语言与SQL的桥梁接下来是sql_generator.py它负责调用LLM API将用户的自然语言问题结合数据库Schema转换成SQL语句。import openai import logging from typing import Optional logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class SQLGenerator: def __init__(self, api_key: str, model: str gpt-4o-mini): 初始化SQL生成器。 这里以OpenAI API为例你可以轻松替换为其他兼容OpenAI格式的API如DeepSeek, Qwen等。 self.client openai.AsyncOpenAI(api_keyapi_key) self.model model # 系统提示词用于设定LLM的角色和行为 self.system_prompt 你是一个专业的SQL专家。你的任务是根据提供的数据库表结构Schema和用户的问题生成正确、高效且安全的SQL查询语句。 规则 1. **只生成SELECT语句**你只能生成用于数据检索的SQL语句。绝对不要生成INSERT、UPDATE、DELETE、DROP、ALTER等任何会修改数据或结构的语句。 2. **严格使用提供的Schema**所有表名和字段名都必须来自提供的Schema。如果用户问题中提到的概念在Schema中不存在请明确指出。 3. **优先考虑性能**在语义正确的前提下使用JOIN、适当的WHERE条件和索引友好的写法。 4. **输出格式**只输出纯SQL语句不要有任何额外的解释、Markdown代码块标记或前言后语。 5. **处理模糊性**如果用户问题模糊例如“最近的数据”做出合理假设并在生成的SQL注释中说明例如 -- 假设‘最近’指过去7天。 async def generate_sql(self, question: str, schema_text: str) - str: 根据用户问题和数据库Schema生成SQL查询语句。 user_prompt f数据库Schema如下 {schema_text} 用户问题{question} 请生成对应的SQL查询语句 messages [ {role: system, content: self.system_prompt}, {role: user, content: user_prompt} ] try: response await self.client.chat.completions.create( modelself.model, messagesmessages, temperature0.1, # 低温度保证输出稳定性 max_tokens500, ) generated_sql response.choices[0].message.content.strip() # 清理输出移除可能存在的 sql ... 标记 if generated_sql.startswith(sql): generated_sql generated_sql[6:] if generated_sql.endswith(): generated_sql generated_sql[:-3] generated_sql generated_sql.strip() logger.info(f生成的SQL: {generated_sql}) return generated_sql except openai.APIError as e: logger.error(fOpenAI API调用失败: {e}) raise ValueError(f无法生成SQLAPI错误: {str(e)}) except Exception as e: logger.error(f生成SQL时发生未知错误: {e}) raise ValueError(f生成SQL失败: {str(e)})核心技巧与调优点系统提示词System Prompt是灵魂这里的self.system_prompt至关重要。它明确规定了LLM的职责边界只生成SELECT、安全红线禁止写操作和输出格式纯SQL。写得越清晰LLM的表现就越可控。温度Temperature设置对于生成代码、SQL这类需要精确性的任务将temperature设低如0.1非常关键。这能极大减少LLM的“胡言乱语”让输出更确定、更可靠。输出清洗LLM喜欢用Markdown代码块包裹输出。我们通过简单的字符串处理移除sql和标记确保拿到纯净的SQL语句。错误处理网络超时、API限额、模型故障都可能发生。良好的错误处理和日志记录logger.error能让你在Server出问题时快速定位。这里我们将异常转换为ValueError向上抛出最终会在Tool调用结果中返回给用户。模型选择我们使用了gpt-4o-mini它在精度和成本间取得了很好的平衡。对于更复杂的多表JOIN或子查询你可能需要能力更强的模型如gpt-4o。你也可以轻松适配其他提供ChatCompletion接口的模型服务。4. 运行、调试与集成让Server真正“活”起来代码写完了但一个不能运行、无法调试的Server只是空中楼阁。这部分我们解决“最后一公里”的问题。4.1 本地运行与手动测试首先我们需要一个测试数据库。创建一个简单的SQLite数据库并填充一些示例数据。# 创建一个Python脚本 init_db.py import sqlite3 conn sqlite3.connect(example.db) cursor conn.cursor() # 创建表 cursor.execute( CREATE TABLE IF NOT EXISTS products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, category TEXT, price REAL, stock INTEGER ) ) cursor.execute( CREATE TABLE IF NOT EXISTS sales ( id INTEGER PRIMARY KEY, product_id INTEGER, sale_date DATE, quantity INTEGER, amount REAL, FOREIGN KEY (product_id) REFERENCES products (id) ) ) # 插入示例数据 cursor.executemany(INSERT INTO products (name, category, price, stock) VALUES (?, ?, ?, ?), [ (Laptop, Electronics, 999.99, 50), (Coffee Mug, Home, 15.50, 200), (Desk Lamp, Home, 45.00, 75), ]) cursor.executemany(INSERT INTO sales (product_id, sale_date, quantity, amount) VALUES (?, ?, ?, ?), [ (1, 2024-07-15, 2, 1999.98), (2, 2024-07-16, 10, 155.00), (1, 2024-07-17, 1, 999.99), (3, 2024-07-18, 5, 225.00), ]) conn.commit() conn.close() print(测试数据库 example.db 已创建并填充数据。)运行python init_db.py创建数据库。接下来设置环境变量并运行我们的Server。打开一个终端export OPENAI_API_KEY你的OpenAI API Key export DB_URLsqliteaiosqlite:///./example.db python server.py如果一切正常Server会启动并等待标准输入。但这并不直观。我们需要一种方式来测试它是否按MCP协议正确响应。4.2 使用MCP CLI进行协议级测试mcpCLI工具提供了一个强大的测试方式mcp dev。它允许我们通过stdio与Server交互并发送标准的MCP请求。这是调试Server协议兼容性的最佳手段。新建一个终端运行mcp dev python server.py这会启动一个交互式会话。你可以输入MCP请求来测试。例如测试列出资源{jsonrpc: 2.0, id: 1, method: resources/list}你应该能看到包含database://schema的响应。再测试读取这个资源{jsonrpc: 2.0, id: 2, method: resources/read, params: {uri: database://schema}}这会触发我们的handle_read_resource函数返回数据库的表结构描述。接着测试列出工具{jsonrpc: 2.0, id: 3, method: tools/list}最后测试核心的execute_sql_query工具{ jsonrpc: 2.0, id: 4, method: tools/call, params: { name: execute_sql_query, arguments: { question: 库存总量最多的产品类别是什么 } } }如果一切顺利你将收到一个JSON-RPC响应其中包含LLM生成的SQL可能是SELECT category, SUM(stock) as total_stock FROM products GROUP BY category ORDER BY total_stock DESC LIMIT 1以及该SQL的执行结果。这个过程能帮你验证整个数据流从接收请求、调用LLM、生成SQL、安全检查、执行查询到返回结果是否全部畅通。如果任何一步出错mcp dev会显示详细的错误信息这是定位问题不可或缺的工具。4.3 集成到LangChain或Cursor从独立服务到AI工具箱Server能独立运行并通过协议测试后下一步就是把它集成到AI Agent生态中。这里以LangChain为例。首先你需要安装LangChain的MCP集成包pip install langchain langchain-mcp然后在你的LangChain应用中可以这样使用import asyncio from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_mcp import MCPServer async def main(): # 1. 启动你的MCP Server作为子进程 # 注意这里需要指定server.py的路径和正确的环境变量 server MCPServer( commandpython, args[/path/to/your/server.py], env{OPENAI_API_KEY: your_key, DB_URL: sqliteaiosqlite:///./example.db} ) # 2. 从Server中获取工具 async with server: tools await server.get_tools() # 你也可以获取 prompts: prompts await server.get_prompts() # 3. 创建LangChain Agent llm ChatOpenAI(modelgpt-4o, temperature0) prompt ChatPromptTemplate.from_messages([ (system, 你是一个数据分析助手可以使用SQL工具来回答问题。), (human, {input}), (placeholder, {agent_scratchpad}), ]) agent create_tool_calling_agent(llm, tools, prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue) # 4. 运行Agent result await agent_executor.ainvoke({ input: 帮我分析一下哪个产品类别的总销售额最高 }) print(result[output]) if __name__ __main__: asyncio.run(main())集成关键点进程管理MCPServer类会以子进程形式启动你的server.py并管理其生命周期async with server:。工具注入await server.get_tools()会自动获取Server声明的所有工具这里是execute_sql_query并将其转换为LangChain的Tool对象。这样你的Agent就能像使用普通LangChain工具一样使用它。透明调用当Agent决定使用这个工具时LangChain会通过stdio向你的Server进程发送MCP调用你的Server处理完毕后返回结果LangChain再将其融入对话流。整个过程对开发者是透明的。对于Cursor编辑器集成更简单。通常只需要在Cursor的MCP配置文件中添加你的Server启动命令即可。这让你能在Cursor的聊天窗口中直接使用自定义的SQL查询工具。5. 进阶优化与生产级考量一个能跑通的Demo和一个健壮的生产级Server之间还有很长的路要走。以下是几个关键的进阶方向5.1 增强SQL生成的安全性与准确性我们之前的_is_safe_sql和_is_read_only_query检查是基础。在生产环境中你需要使用SQL解析器集成sqlparse这样的库来解析生成的SQL构建AST抽象语法树从而精确判断语句类型SELECT/INSERT等、操作的表和字段。这能有效防止通过字符串拼接绕过关键词黑名单的攻击。实现查询超时与资源限制在execute_query中设置SQL执行的超时时间如timeout30防止复杂查询或意外循环拖垮数据库。同时可以限制返回的行数例如LIMIT 1000避免一次性拉取海量数据。Schema缓存与更新每次生成SQL都反射整个数据库Schema可能很慢。可以实现一个带TTL生存时间的缓存机制定期更新Schema。当检测到数据库有DDL变更时可通过监听或定时检查使缓存失效。5.2 提升错误处理与用户体验结构化错误返回不要只返回“执行失败”。将错误分类如“SQL生成错误”、“数据库连接错误”、“权限错误”并提供对用户或Agent友好的建议信息。添加查询解释除了返回数据还可以让LLM对查询结果进行简要分析生成一两句洞察。这可以通过在_format_result后再次调用LLM来实现将原始数据和问题一起喂给LLM让它“解读”数据。支持分页与流式响应如果查询结果很大可以考虑支持分页参数。对于MCP还可以探索使用Server-Sent Events (SSE) 进行流式结果返回提升交互体验。5.3 性能监控与日志结构化日志使用structlog或logging的DictFormatter记录结构化的日志包含请求ID、工具名、执行时间、错误码等字段便于后续用ELK等工具分析。指标收集集成prometheus-client暴露如mcp_tool_call_duration_seconds工具调用耗时、mcp_sql_generation_errors_totalSQL生成错误数等指标方便监控Server健康度。配置化管理将数据库连接字符串、LLM模型选择、安全规则等抽离到配置文件如config.yaml或环境变量中避免硬编码。从使用现成的MCP Server到自己动手编写这一步跨越带来的不仅是能力的扩展更是思维的转变。你不再是被动接受工具功能的用户而是能够根据具体业务场景量身定制AI Agent“手脚”的创造者。这个过程必然会遇到比使用现成工具更多的挑战比如协议细节、异步编程、错误处理和安全边界。但每解决一个这样的问题你对整个AI应用栈的理解就会加深一层。我建议你以本文的“智能SQL查询分析师”为起点先把它跑通理解每一行代码的作用。然后尝试改造它比如连接你公司的MySQL数据库增加对存储过程的支持或者把它变成一个“内部知识库问答”Server。真正的熟练源于一次次将想法付诸实践的迭代。当你亲手打造的Server成功响应第一个来自AI Agent的请求并返回精准的数据时那种成就感是单纯调用API无法比拟的。