让 AI 直接查数据库:我写了个 MCP 工具服务
9个数据库工具,STDIO 接入 WorkBuddy,AI 说"帮我查一下"就够了
起因
每次查数据都要:打开数据库客户端 → 选库 → 写 SQL → 执行 → 复制结果 → 粘贴到聊天里。
尤其是那种"帮我看看 users 表最近7天新增了多少"的临时查询,来回切换工具很烦。
于是我想:能不能让 AI 直接查?我只需要说一句话,它帮我去库里查完告诉我。
MCP 是什么
MCP(Model Context Protocol)是 Anthropic 提出的一个协议,让 AI 能调用外部工具。
类比一下:
简单说:你写一个 MCP Server,把数据库操作封装成"工具",AI 就能直接调用。不用写 API,不用部署服务,本地跑就行。
我的9个工具
连接的是 monitor_db(PostgreSQL,14张表),封装了日常数据库操作:
怎么实现的
技术栈
核心代码结构
db-mcp-server/ ├── src/main/java/ │ ├── DbMcpServer.java // 入口,启动STDIO Server│ ├── DatabaseService.java // 数据库操作核心逻辑│ └── tools/│ ├── ListSchemasTool.java │ ├── ListTablesTool.java │ ├── DescribeTableTool.java │ ├── ExecuteQueryTool.java │ └── ... // 共9个├── src/main/resources/ │ └── application.yml // 数据库连接配置└── pom.xml
工具注册示例
// 注册一个"查看表结构"的工具server.addTool("describe_table", "查看指定表的结构,包含列名、数据类型、是否可空、默认值、注释、主键标识", Map.of( "schema", "Schema名称", "tableName", "表名称" ), (args) -> { String schema = args.get("schema").toString(); String table = args.get("tableName").toString(); return databaseService.describeTable(schema, table); } );
每个工具就是:名字 + 描述 + 参数 + 执行逻辑。AI 根据描述自己判断什么时候该用哪个工具,不需要你硬编码调用关系。
接入 WorkBuddy
写好 MCP Server 后,在 WorkBuddy 的 MCP 配置里加上:
// ~/.workbuddy/mcp.json{ "mcpServers": { "db-mcp": { "command": "java", "args": ["-jar", "/path/to/db-mcp-server.jar"], "env": { "DB_URL": "jdbc:postgresql://localhost:5432/monitor_db", "DB_USER": "readonly", "DB_PASSWORD": "***" } } } }
配完之后,AI 就多了9个数据库工具。直接对话:
👤 我:
帮我看看 monitor_db 里有哪些表
🤖 AI:
调用 list_tables 工具查询...
monitor_db 共有 14 张表:sys_user、sys_role、sys_menu、gateway_route、gateway_log...
👤 我:
gateway_log 表最近7天有多少条记录
🤖 AI:
调用 describe_table + execute_query...
gateway_log 最近7天新增 12,847 条记录,日均 1,835 条。
踩的几个坑
坑1:STDIO 协议初始化
MCP 的 STDIO 通信不是随便读写 stdin/stdout 就行的。必须按协议握手:
客户端发 initialize请求服务端回 initialize响应 + capabilities客户端发 initialized通知之后才是正常的工具调用
用官方 Java SDK 可以省掉这些细节,但了解原理有助于排查问题。
坑2:日志不能走 stdout
STDIO 模式下,stdout 是通信通道,只有 MCP 协议消息能走 stdout。
如果你的应用用 System.out.println 或 Logback 输出到 stdout,AI 客户端解析协议时直接报错。
解决:所有日志输出重定向到 stderr,或者配置 Logback 只输出到文件。
# logback-spring.xml<appender name="FILE" class="ch.qos.logback.core.FileAppender"><file>logs/db-mcp-server.log</file></appender><root level="INFO"><!-- 不要写 <appender-ref ref="STDOUT"/> --><appender-ref ref="FILE"/></root>
坑3:SQL 注入防护
execute_query 接受用户输入的 SQL,直接拼到 Statement 里等于裸奔。
防护措施:
execute_query 只允许 SELECT,正则检测开头关键字 强制加 LIMIT,防止一次查出百万行execute_update 需要二次确认,工具描述里标注"⚠️ 危险操作" 数据库用户用只读账号连接,从源头杜绝写操作
// 只允许SELECTString upperSql = sql.trim().toUpperCase();if (!upperSql.startsWith("SELECT")) { throw new IllegalArgumentException("只允许SELECT查询"); }// 强制LIMITif (!upperSql.contains("LIMIT")) { sql = sql + " LIMIT 1000"; }
坑4:连接池泄露
AI 一次对话可能调用多个工具,如果每次都新建连接,连接很快就用完了。
解决:HikariCP 连接池,最大连接数设小一点(10就够了),配合 pool_status 工具随时监控。
实际效果
用了两个月,几个真实场景:
还能做什么
MCP 不只是数据库,任何"AI 不方便直接操作但你能写代码包一层"的场景都适合:
- Redis MCP
——让 AI 直接查缓存、看队列长度 - 文件系统 MCP
——让 AI 读写特定目录的文件 - Git MCP
——让 AI 帮你看 diff、查 log - K8s MCP
——让 AI 查 Pod 状态、看日志
MCP 的本质就是:给 AI 一双手。它本来只能说话,有了 MCP 工具它就能动手干活了。
总结
MCP 不复杂——一个 Server + 几个 Tool,Java 写完打包成 jar STDIO 模式最省事——不用部署服务,本地跑就行 安全要重视——只读账号、SQL 过滤、强制 LIMIT 日志别走 stdout——这是 STDIO 模式最大的坑 连接池控制好——10 个连接够用,多了浪费
最难的不是写代码,是想清楚哪些操作值得封装成工具、工具的粒度怎么定。太粗不好用,太细 AI 不知道调哪个。我的经验:一个工具干一件事,描述写清楚,AI 自己会选。
夜雨聆风