当前位置:首页>python>Text2SQL 和 Text2Python 的 MCP 实现

Text2SQL 和 Text2Python 的 MCP 实现

  • 2026-10-09 22:53:17
Text2SQL 和 Text2Python 的 MCP 实现
把自然语言变成SQL 查询、把需求描述变成可执行的 Python 程序,这两件事听起来像“让模型写两段字符串”。但真正落地时,你很快会遇到三类硬问题:
上下文怎么给:数据库 schema、业务词表、样例值、运行时约束……怎么稳定、标准化地提供给模型?
动作怎么做:SQL 到底在哪执行?Python 在什么隔离环境里跑?失败怎么回滚?
安全怎么兜底:提示注入、越权读写、工具链漏洞、审计与授权……不处理就不可能上线。
MCP(Model Context Protocol)的价值,恰好是把“给上下文”和“让模型做动作”这两件事,从具体模型/具体应用里抽离出来,做成标准接口:Host(LLM 应用)↔ Client(连接器)↔ Server(能力提供方),通信基于JSON-RPC 2.0、支持能力协商、并且把Resources / Tools / Prompts做成统一语义。 
下面这篇文章按“从零开始”的方式,给出一套Text2SQL + Text2Python 的 MCP Server 设计与实现骨架,并把关键原理讲透:怎么把它做成真正可用、可控、可评估的工程系统。

1. 先对齐:你要做的是“Text2SQL/2Python”,还是“可上线的查询/执行系统”?

很多 Text2SQL Demo 的隐含前提是:
  • schema 很小
  • 没有权限边界
  • 执行环境随便跑
  • 出错就把报错丢回模型再试
而现实里,Text2SQL 的标杆数据集(如 Spider / BIRD)之所以难,是因为它考察的是跨库泛化、复杂查询、真实数据值、以及执行正确性:
  • Spider 1.0:10,181 个问题、200 个多表数据库、复杂 SQL,训练/测试库不重合,强调对新 schema 的泛化。 
  • BIRD:12,751 对 question-SQL,95 个大库,总数据量 33.4GB,强调“脏值/外部知识/效率”等真实问题。 
这意味着:你的 MCP 实现不能只产出 SQL 字符串,而要产出一个可控系统:
  • 能把正确的 schema 片段喂给模型(减少噪声)
  • 能把 SQL 安全地执行在受控连接上(只读/超时/限行数)
  • 能把执行反馈(错误、统计、Explain)结构化返回给模型做自修复
  • 能审计、能授权、能升级

2. MCP 在这里怎么用:Resources / Tools / Prompts 的职责边界

MCP 的三类能力建议这样切: 

2.1 Resources:把“上下文”做成可寻址、可缓存的对象

典型资源:
  • db://schema/{db}:库级 schema(表/列/类型/外键)
  • db://dict/{db}:业务词表/字段别名/枚举含义
  • runtime://python:Python 沙箱能力说明(允许包、资源限制、禁网等)
  • Resources 的关键不是“内容多”,而是稳定、可复用、可做缓存与权限控制。

2.2 Tools:把“动作”做成可审计、可限权的函数

典型工具:
  • sql.execute(sql, params, db, max_rows, timeout_ms):执行只读 SQL
  • sql.explain(sql, db):Explain/Query Plan
  • python.run(code, files, limits):在隔离环境运行代码并返回 stdout/stderr/产物摘要
  • MCP 规范强调工具调用需要清晰的用户授权/控制语义,并提醒“工具代表任意代码执行路径,必须慎重”。 

2.3 Prompts:把“最佳实践提示词模板”变成可复用资产

例如:一个专门用于 Text2SQL 的 prompt 模板,强制模型先做 schema linking 再产出 SQL AST。

3. Text2SQL 原理拆解:从 Schema Linking 到“可执行正确性闭环”

把 Text2SQL 做成工程系统,一般是下面这条流水线(你可以把它映射成 Host 侧 Agent 的多步调用,也可以封装进 Server 侧工具里)。

3.1 Schema Linking:决定“给模型哪些表/列”

LLM Text2SQL 的一个核心工程技巧是 schema linking:先筛选相关表/列,再让模型生成 SQL,减少噪声与 token 成本。但 schema linking 本身也有风险:漏召回关键列会直接导致不可修复的错误;过度裁剪会破坏结构完整性。RSL-SQL 这类工作专门讨论了这些风险并用双向链接、上下文增强与多轮自修正来提高召回。 
工程实现上,schema linking 通常是三路信号融合:
  • 词面匹配(字段别名、同义词、分词/拼写变体)
  • 向量检索(question ↔ column/table description)
  • 结构约束(外键路径、join 可达性)
关键点:别把 linking 当成一次性过滤器,而要当成“候选生成器 + 风险兜底机制”。实践里会保留一个full schema 模式和一个linked schema 模式,失败时回退或投票。

3.2 SQL 生成:别让模型“自由写字符串”

生产系统里建议至少做到“两层约束”:
  1. 结构化输出:让模型输出 SQL 的结构(AST/JSON),再由你序列化成 SQL
  2. 语法解析与白名单:生成后用解析器(如 sqlglot)解析,确保单语句、只读、禁用危险关键词

3.3 执行与反馈:执行正确性 > 字符串相似

Spider/BIRD 这类基准都强调执行维度。 
工程上,执行层要做的不是“跑一下”,而是:
  • 强制只读连接 / 只读事务
  • 超时、限行数、限返回列宽
  • Explain 成本阈值(防止全表扫爆库)
  • 把数据库错误结构化返回给模型(错误码/位置/期望类型)
这样你才能构建自修复闭环:模型 → 执行 → 结构化报错 → 模型修正 → 再执行。

4. Text2Python 原理拆解:把“写代码”变成“受控执行任务”

Text2Python 真正的难点不是生成代码,而是安全、可重复、可诊断:

4.1 先定义 I/O 合同(Contract)

在让模型写代码之前,你要先让系统明确:
  • 输入:有哪些变量、类型、数据规模上限
  • 输出:返回值结构(JSON/表格/文件)
  • 约束:是否允许网络、允许哪些包、时间/内存限制
没有 contract,模型只会“写到能跑为止”,并且难以评估。

4.2 沙箱:不要幻想“Python 里限制 builtins 就安全”

安全执行 Python 的底线是进程级隔离(容器/虚拟机/受限用户),并配合:
  • 禁网
  • 限 CPU/内存/文件大小
  • 限系统调用(seccomp 等)
  • 只读文件系统(或限定工作目录)
如果你把 Python 直接 exec() 在服务进程里,迟早会出事故。

4.3 诊断闭环:让模型基于“可操作反馈”修复

你要返回的不是“失败了”,而是:
  • stdout / stderr(截断策略)
  • 异常类型 + 栈
  • 运行时统计(耗时/内存)
  • 可选:单测结果、lint/type check 结果

5. 用 MCP Python SDK(FastMCP)搭两个 Server

下面用 MCP 官方 Python SDK 的FastMCP写骨架。官方文档与 Quickstart 的写法是:
from mcp.server.fastmcp import FastMCP
用 
@mcp.tool() / @mcp.resource()
 注册能力,然后 
mcp.run() 
启动。 

STDIO 模式日志注意:不要向 stdout 打日志,会破坏 JSON-RPC 消息;写 stderr 或用 logging。 

5.1 依赖与目录

uv init mcp-text2cd mcp-text2uv venv && source .venv/bin/activateuv add "mcp[cli]" sqlalchemy sqlglot pydantic
(SQL 驱动按需加:psycopg、pymysql 等)

6. SQL MCP Server:schema 资源 + 安全执行工具

6.1 关键设计点

资源:提供 schema(并可加入字段别名、注释、样例值摘要)
工具:执行 SQL(只读、单语句、自动补 LIMIT、超时、限行数)
返回结构:建议用 JSON(便于模型稳定消费),FastMCP 支持 json_response=True。 ()

6.2 示例代码(骨架)

# sql_server.pyfrom __future__ import annotationsimport osfrom typing import Any, Optionalimport sqlalchemy as saimport sqlglotfrom mcp.server.fastmcp import FastMCPfrom pydantic import BaseModelmcp = FastMCP("text2sql", json_response=True)ENGINE_URL = os.environ.get("DB_URL", "sqlite:///./demo.db")engine = sa.create_engine(ENGINE_URL, future=True)class QueryResult(BaseModel):    columns: list[str]    rows: list[list[Any]]    row_count: int    truncated: booldef _is_readonly(ast: sqlglot.Expression) -> bool:    # 只允许 SELECT(可按需扩展到 WITH + SELECT)    return ast.key.upper() in {"SELECT", "WITH"}def _normalize_sql(sql: str) -> str:    # 禁止多语句    if ";" in sql.strip().rstrip(";"):        raise ValueError("Multiple statements are not allowed.")    return sql.strip().rstrip(";")@mcp.resource("db://schema")def get_schema() -> str:    """Return database schema (tables/columns/types)."""    insp = sa.inspect(engine)    out = []    for t in insp.get_table_names():        cols = insp.get_columns(t)        out.append(            {                "table": t,                "columns": [{"name": c["name"], "type": str(c["type"])} for c in cols],                "pk": insp.get_pk_constraint(t).get("constrained_columns", []),                "fks": insp.get_foreign_keys(t),            }        )    return sa.json.dumps(out)  # 或者用 json.dumps@mcp.tool()def sql_execute(sql: str, max_rows: int = 200) -> QueryResult:    """Execute a read-only SQL query safely.    Args:        sql: SQL statement (SELECT only).        max_rows: Maximum rows to return (truncate if exceeded).    """    sql = _normalize_sql(sql)    ast = sqlglot.parse_one(sql)    if not _is_readonly(ast):        raise ValueError("Only SELECT queries are allowed.")    # 可选:自动补 LIMIT(示意,生产建议更严谨)    if "LIMIT" not in sql.upper():        sql = f"{sql} LIMIT {max_rows + 1}"    with engine.connect() as conn:        res = conn.execute(sa.text(sql))        cols = list(res.keys())        rows = res.fetchmany(max_rows + 1)    truncated = len(rows) > max_rows    rows = rows[:max_rows]    return QueryResult(        columns=cols,        rows=[list(r) for r in rows],        row_count=len(rows),        truncated=truncated,    )if __name__ == "__main__":    mcp.run(transport="stdio")
你应该在生产里补齐的部分(但骨架先跑通):
  • 超时:驱动级/连接级 statement_timeout(Postgres)或应用侧 kill
  • Explain:单独 sql_explain 工具,做 cost 阈值
  • 权限:只读账号 + schema 白名单
  • 参数化:避免模型直接拼接值(尤其文本条件),要求传 params 并在服务端绑定

7. Python MCP Server:隔离执行 + 结构化结果

7.1 两种落地方式

  1. 方式 A(推荐):在 MCP Server 里调用“沙箱执行器”(容器/微虚拟机/受限用户进程),Server 只负责编排与回传结果
  2. 方式 B(仅本地 Demo):subprocess + 资源限制(不等于强安全,但能展示闭环)

7.2 示例代码(本地 Demo 骨架)

# python_server.pyfrom __future__ import annotationsimport subprocessimport tempfilefrom typing import Anyfrom mcp.server.fastmcp import FastMCPfrom pydantic import BaseModelmcp = FastMCP("text2python", json_response=True)class PyRunResult(BaseModel):    exit_code: int    stdout: str    stderr: str@mcp.tool()def python_run(code: str, timeout_s: int = 3) -> PyRunResult:    """Run python code in a subprocess (demo sandbox).    Args:        code: Python code to execute.        timeout_s: Execution timeout.    """    with tempfile.TemporaryDirectory() as td:        p = subprocess.run(            ["python", "-I", "-c", code],  # -I: isolate (no user site-packages)            cwd=td,            capture_output=True,            text=True,            timeout=timeout_s,        )        return PyRunResult(            exit_code=p.returncode,            stdout=p.stdout[-8000:],  # 截断策略            stderr=p.stderr[-8000:],        )if __name__ == "__main__":    mcp.run(transport="stdio")

重要:-I 只能减少环境污染,不等于安全隔离。真正要上线,还是要进程级隔离 + 禁网 + 配额。

8. “把 Text2SQL/Text2Python 封装进 MCP Tool”——用 Sampling 做 Server 侧生成(可选高级)

如果你希望 Host 只调用一个工具:text2sql.query(question),由 Server 自己完成“生成→执行→修复”,可以用 MCP 的Sampling:Server 在工具内部请求 Host 提供一次 LLM 采样。Python SDK 示例是 ctx.session.create_message(...)。
这时你的 Server 就能实现一个完整闭环:
  1. 读 db://schema
  2. 采样生成 SQL(结构化输出)
  3. sql_execute 执行
  4. 失败则把报错回喂再采样修复(限定轮次)
注意:MCP 规范对 sampling 的用户控制有明确要求(是否采样、发送的 prompt、返回结果可见性等)。 

9. 安全与攻防:MCP + 执行动作 = 必须默认“零信任”

当你把“读数据、跑代码、改文件”的能力接给模型,安全问题会从“模型胡说”升级为“模型能做事”。MCP 规范本身就把安全与用户控制作为关键原则。 
再强调两个现实教训(都是真实发生过的类型):
  • 工具链漏洞:官方 MCP Git Server 曾出现路径校验问题(CVE-2025-68145),需要升级修复版本;本质是“工具提供文件系统能力时,边界校验必须严密”。 
  • 间接提示注入:来自外部资源(文档、工单、网页)的文本可能携带“隐藏指令”,诱导模型越权调用工具。Microsoft 给出过针对 MCP 场景的缓解建议(隔离指令与数据、最小权限、显式用户确认等)。 
上线清单(最低限):
  • SQL:只读账号、schema 白名单、限时限量、Explain 阈值、审计日志
  • Python:容器/VM 隔离、禁网、配额、工作目录白名单、产物扫描
  • MCP:工具调用需要明确授权 UI、敏感资源需要 OAuth/Token、版本升级与漏洞跟踪

10. 真正要追求的指标:可评估、可回归、可演进

Text2SQL:建议用 Spider/BIRD 风格的“执行正确性”做回归思路,至少在你的业务库上建立query 测试集 + 可执行断言(行数、聚合结果、关键字段)。 
Text2Python:用“单测 + 输出契约 + 资源上限”做回归,把“能跑”变成“稳定产出”。

最新文章

随机文章