云计算百科
云计算领域专业知识百科平台

Claude 连不上数据库?MCP 服务器 30 分钟从零搭一个

14:00 痛点先摆在台面上

Claude 只能读写文件,连个数据库都查不了,这怎么当生产力工具?

我下午被这事儿卡了快二十分钟。想让 Claude 帮我跑一段用户留存分析,数据在远端,结果它一句"我没有数据库访问能力"把我噎回来。Bash 倒是能跑 psql,可那不叫 Agent,那叫让我自己写脚本再让它执行。

翻了翻《Claude Code 实战:Harness 工程之道》第 6 章,作者把这事说得很透:Claude 本身只有 Read/Bash/Edit 这类基础工具,外部系统全靠 MCP(Model Context Protocol)扩展。MCP 是个客户端-服务器协议——Claude 当客户端,你写的或者别人写好的服务当服务器,中间走 stdio 或 HTTP。书里管这叫把 M×N 问题压成 M+N:M 个模型接 N 个工具,原本要 M×N 个适配,现在大家遵守同一协议,模型方写 M 个客户端、工具方写 N 个服务器就完事。

定位我理解了——MCP 扩展的就是 Agentic Loop 里"工具执行"那一格的工具池。说白了,给 Claude 加挂件。

14:20 配置先跑通

MCP 配置两条路,都简单到离谱。

第一条是 CLI:

# 加一个本地 stdio 服务器
claude mcp add filesystem npx -y @modelcontextprotocol/server-filesystem /workspace

# 加一个远程 HTTP 服务器,带认证头
claude mcp add –transport http –header "Authorization: Bearer ${TOKEN}" \\
company-jira https://jira.company.com/mcp

# 加到用户级别,所有项目都能用
claude mcp add –scope user github — npx -y @modelcontextprotocol/server-github

# 管理
claude mcp list # 看哪些挂上了
claude mcp test github # 测连接
claude mcp remove github # 删掉

第二条是项目根放一个 .mcp.json,团队共享配置走这个:

{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
},
"fetch": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-fetch"]
},
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" }
},
"database": {
"command": "npx",
"args": ["-y", "@bytebase/dbhub", "–dsn", "${DATABASE_URL}"]
}
}
}

我一开始选了 CLI,图快。后来换台机器又得敲一遍,老老实实迁回 .mcp.json。这是个真实教训:临时验证用 CLI,团队/长期用配置文件。环境变量那种 ${VAR:-default} 兜底写法是书里抄的,挺好用。

14:40 实战一:内置 MCP 挂数据库

先上最痛的一刀——数据库。官方有现成的 @modelcontextprotocol/server-postgres,npm 直接跑:

{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "${DATABASE_URL:-postgresql://localhost:5432/mydb}"
}
}
}
}

存到 .mcp.json,claude mcp list 看一眼状态是 connected,问它"这张表有哪些字段"——它真去查了。这一下午就值了。

中途踩了个坑:DATABASE_URL 我开始写死成 localhost,结果 Claude 跑去连我本地的空库,问啥都回"表不存在"。改成 ${DATABASE_URL:-…} 兜底之后才顺。

15:10 实战二:自定义 MCP 服务器

内置的能用,但我想要的不止这些。我想给 Claude 一个 todo 工具,让它自己记任务。这得自己写 MCP 服务器。

书里给了 Python 和 TS 两个版本。我先抄的 Python,因为快。

Python 版(mcp-servers/todo-server.py,配套 requirements.txt 写 mcp>=1.0.0,pip install -r requirements.txt):

from mcp.server import Server
from mcp.server.stdio import stdio_server

server = Server("todo-server")
todos = []

def generate_id():
return str(len(todos) + 1)

@server.tool("todo_add")
async def add_todo(text: str) > str:
"""Add a new todo item"""
todo_id = generate_id()
todos.append({"id": todo_id, "text": text, "done": False})
return f"Added: {todo_id}{text}"

@server.tool("todo_list")
async def list_todos() > str:
"""List all todo items"""
if not todos:
return "No todos found."
return "\\n".join(
f"[{'x' if t['done'] else ' '}] {t['id']}: {t['text']}"
for t in todos
)

async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream)

if __name__ == "__main__":
import asyncio
asyncio.run(main())

跑起来一报错——generate_id 没定义。书里这段省了 ID 生成函数,我自己补了一个 generate_id() 返回 len(todos)+1,能用就行。这是抄书必经环节,别指望开箱即用。

后来我想顺便试 TS 版,因为 TS 版多了个 resource(资源)概念,能暴露 stats://current 这种 URI 给 Claude 读统计。

TS 版(mcp-servers/todo-server.ts,配套 package.json 装 @modelcontextprotocol/sdk 和 zod,记得写 "type": "module"):

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 内存存储
const todos: { id: string; text: string; done: boolean }[] = [];

// 创建 MCP 服务器
const server = new McpServer({
name: "todo-server",
version: "1.0.0",
});

// 工具:添加待办
server.tool(
"todo_add",
"Add a new todo item", // Claude 用这个判断何时调用
{ text: z.string().describe("The todo text") }, // 输入参数 schema
async ({ text }) => {
const todo = {
id: Math.random().toString(36).substring(2, 9),
text,
done: false,
};
todos.push(todo);
return {
content: [{ type: "text", text: `Added: ${todo.id}${todo.text}` }],
};
}
);

// 工具:列出待办
server.tool("todo_list", "List all todo items", {}, async () => {
const text = todos.length === 0
? "No todos found."
: todos.map((t) => `[${t.done ? "x" : " "}] ${t.id}: ${t.text}`).join("\\n");
return { content: [{ type: "text", text }] };
});

// 资源:统计信息
server.resource("stats", "Server statistics", async () => {
return {
contents: [{
uri: "stats://current",
mimeType: "application/json",
text: JSON.stringify({
total: todos.length,
completed: todos.filter((t) => t.done).length,
pending: todos.filter((t) => !t.done).length,
}, null, 2),
}],
};
});

// 启动
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Server started"); // 日志走 stderr,别污染 stdout
}
main().catch(console.error);

这里有个细节我踩了一脚:console.error 不是写错。MCP 走 stdio 通信,stdout 是协议通道,你 print 一条调试日志进去 Claude 就懵了。日志必须吐到 stderr。

挂上之后,Claude 调用工具的命名规则是 mcp__todo-server__todo_add——双下划线分段。这名字可以直接写进 Skills 的 allowed-tools,让 Skill(菜谱)指挥 MCP 工具(食材)干活,书里管这叫"厨房与菜谱"。

15:50 调试三板斧

跑不通的时候三招,从轻到重:

claude mcp list # 看哪些挂上了、状态对不对
claude mcp test todo-server # 单测某一个服务器
claude –mcp-debug # 全局开 debug,看协议层报文

–mcp-debug 是压箱底那招,输出会非常啰嗦,但能看到 Claude 到底发什么、服务器回什么。我 TS 版第一次跑不通就是靠它看出 package.json 里漏写 "type": "module",import 路径全炸。

16:20 收尾

一下午跑下来,MCP 这套东西的体感是:配置简单、协议干净、坑都集中在「抄书少抄了一行」「stdout/stderr 搞混」这种小事上。M+N 的设计是真省事——我今天写的 todo-server,明天接个 Cursor、后天接别的客户端,服务器代码一行不用改。

顺便一提,雷达鸭后台数据查询我后来就是用这套自定义 MCP 服务器接 UniCloud 的 HTTP 接口写的,Claude 直接问留存、问日活,比我自己开控制台点快多了。

那问题来了——既然 MCP 把 Claude 的手脚放开了,数据库能连、API 能调、自定义逻辑能跑,那它和你自己写个 CLI 脚本让 Claude 用 Bash 调,到底差在哪?仅仅是多了层协议包装吗?


标签

MCP、Claude Code、Model Context Protocol、AI Agent、数据库集成

个人介绍

雷达鸭 App 独立开发者,10+ 年软件开发经验,软件设计师、人工智能应用工程师,专注鸿蒙 ArkTS + Web 前端,正在探索 AI 自动化。雷达鸭在华为应用市场和微信小程序都能搜到,收录中国一人公司赚钱案例。

声明

本文基于《Claude Code 实战:Harness 工程之道》(黄佳 著)第 6 章内容整理与实战记录,代码示例与配置参考原书。遵循 MIT 协议。

赞(0)
未经允许不得转载:网硕互联帮助中心 » Claude 连不上数据库?MCP 服务器 30 分钟从零搭一个
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!