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 协议。
网硕互联帮助中心






评论前必须登录!
注册