Skip to main content
Glama
Khushboo-Mishra

SQL-MCP-101

SQL-MCP-101

MCP 新手? 从交互式教程开始,这是一份逐步点击的指南,带你了解工具、资源和提示,以及如何判断某个功能应该归属于哪一种。

一个小型、注释详尽的 MySQL MCP 服务器,用大约 1,100 行 Python 演示了 Model Context Protocol 的全部三种原语(工具、资源和提示),并附带一个用于探索它的浏览器界面。

这个仓库的存在是为了被阅读,而不仅仅是被运行。如果你已经见过 MCP 被提及,并且想了解构建一个服务器实际上涉及什么,这是一个完整、可运行的示例,小到可以一口气读完:每个文件对应一种原语,注释解释的是为什么而不是是什么,还有一个刻意带有缺陷的演示数据库,让示例找到的是真实问题,而不是玩具问题。

mcp_server/
├── database.py    read-only introspection; the only file not about MCP
├── execution.py   running queries and writes, plus every safety control
├── tools.py       6 TOOLS      inspect structure, cannot read or change a row
├── data_tools.py  6 TOOLS      read rows, and insert / update / delete / alter
├── resources.py   4 RESOURCES  content the APPLICATION attaches (+2 templates)
├── prompts.py     6 PROMPTS    workflows the USER invokes
└── server.py      wires them together, about 10 meaningful lines

该服务器是可读写的:它通过运行真实查询来回答关于数据的问题,并且可以更改数据和模式。它被锁定在一个一次性的演示数据库上,而保证这一点的安全控制措施位于 execution.py 中,下文会进行解释。这种设计本身就是课程的一部分。


唯一值得记住的想法

大多数 MCP 教程只涉及工具,这让人们以为 MCP 就是工具。它实际上是三种原语,区别在于由谁控制:

原语

由谁决定

何时发生

类比

工具

模型

对话中途,自主决定

模型可以调用的函数

资源

应用程序

开始时,由人选择

你附加的文件

提示

用户

明确地,从菜单中选择

一个保存好的专家问题

同一份数据可以以多种形式出现。在这个仓库中,get_table_ddl 是一个工具,而 schema://table/{name}/ddl 是一个资源。同样的字节,通过两种不同的方式获取,因为“模型在决定需要时自行获取”和“人类在开始前附加”确实是不同的需求。


Related MCP server: mysql-mcp-server

快速开始

git clone https://github.com/Khushboo-Mishra/SQL-MCP-101.git
cd SQL-MCP-101
bash scripts/setup.sh

setup.sh 会检查前置条件、创建虚拟环境、安装两个依赖项、创建演示数据库,并端到端验证服务器。遇到第一个缺失项时,它会停止并显示一条具体消息。

然后一次性查看全部三种原语:

bash scripts/run_explorer.sh

环境要求

  • Python 3.10+

  • 本地运行 MySQL 8.x(brew services start mysql)

  • Node.js:可选,仅用于 MCP Inspector

  • Ollama:可选,仅用于界面的聊天面板

默认使用 127.0.0.1:3306 上的 root 且无密码,这是 Homebrew 的默认设置,因此大多数人无需更改。否则请导出 MYSQL_USER、MYSQL_PASSWORD、MYSQL_HOST、MYSQL_PORT。


构建了什么

12 个工具、4 个资源 + 2 个 URI 模板,以及 6 个提示,基于一个六表的演示数据库。

工具:由模型调用

按影响范围而不是按子系统拆分成两个文件。这是一个值得借鉴的刻意设计选择:它让风险面保持很小,并且对任何审查服务器或为其数据库编写 GRANT 的人来说都一目了然。

tools.py:检查结构。不能读取任何行,也不能更改任何内容。

工具

用途

list_tables

所有表和视图,附行数估算

describe_table(table)

列、类型、键、索引、外键

get_table_ddl(table)

精确的 CREATE TABLE 语句

list_relationships

所有已声明的外键

find_sensitive_columns

列名称暗示 PII 或机密的列

search_columns(keyword)

当你忘记某列在哪个表中时,查找该列

data_tools.py:读取行并更改数据。这是有后果的那一半。

工具

用途

run_query(sql, limit)

运行 SELECT 并取回行;这正是回答数据问题的方式

execute_statement(sql)

INSERT / UPDATE / DELETE / CREATE / ALTER / DROP / TRUNCATE

insert_row(table, values)

结构化插入,值作为绑定参数发送

update_rows(table, changes, where)

结构化更新,where 必填

delete_rows(table, where)

结构化删除,where 必填

show_audit_log(limit)

服务器执行过的每一条语句

为什么同时提供通用的 execute_statement 和结构化封装工具? 结构化工具更安全:参数有类型,值被绑定,因此模型永远不会编写 SQL 文本,也不会生成格式错误的内容。但它们只能做你预料到的事情。而通用的 SQL 入口可以处理长尾需求:窗口函数、你未曾预见的 ALTER 语句。大多数真实服务器最终都会同时提供两者,正是这个原因。

资源:由应用程序附加

URI

类型

内容

schema://tables

JSON

表清单

schema://ddl

SQL

整个模式的 DDL

schema://relationships

JSON

所有外键

schema://overview

Markdown

人类可读的摘要

schema://table/{name}

JSON

单个表(模板化)

schema://table/{name}/ddl

SQL

单个表的 DDL(模板化)

静态资源具有固定的 URI,并出现在 resources/list 中,因此客户端可以在选择器中显示它。模板化资源则包含 {placeholders},并出现在 resources/templates/list 中。由于没有固定的列表可显示,客户端需要填写空白部分。

提示:由用户调用

提示

参数

作用

audit_schema

无

五步健康检查:键、关系、PII、命名

explain_table

table

用通俗语言解释一个表

ask_data

question

编写查询,运行它,并用通俗语言回答

modify_data

request

预览 → 确认 → 应用 → 验证,用于更改

document_schema

无

生成参考文档

onboarding_tour

role

引导式初步了解,针对不同角色定制


如何判断:工具、资源还是提示?

这是人们最容易卡住的问题。请按以下顺序逐一判断。

1. 它是执行某个操作,还是获取模型选择的内容? → 工具。 任何模型应该能够自行决定做的事情。

2. 它是否是一份人类在开始前合理附加的文档? → 资源。 参考资料、整个模式的上下文、任何稳定的内容。

3. 它是否是一项人们会重复执行的任务,而提问的方式本身就是专业所在? → 提示。 直接给出那个好问题,而不是指望别人重新发现它。

两条能解决大部分剩余疑问的经验法则:

谁发起? 模型 → 工具。应用程序 → 资源。用户 → 提示。

你希望它出现在菜单中吗? 如果是,它就是提示。菜单是给人用的,只有提示会以命令的形式呈现给人。

本仓库的示例

功能

选择

原因

获取单个表的结构

工具

模型在推理过程中不可预测地需要它

整个模式的 DDL

两者

工具给模型用;资源供人预先附加

模式审计

提示

一项可重复的任务,知道该问什么正是其价值所在

搜索列

工具

需要模型在调用时选择的参数

Markdown 概览

资源

被动参考,无需做决定

常见误区

  • 把所有东西都做成工具。 这可行,但模型会浪费调用次数去获取人类本可以一次性附加的上下文,而且用户得不到可发现的入口点。

  • 把需要模型选择参数的东西做成资源。 如果参数由模型决定,那就是工具。

  • 让提示执行操作。 提示返回的是文本。如果你发现自己要在提示里查询数据库,那你真正需要的是工具。


演示数据库

mcp_demo,六张表,刻意不完美,让示例能找到真实问题:

表

刻意引入的缺陷

CUSTOMERS

EMAIL、PHONE,敏感列扫描会触发

PRODUCTS

SKU 是 UNIQUE,但不是主键,这是一个值得讨论的自然键

ORDERS

(干净,作为参考示例)

ORDER_ITEMS

PRODUCT_ID 看起来像外键,但没有约束

AUDIT_LOG

完全没有主键

legacy_notes

使用 snake_case,而其他所有表都是 UPPER_CASE

对它运行 audit_schema,上述每个问题都应该显现出来。这就是演示的意义:工具找到的是真实问题,而不是玩具问题。


运行

探索器:一次查看所有原语

bash scripts/run_explorer.sh

它会打印 initialize 握手,然后列出并演练工具、资源(静态和模板化)以及提示。先运行它,它可以确认设置正常,并在一屏之内展示整个协议面。

Web 界面:在浏览器中使用全部三种原语

bash scripts/run_ui.sh          # http://127.0.0.1:8000
PORT=9000 bash scripts/run_ui.sh

四个面板,每个展示一件值得展示的内容:

面板

演示内容

聊天

用自然语言提问;模型选择的每个工具都会以内联方式列在答案上方

工具

全部 12 个,按影响范围分组,每个都可以通过表单调用

资源

静态和模板化资源,可原地阅读

提示

展开一个即可查看文本,或直接发送到聊天

底部的一条实时活动栏会显示底层真实的 JSON-RPC:tools/call、resources/read、prompts/get,让协议全程可见。

这个页面本身就是一个 MCP 客户端:它自己无法访问 MySQL。屏幕上的所有内容都是通过 Claude Desktop 所使用的同一协议到达的。

聊天需要通过 Ollama 使用本地 LLM,免费、无需 API 密钥,也不会有任何数据离开你的机器:

brew install ollama && ollama serve
ollama pull qwen2.5:7b

改为设置 ANTHROPIC_API_KEY,它会自动切换到 Claude API。工具、资源和提示面板在完全没有 LLM 的情况下也能工作。

MCP Inspector:Anthropic 自己的客户端

bash scripts/run_inspector.sh

打开打印的 http://localhost:6274?... URL,需要令牌。它有独立的 Tools、Resources 和 Prompts 标签页,这是展示三者最令人信服的方式:其中没有一行是我们的代码,所以如果 Inspector 能驱动服务器,那么服务器就真正符合规范。

建议的参观路线:Tools → describe_table 配合 ORDERS;Resources → schema://overview;Prompts → audit_schema。

Claude Desktop / Claude Code

bash scripts/add_to_claude_desktop.sh    # Claude Desktop, run from Terminal.app
bash scripts/install_claude.sh           # Claude Code, safe to run anywhere

add_to_claude_desktop.sh 会备份你的配置、保留已注册的服务器、验证 JSON、对确切的启动命令进行冒烟测试,并重新启动应用。完成后它会打印一份建议的演示脚本。

然后问:"审计这个数据库",或者从菜单中选择 audit_schema 提示词,这是提示词终于变得可见的地方。

--desktop 必须从 Terminal.app 运行,而不是从 Claude Desktop 内部运行。 Claude Desktop 将配置保存在内存中,并从该副本重写文件,因此在其运行期间所做的编辑会被静默丢弃。该脚本会退出应用、编辑并重新启动,这会终止你启动它的那个会话。


阅读代码

从头到尾大约一小时。这个顺序可以逐步构建,无需前向引用:

1. mcp_server/server.py:从这里开始。十行有意义的代码,整个架构一屏即可容纳:创建服务器、注册三个原语、运行。其他都是细节。

2. mcp_server/database.py:普通的 MySQL 代码,其中完全没有 MCP。值得早点阅读,因为它展示了 MCP 层有多薄:如果你已经有一个数据访问层,你就已经完成了一大半。

仔细看看 safe_identifier。MySQL 不允许你将表名绑定为参数(SHOW CREATE TABLE %s 不是有效的 SQL),因此标识符必须被插值到字符串中。这是一个真正的注入风险,而那个小函数正是让它安全的关键。

3. mcp_server/tools.py:@mcp.tool() 装饰器,以及整个项目中工作量最大的思想:文档字符串就是提示词。它是模型在决定是否调用工具时唯一会读取的内容,所以它是为模型而写的,而不是为阅读源码的人写的。

4. mcp_server/resources.py:静态 URI 与模板化 URI 的对比,以及为什么 get_table_ddl 同时作为工具和资源存在。这种重复是刻意的,也是"谁控制什么"这一思想最清晰的例证。

5. mcp_server/prompts.py:提示词返回的是文本,而不是数据。文本是一条指令,通常告诉模型该使用哪些工具。文件很短,也是大多数人从未见过的一个。

6. mcp_server/execution.py:当你想知道如何让写访问变得安全时再读这个。五个控制项,每个都附有注释说明它防止了什么。

7. examples/explore_server.py:协议的另一面。一个极简客户端,列出并调用所有内容,这样你就能看到实际在线上传输的是什么。


更进一步

这个服务器限定在一个数据库范围内,以保持示例简短。要进一步扩展:

  • 多个 schema:将 schema 作为工具参数,而不是读取 MYSQL_DEMO_SCHEMA。添加一个允许列表,使代理无法触达生产环境。

  • 查询执行:一个 run_query 工具。可行,但它会彻底改变安全性的故事:服务器随后需要能读取你表的凭据,而结果会进入模型的上下文。强制只允许 SELECT,注入一个 LIMIT,并使用只读数据库用户。

  • 远程传输:mcp.run(transport="streamable-http")。同样的工具,同样的代码,不同的管道。在暴露之前添加身份验证。

  • 缓存:describe_table 每次调用都会访问数据库。一旦模型开始循环调用它,一个短 TTL 缓存就值得了。


安全说明

这个服务器可以更改你的数据。这是刻意的:"代理能否写入我的数据库?"是每个团队都会问的问题,而一个如何安全做到这一点的可用示例,比一个回避该主题的示例更有用。但这确实意味着控制项很重要。

五个控制项,全部在 execution.py 中

控制项

它阻止了什么

Schema 锁

每条语句都在固定连接到演示数据库的连接上运行;对任何其他数据库的引用都会被拒绝

每次调用一条语句

第二条语句不能搭合法语句的便车

独立的读/写入口

run_query 拒绝写入,execute_statement 拒绝读取,因此两者都无法被说服去做对方的工作

行数上限

宽泛的 SELECT 不能淹没模型的上下文

审计日志

每条语句都被记录,并可通过 show_audit_log 读取

一个拒绝列表还会拒绝那些会逃逸 schema 锁、触达文件系统或更改服务器级状态的语句,包括权限变更、用户管理、文件导入/导出以及数据库级操作。

有一个微妙之处,因为它是一个容易重复犯的错误:schema 锁不能仅靠模式匹配来工作。在 SQL 中,a.b 通常是 alias.column(SELECT c.NAME FROM CUSTOMERS c),而不是 schema.table,因此拒绝每个带点的名称会破坏普通的连接操作,这正是这个项目第一个版本所犯的 bug。现在它会将每个限定符与服务器上实际的数据库列表进行比较:真实的数据库名称会被拒绝,表别名则原样通过。

将其指向受限用户

上述控制项是纵深防御,而不是防御本身。在演示之外的任何场景中,请以 MySQL 用户身份连接,其授权仅覆盖你打算暴露的 schema。如果凭据无法触达生产环境,那么提示注入或模型错误也无法触达。

还有两件事值得直说:

  • 表名不能作为绑定参数。 SHOW CREATE TABLE %s 不是有效的 SQL,因此标识符必须被插值,这是一个真正的注入点。database.safe_identifier 正是让它安全的关键,它是整个项目中最重要的函数。

  • 连接的 MySQL 用户才是真正的边界。 给它一个限定在你打算暴露的 schema 范围内的只读 GRANT。代码的只读性是纵深防御,而不是防御本身。


许可证

MIT,参见 LICENSE。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with MySQL databases through MCP, supporting query execution, table operations (insert, update, delete), and schema inspection for natural language database management.
    202 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables MySQL database operations through MCP, including executing SQL queries, listing databases and tables, and describing table structures.
    730 npm
    5
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables natural language interaction with MySQL databases through MCP, supporting SQL execution, schema exploration, and database management via tools, resources, and prompts.
    5
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables natural language interaction with MySQL databases through MCP tools for querying, executing DDL/DML, listing databases/tables, and describing table schemas, with parameterized queries and read-only mode.
    730 npm
    MIT