AI Day 56
AI Day 56: 实战(6):MCP Server开发 — 扩展AI的能力边界
AI Day 56: 实战(6):MCP Server开发 — 扩展AI的能力边界
2026-05-27
日期: 2026-05-27 | 阶段: 第五阶段 · 动手实战 (Day 51-60) | 主题: MCP Server Development
学习路径 / Learning Path
AI/LLM 深度技术学习 60天计划
├── 第一阶段:模型基础 (Day 1-15) ✅
│ ├── Day 1: Transformer与LLM基础 ✅
│ ├── Day 2: 量化与本地部署 ✅
│ ├── Day 3: 训练全流程 ✅
│ ├── Day 4: Prompt Engineering ✅
│ ├── Day 5: RAG架构 ✅
│ ├── Day 6: 向量数据库与Embedding ✅
│ ├── Day 7: 微调技术 ✅
│ ├── Day 8: 推理优化 ✅
│ ├── Day 9: 长上下文技术 ✅
│ ├── Day 10: 多模态模型 ✅
│ ├── Day 11: 推理模型 ✅
│ ├── Day 12: Agent框架 ✅
│ ├── Day 13: MCP协议 ✅
│ ├── Day 14: 模型评估 ✅
│ └── Day 15: 阶段一总结 ✅
├── 第二阶段:工程实践 (Day 16-30) ✅
│ ├── Day 16: LLM应用架构 ✅
│ ├── Day 17: 安全与护栏 ✅
│ ├── Day 18: 可观测性 ✅
│ ├── Day 19: 生产RAG·解析与分块 ✅
│ ├── Day 20: 生产RAG·检索与重排 ✅
│ ├── Day 21: 生产RAG·评估与迭代 ✅
│ ├── Day 22: Agent状态与恢复 ✅
│ ├── Day 23: Agent成本优化 ✅
│ ├── Day 24: 多Agent系统 ✅
│ ├── Day 25: Agent测试部署 ✅
│ ├── Day 26: LLM成本工程 ✅
│ ├── Day 27: 多模型编排 ✅
│ ├── Day 28: LLM应用测试 ✅
│ ├── Day 29: 企业LLM平台 ✅
│ └── Day 30: 阶段二总结 ✅
├── 第三阶段:金融零售AI应用 (Day 31-42) ✅
│ ├── Day 31: 金融AI风控 ✅
│ ├── Day 32: 智能投顾与量化 ✅
│ ├── Day 33: 合规与RegTech ✅
│ ├── Day 34: 信贷AI全链路 ✅
│ ├── Day 35: 金融AI总结 ✅
│ ├── Day 36: 零售AI推荐 ✅
│ ├── Day 37: 智能客服 ✅
│ ├── Day 38: 供应链AI ✅
│ ├── Day 39: 智能营销 ✅
│ ├── Day 40: 零售AI总结 ✅
│ ├── Day 41: CeFi×DeFi×AI融合 ✅
│ └── Day 42: AI融合案例与职业 ✅
├── 第四阶段:面试冲刺 (Day 43-50) ✅
│ ├── Day 43: 系统设计·LLM平台 ✅
│ ├── Day 44: 系统设计·RAG系统 ✅
│ ├── Day 45: 系统设计·Agent系统 ✅
│ ├── Day 46: 系统设计·推荐系统 ✅
│ ├── Day 47: 面试·产品AI ✅
│ ├── Day 48: 面试·架构AI ✅
│ ├── Day 49: 面试·行为AI ✅
│ └── Day 50: 学习总结 ✅
└── 第五阶段:动手实战 (Day 51-60)
├── Day 51: 本地大模型部署全流程 ✅
├── Day 52: RAG系统实战:从文档到问答 ✅
├── Day 53: RAG进阶:评估优化与生产化 ✅
├── Day 54: LoRA微调实战:训练你的专属模型 ✅
├── Day 55: Agent开发实战:构建工具调用Agent ✅
├── Day 56: MCP Server开发:扩展AI能力边界 ← 你在这里
├── Day 57: 多模态应用:图文理解与文档分析
├── Day 58: AI应用全栈开发:前后端集成
├── Day 59: 性能调优与成本实战
└── Day 60: 总结与作品集
核心概念 / Core Concepts
MCP 让你的工具能被任何 AI 客户端调用 / One Server, Every Client
Day 55 的 Agent 工具 vs Day 56 的 MCP Server:
Day 55 Agent 工具:
get_token_price() ──→ 只有你的 Agent 能用
get_protocol_tvl() ──→ 写在代码里,绑定框架
web_search() ──→ 换个项目要重写
Day 56 MCP Server:
get_token_price() ──→ Claude Code 能用
──→ Cursor 能用
──→ Windsurf 能用
──→ 任何 MCP 客户端都能用
──→ 一次开发,处处可用!
这就是 MCP 的核心价值:标准化的工具接口协议
Day 13 理论回顾 → 今天实践 / Theory Recap → Practice
Day 13 学了 MCP 的架构:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Client │────→│ Protocol │────→│ Server │
│ (Claude) │←────│ (MCP) │←────│ (你开发) │
└──────────┘ └──────────┘ └──────────┘
MCP Server 提供三种能力:
1. Tools — 可调用的函数(类似 API)
2. Resources — 可读取的数据(类似文件系统)
3. Prompts — 预定义的提示模板
今天的目标:
理论 实践
Day 13 "MCP 有三种能力" → 实际实现 Tools + Resources
Day 13 "JSON-RPC 通信" → 用 SDK 处理底层协议
Day 13 "stdio 传输" → 让 Claude Code 连接你的 Server
从"知道 MCP 是什么"到"发布一个真正的 MCP Server"!
知识点1:MCP SDK 安装与项目初始化 / MCP SDK Setup
TypeScript SDK 选择 / Why TypeScript
MCP 有两个官方 SDK:
TypeScript SDK — 更成熟、示例更多、npm 生态
Python SDK — 也可以,但社区资源较少
选择 TypeScript 的理由:
1. MCP 诞生于 Anthropic,生态以 TS 为主
2. npm 发布更方便(MCP Server 通常是 CLI 工具)
3. 类型系统帮助保证协议正确性
4. 大多数开源 MCP Server 都是 TS 写的
不用担心不会 TS:
今天的代码不复杂
主要是填充 MCP SDK 的模板
核心逻辑和 Day 55 的 Python 工具类似
项目初始化 / Project Initialization
# === Step 1: 创建项目目录 ===
mkdir mcp-notes-server
cd mcp-notes-server
# === Step 2: 初始化 npm 项目 ===
npm init -y
# === Step 3: 安装 MCP SDK ===
npm install @modelcontextprotocol/sdk
# === Step 4: 安装 TypeScript 相关 ===
npm install -D typescript @types/node tsx
# === Step 5: 初始化 TypeScript ===
npx tsc --init
# === Step 6: 其他依赖 ===
npm install zod # 输入验证
npm install glob # 文件匹配
npm install gray-matter # Markdown frontmatter 解析
项目结构 / Project Structure
mcp-notes-server/
├── src/
│ ├── index.ts # MCP Server 入口
│ ├── tools/
│ │ ├── search.ts # 笔记搜索工具
│ │ └── content.ts # 内容获取工具
│ ├── resources/
│ │ └── notes.ts # 笔记资源定义
│ └── utils/
│ ├── markdown.ts # Markdown 解析
│ └── search.ts # 搜索算法
├── package.json
├── tsconfig.json
└── README.md
tsconfig.json 配置 / TypeScript Config
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
package.json 关键配置 / Package.json
{
"name": "mcp-notes-server",
"version": "1.0.0",
"description": "MCP Server for searching and reading learning notes",
"type": "module",
"bin": {
"mcp-notes-server": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsx src/index.ts",
"start": "node dist/index.js"
},
"keywords": ["mcp", "notes", "search", "ai"],
"license": "MIT"
}
知识点2:第一个 MCP Server — 笔记搜索 / First MCP Server: Note Search
完整实现 / Full Implementation
/**
* src/index.ts
* MCP Notes Server — 让 AI 能搜索和读取你的学习笔记
*
* 提供的能力:
* - Tool: search_notes — 搜索笔记内容
* - Tool: get_note_content — 获取完整笔记内容
* - Resource: notes://list — 浏览所有笔记列表
*
* Day 13 学的 MCP 协议 → 今天的实际实现
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import * as fs from "fs";
import * as path from "path";
import { glob } from "glob";
// ==========================================
// 配置
// ==========================================
const NOTES_DIR = process.env.NOTES_DIR || "E:/code/momofinance/momoweb3/docs/ai";
// ==========================================
// 工具函数
// ==========================================
interface NoteInfo {
filename: string;
title: string;
day: number;
path: string;
size: number;
}
/**
* 扫描笔记目录,获取所有笔记信息
*/
function scanNotes(): NoteInfo[] {
const pattern = path.join(NOTES_DIR, "day*.md").replace(/\\/g, "/");
const files = glob.sync(pattern);
return files.map((filePath) => {
const filename = path.basename(filePath);
const content = fs.readFileSync(filePath, "utf-8");
const firstLine = content.split("\n")[0] || "";
const title = firstLine.replace(/^#\s*/, "").trim();
const dayMatch = filename.match(/day(\d+)/);
const day = dayMatch ? parseInt(dayMatch[1]) : 0;
return {
filename,
title,
day,
path: filePath,
size: content.length,
};
}).sort((a, b) => a.day - b.day);
}
/**
* 在笔记中搜索关键词
*/
function searchInNotes(
query: string,
maxResults: number = 5
): Array<{ filename: string; title: string; matches: string[]; score: number }> {
const notes = scanNotes();
const queryLower = query.toLowerCase();
const queryTerms = queryLower.split(/\s+/).filter(Boolean);
const results: Array<{
filename: string;
title: string;
matches: string[];
score: number;
}> = [];
for (const note of notes) {
const content = fs.readFileSync(note.path, "utf-8");
const contentLower = content.toLowerCase();
// 计算相关性分数
let score = 0;
const matches: string[] = [];
for (const term of queryTerms) {
// 标题匹配权重更高
if (note.title.toLowerCase().includes(term)) {
score += 10;
}
// 内容匹配
const termCount = (contentLower.match(new RegExp(term, "g")) || []).length;
score += termCount;
// 提取匹配上下文
if (termCount > 0) {
const idx = contentLower.indexOf(term);
const start = Math.max(0, idx - 50);
const end = Math.min(content.length, idx + term.length + 100);
const snippet = content.substring(start, end).replace(/\n/g, " ").trim();
if (snippet && !matches.includes(snippet)) {
matches.push(`...${snippet}...`);
}
}
}
if (score > 0) {
results.push({
filename: note.filename,
title: note.title,
matches: matches.slice(0, 3), // 最多3个匹配片段
score,
});
}
}
// 按分数排序
return results
.sort((a, b) => b.score - a.score)
.slice(0, maxResults);
}
// ==========================================
// 创建 MCP Server
// ==========================================
const server = new Server(
{
name: "mcp-notes-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// ==========================================
// 注册 Tools
// ==========================================
/**
* 列出所有可用工具
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "search_notes",
description:
"搜索学习笔记。输入关键词,返回最相关的笔记和匹配内容片段。" +
"支持中英文搜索。" +
"适用于查找特定知识点、概念定义、或相关笔记。",
inputSchema: {
type: "object" as const,
properties: {
query: {
type: "string",
description: "搜索关键词,如 'LoRA微调' 或 'RAG evaluation'",
},
max_results: {
type: "number",
description: "最大返回结果数,默认5",
default: 5,
},
},
required: ["query"],
},
},
{
name: "get_note_content",
description:
"获取指定笔记的完整内容。" +
"输入笔记的文件名(如 'day7-finetuning-lora-qlora.md'),返回完整的 Markdown 内容。" +
"通常先用 search_notes 找到相关笔记,再用此工具获取详细内容。",
inputSchema: {
type: "object" as const,
properties: {
filename: {
type: "string",
description: "笔记文件名,如 'day7-finetuning-lora-qlora.md'",
},
section: {
type: "string",
description: "可选,只返回包含该关键词的章节",
},
},
required: ["filename"],
},
},
],
};
});
/**
* 执行工具调用
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "search_notes": {
const query = (args as { query: string; max_results?: number }).query;
const maxResults = (args as { max_results?: number }).max_results || 5;
const results = searchInNotes(query, maxResults);
if (results.length === 0) {
return {
content: [
{
type: "text" as const,
text: `未找到与"${query}"相关的笔记。请尝试其他关键词。`,
},
],
};
}
const formatted = results.map((r, i) =>
`[${i + 1}] ${r.filename}\n` +
` 标题: ${r.title}\n` +
` 相关性: ${r.score}\n` +
` 匹配内容:\n${r.matches.map(m => ` ${m}`).join("\n")}`
).join("\n\n");
return {
content: [
{
type: "text" as const,
text: `找到 ${results.length} 个相关笔记:\n\n${formatted}`,
},
],
};
}
case "get_note_content": {
const filename = (args as { filename: string; section?: string }).filename;
const section = (args as { section?: string }).section;
const filePath = path.join(NOTES_DIR, filename);
if (!fs.existsSync(filePath)) {
return {
content: [
{
type: "text" as const,
text: `文件不存在: ${filename}。请使用 search_notes 查找正确的文件名。`,
},
],
isError: true,
};
}
let content = fs.readFileSync(filePath, "utf-8");
// 如果指定了 section,只返回相关部分
if (section) {
const sectionLower = section.toLowerCase();
const lines = content.split("\n");
const relevantSections: string[] = [];
let capturing = false;
let currentSection: string[] = [];
for (const line of lines) {
if (line.match(/^#{1,3}\s/)) {
// 新的标题行
if (capturing && currentSection.length > 0) {
relevantSections.push(currentSection.join("\n"));
}
capturing = line.toLowerCase().includes(sectionLower);
currentSection = capturing ? [line] : [];
} else if (capturing) {
currentSection.push(line);
}
}
if (capturing && currentSection.length > 0) {
relevantSections.push(currentSection.join("\n"));
}
if (relevantSections.length > 0) {
content = relevantSections.join("\n\n---\n\n");
} else {
content = `未找到包含"${section}"的章节。\n\n完整内容(前2000字):\n\n${content.slice(0, 2000)}...`;
}
}
// 截断过长内容
if (content.length > 5000) {
content = content.slice(0, 5000) + `\n\n... (内容过长,已截断。共 ${content.length} 字)`;
}
return {
content: [
{
type: "text" as const,
text: content,
},
],
};
}
default:
return {
content: [
{
type: "text" as const,
text: `未知工具: ${name}`,
},
],
isError: true,
};
}
});
// ==========================================
// 注册 Resources
// ==========================================
/**
* 列出所有可用资源
*/
server.setRequestHandler(ListResourcesRequestSchema, async () => {
const notes = scanNotes();
return {
resources: [
{
uri: "notes://list",
name: "学习笔记列表",
description: `所有 AI/LLM 学习笔记索引 (共 ${notes.length} 篇)`,
mimeType: "application/json",
},
// 每个笔记也是一个资源
...notes.map((note) => ({
uri: `notes://${note.filename}`,
name: `Day ${note.day}: ${note.title}`,
description: `学习笔记 ${note.filename} (${Math.round(note.size / 1024)}KB)`,
mimeType: "text/markdown",
})),
],
};
});
/**
* 读取资源内容
*/
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
if (uri === "notes://list") {
const notes = scanNotes();
const list = notes.map((n) => ({
day: n.day,
filename: n.filename,
title: n.title,
size_kb: Math.round(n.size / 1024),
}));
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(list, null, 2),
},
],
};
}
// notes://day7-finetuning-lora-qlora.md
const filename = uri.replace("notes://", "");
const filePath = path.join(NOTES_DIR, filename);
if (!fs.existsSync(filePath)) {
throw new Error(`Resource not found: ${uri}`);
}
const content = fs.readFileSync(filePath, "utf-8");
return {
contents: [
{
uri,
mimeType: "text/markdown",
text: content,
},
],
};
});
// ==========================================
// 启动 Server
// ==========================================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Notes Server running on stdio");
console.error(`Notes directory: ${NOTES_DIR}`);
console.error(`Notes found: ${scanNotes().length}`);
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
知识点3:高级功能 / Advanced Features
Prompt 模板 / Prompt Templates
/**
* src/prompts.ts
* MCP Prompt 模板 — 预定义的提示词,客户端可以直接使用
*
* Day 4 学的 Prompt Engineering 技巧
* → 封装成 MCP Prompt 让任何客户端使用
*/
import {
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
export function registerPrompts(server: Server) {
/**
* 列出所有 Prompt 模板
*/
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: "summarize_day",
description: "总结指定Day的学习笔记,提取关键知识点",
arguments: [
{
name: "day_number",
description: "Day编号,如 7",
required: true,
},
],
},
{
name: "compare_concepts",
description: "对比两个概念的异同(从笔记中提取信息)",
arguments: [
{
name: "concept_a",
description: "第一个概念,如 'RAG'",
required: true,
},
{
name: "concept_b",
description: "第二个概念,如 '微调'",
required: true,
},
],
},
{
name: "interview_prep",
description: "基于笔记内容,为指定话题准备面试答案",
arguments: [
{
name: "topic",
description: "面试话题,如 'Agent架构设计'",
required: true,
},
],
},
],
};
});
/**
* 获取 Prompt 内容
*/
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "summarize_day": {
const dayNumber = args?.day_number || "1";
return {
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text:
`请先使用 search_notes 工具搜索 "Day ${dayNumber}" 相关的笔记,` +
`然后使用 get_note_content 获取完整内容,最后按以下格式总结:\n\n` +
`## Day ${dayNumber} 学习总结\n\n` +
`### 核心概念(3-5个要点)\n` +
`### 关键代码/命令\n` +
`### 面试可能问到的问题\n` +
`### 与其他Day的关联\n` +
`### 一句话总结`,
},
},
],
};
}
case "compare_concepts": {
const a = args?.concept_a || "概念A";
const b = args?.concept_b || "概念B";
return {
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text:
`请分别搜索"${a}"和"${b}"相关的笔记,然后按以下格式对比:\n\n` +
`## ${a} vs ${b} 对比分析\n\n` +
`| 维度 | ${a} | ${b} |\n` +
`|------|------|------|\n` +
`| 核心原理 | | |\n` +
`| 适用场景 | | |\n` +
`| 优势 | | |\n` +
`| 劣势 | | |\n` +
`| 成本 | | |\n\n` +
`### 什么时候用 ${a}?\n` +
`### 什么时候用 ${b}?\n` +
`### 能否结合使用?`,
},
},
],
};
}
case "interview_prep": {
const topic = args?.topic || "AI";
return {
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text:
`请搜索"${topic}"相关的笔记,然后帮我准备面试答案:\n\n` +
`## ${topic} 面试准备\n\n` +
`### 30秒版本(电梯话术)\n` +
`### 2分钟详细版本\n` +
`### 可能的追问及答案\n` +
`### 实际案例/项目经验\n` +
`### 需要注意的"坑"`,
},
},
],
};
}
default:
throw new Error(`Unknown prompt: ${name}`);
}
});
}
多 Tool 组合与流式响应 / Multi-Tool & Streaming
/**
* 高级工具:笔记分析(组合多个基础能力)
*
* 这个工具展示了如何在 MCP Server 中
* 实现复杂的多步骤逻辑
*/
// 在 ListToolsRequestSchema 中添加:
{
name: "analyze_learning_progress",
description:
"分析学习进度和知识覆盖度。" +
"扫描所有笔记,统计各阶段完成情况、知识点覆盖、" +
"和建议下一步学习方向。",
inputSchema: {
type: "object" as const,
properties: {
stage: {
type: "string",
description: "可选,只分析指定阶段,如 'phase1', 'phase2'",
enum: ["phase1", "phase2", "phase3", "phase4", "phase5", "all"],
},
},
},
}
// 在 CallToolRequestSchema 中添加:
case "analyze_learning_progress": {
const notes = scanNotes();
const stage = (args as { stage?: string }).stage || "all";
// 按阶段分组
const phases: Record<string, NoteInfo[]> = {
phase1: notes.filter(n => n.day >= 1 && n.day <= 15),
phase2: notes.filter(n => n.day >= 16 && n.day <= 30),
phase3: notes.filter(n => n.day >= 31 && n.day <= 42),
phase4: notes.filter(n => n.day >= 43 && n.day <= 50),
phase5: notes.filter(n => n.day >= 51 && n.day <= 60),
};
const phaseNames: Record<string, string> = {
phase1: "模型基础 (Day 1-15)",
phase2: "工程实践 (Day 16-30)",
phase3: "金融零售AI应用 (Day 31-42)",
phase4: "面试冲刺 (Day 43-50)",
phase5: "动手实战 (Day 51-60)",
};
const phaseExpected: Record<string, number> = {
phase1: 15, phase2: 15, phase3: 12, phase4: 8, phase5: 10,
};
let report = "# 学习进度分析报告\n\n";
const targetPhases = stage === "all"
? Object.keys(phases)
: [stage];
for (const p of targetPhases) {
const completed = phases[p]?.length || 0;
const expected = phaseExpected[p] || 0;
const pct = expected > 0 ? Math.round((completed / expected) * 100) : 0;
const bar = "█".repeat(Math.round(pct / 5)) + "░".repeat(20 - Math.round(pct / 5));
report += `## ${phaseNames[p]}\n`;
report += `进度: [${bar}] ${pct}% (${completed}/${expected})\n`;
if (phases[p] && phases[p].length > 0) {
report += `已完成:\n`;
for (const note of phases[p]) {
report += ` - Day ${note.day}: ${note.title}\n`;
}
}
report += "\n";
}
// 总体统计
const totalCompleted = notes.length;
const totalExpected = 60;
report += `## 总体进度\n`;
report += `已完成 ${totalCompleted}/${totalExpected} 天 (${Math.round(totalCompleted / totalExpected * 100)}%)\n`;
report += `笔记总大小: ${Math.round(notes.reduce((s, n) => s + n.size, 0) / 1024)} KB\n`;
return {
content: [{ type: "text" as const, text: report }],
};
}
错误处理最佳实践 / Error Handling Best Practices
/**
* MCP Server 的错误处理
*
* Day 17 学的安全与护栏 + Day 22 的错误恢复
* → 在 MCP Server 中的实践
*/
// 1. 输入验证
function validateFilename(filename: string): boolean {
// 防止路径遍历攻击
if (filename.includes("..") || filename.includes("/") || filename.includes("\\")) {
return false;
}
// 只允许 .md 文件
if (!filename.endsWith(".md")) {
return false;
}
return true;
}
// 2. 在工具调用中使用
case "get_note_content": {
const filename = (args as { filename: string }).filename;
if (!validateFilename(filename)) {
return {
content: [{
type: "text" as const,
text: `无效的文件名: "${filename}"。文件名只能包含字母、数字、连字符,且必须以 .md 结尾。`,
}],
isError: true,
};
}
// ... 正常处理
}
// 3. 全局错误处理
server.onerror = (error: Error) => {
console.error("[MCP Server Error]", error.message);
// 不要崩溃,记录错误继续运行
};
// 4. 超时保护
function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms)
),
]);
}
知识点4:与 Claude Code 集成 / Claude Code Integration
配置 MCP Server / Configuring MCP Server
// .claude/settings.local.json (项目级配置)
// 或 ~/.claude/settings.json (全局配置)
{
"mcpServers": {
"notes-server": {
"command": "node",
"args": ["E:/code/momofinance/momoweb3/mcp-notes-server/dist/index.js"],
"env": {
"NOTES_DIR": "E:/code/momofinance/momoweb3/docs/ai"
}
}
}
}
构建并测试 / Build & Test
# === Step 1: 构建 TypeScript ===
cd mcp-notes-server
npm run build
# === Step 2: 本地测试 (直接运行) ===
# MCP Server 通过 stdio 通信
# 手动测试需要发送 JSON-RPC 消息
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
# === Step 3: 在 Claude Code 中测试 ===
# 重启 Claude Code,它会自动连接配置的 MCP Server
# 然后你可以这样用:
# "搜索我笔记中关于 LoRA 的内容"
# → Claude Code 会自动调用 search_notes 工具
# "读取 Day 7 的完整笔记"
# → Claude Code 会调用 get_note_content 工具
# "帮我总结 Day 51-55 的实战收获"
# → Claude Code 会多次调用工具,综合生成总结
实际效果展示 / Demo in Claude Code
在 Claude Code 中的对话效果:
用户: "我之前学的RAG系统,有哪些关键指标?"
Claude Code:
→ 调用 search_notes("RAG 评估 指标")
→ 找到 day21-production-rag-evaluation.md, day53-hands-on-rag-optimization.md
→ 调用 get_note_content("day21-production-rag-evaluation.md", section: "评估")
→ 整合信息后回答:
"根据你 Day 21 和 Day 53 的笔记,RAG 系统的关键评估指标是 RAGAS 框架的四个维度:
1. Faithfulness(忠实度)— 回答是否忠于检索到的上下文
2. Answer Relevancy(回答相关性)— 回答是否与问题相关
3. Context Precision(上下文精度)— 检索到的内容是否精确相关
4. Context Recall(上下文召回)— 是否检索到了所有相关内容
在 Day 53 的实战中,你通过 Golden QA 集做了基线评估..."
这就是 MCP 的价值:
Claude Code 能直接访问你的个人知识库
不需要手动复制粘贴笔记内容
AI 助手变成了"懂你"的助手
知识点5:Web3 MCP Server / Web3 Data MCP Server
链上数据查询 Server / On-chain Data Query Server
/**
* src/web3-server.ts
* Web3 MCP Server — 查询链上数据
*
* 提供的工具:
* - get_eth_price: 查询 ETH 实时价格
* - get_token_balance: 查询地址的 Token 余额
* - get_gas_price: 查询当前 Gas 价格
*
* 结合 Day 55 的 Web3 Agent 工具
* → 封装成 MCP Server 让任何 AI 客户端使用
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// ==========================================
// API 配置
// ==========================================
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || "";
const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY || "";
// ==========================================
// HTTP 请求工具
// ==========================================
async function fetchJSON(url: string): Promise<any> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} finally {
clearTimeout(timeout);
}
}
// ==========================================
// 创建 Web3 MCP Server
// ==========================================
const server = new Server(
{ name: "mcp-web3-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// ==========================================
// 注册 Tools
// ==========================================
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_eth_price",
description:
"获取 ETH 的实时价格和市场数据。" +
"返回当前价格(USD)、24h变化、市值等。" +
"数据来源: CoinGecko API。",
inputSchema: {
type: "object" as const,
properties: {
currency: {
type: "string",
description: "价格货币单位,默认 'usd'",
default: "usd",
},
},
},
},
{
name: "get_token_balance",
description:
"查询以太坊地址的 ETH 余额和主要 ERC20 代币余额。" +
"输入以太坊地址(0x开头),返回余额信息。" +
"需要 ETHERSCAN_API_KEY 环境变量。",
inputSchema: {
type: "object" as const,
properties: {
address: {
type: "string",
description: "以太坊地址,如 '0x...'",
},
},
required: ["address"],
},
},
{
name: "get_gas_price",
description:
"获取以太坊当前 Gas 价格。" +
"返回 Safe/Proposed/Fast 三档 Gas 价格(Gwei)。" +
"帮助判断当前是否适合发送交易。",
inputSchema: {
type: "object" as const,
properties: {},
},
},
{
name: "get_protocol_tvl",
description:
"获取 DeFi 协议的 TVL(总锁仓量)。" +
"输入协议名称(如 'uniswap', 'aave', 'lido')," +
"返回当前 TVL、链分布等数据。数据来源: DeFiLlama。",
inputSchema: {
type: "object" as const,
properties: {
protocol: {
type: "string",
description: "协议名称,如 'uniswap', 'aave', 'lido'",
},
},
required: ["protocol"],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "get_eth_price": {
const currency = (args as { currency?: string }).currency || "usd";
const data = await fetchJSON(
`https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=${currency}&include_24hr_change=true&include_market_cap=true&include_24hr_vol=true`
);
const eth = data.ethereum;
const result = [
`ETH 实时价格:`,
` 价格: $${eth[currency]?.toLocaleString()}`,
` 24h 变化: ${eth[`${currency}_24h_change`]?.toFixed(2)}%`,
` 市值: $${(eth[`${currency}_market_cap`] / 1e9)?.toFixed(2)}B`,
` 24h 交易量: $${(eth[`${currency}_24h_vol`] / 1e9)?.toFixed(2)}B`,
` 数据时间: ${new Date().toISOString()}`,
].join("\n");
return { content: [{ type: "text" as const, text: result }] };
}
case "get_token_balance": {
const address = (args as { address: string }).address;
// 验证地址格式
if (!address.match(/^0x[a-fA-F0-9]{40}$/)) {
return {
content: [{
type: "text" as const,
text: `无效的以太坊地址格式: ${address}`,
}],
isError: true,
};
}
if (!ETHERSCAN_API_KEY) {
return {
content: [{
type: "text" as const,
text: "需要配置 ETHERSCAN_API_KEY 环境变量才能查询余额。",
}],
isError: true,
};
}
const data = await fetchJSON(
`https://api.etherscan.io/api?module=account&action=balance&address=${address}&tag=latest&apikey=${ETHERSCAN_API_KEY}`
);
if (data.status !== "1") {
return {
content: [{
type: "text" as const,
text: `查询失败: ${data.message}`,
}],
isError: true,
};
}
const balanceWei = BigInt(data.result);
const balanceEth = Number(balanceWei) / 1e18;
const result = [
`地址: ${address}`,
`ETH 余额: ${balanceEth.toFixed(6)} ETH`,
`Wei: ${balanceWei.toString()}`,
].join("\n");
return { content: [{ type: "text" as const, text: result }] };
}
case "get_gas_price": {
if (!ETHERSCAN_API_KEY) {
// 使用公开 API 作为后备
const data = await fetchJSON(
`https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=${ETHERSCAN_API_KEY || "YourApiKeyToken"}`
);
if (data.status === "1") {
const gas = data.result;
const result = [
`以太坊当前 Gas 价格:`,
` Safe (慢): ${gas.SafeGasPrice} Gwei`,
` Proposed (中): ${gas.ProposeGasPrice} Gwei`,
` Fast (快): ${gas.FastGasPrice} Gwei`,
` Base Fee: ${gas.suggestBaseFee} Gwei`,
``,
`费用估算 (ETH Transfer, 21000 gas):`,
` Safe: ~${(Number(gas.SafeGasPrice) * 21000 / 1e9).toFixed(6)} ETH`,
` Fast: ~${(Number(gas.FastGasPrice) * 21000 / 1e9).toFixed(6)} ETH`,
``,
`建议: ${Number(gas.ProposeGasPrice) < 20 ? "Gas 较低,适合交易" : "Gas 较高,非紧急交易可以等等"}`,
].join("\n");
return { content: [{ type: "text" as const, text: result }] };
}
}
return {
content: [{
type: "text" as const,
text: "无法获取 Gas 价格。请配置 ETHERSCAN_API_KEY。",
}],
};
}
case "get_protocol_tvl": {
const protocol = (args as { protocol: string }).protocol;
const data = await fetchJSON(
`https://api.llama.fi/protocol/${protocol}`
);
const currentTvl = data.currentChainTvls || {};
const totalTvl = Object.entries(currentTvl)
.filter(([k]) => !k.endsWith("-staking") && !k.endsWith("-borrowed"))
.reduce((sum, [, v]) => sum + (v as number), 0);
// Top 5 chains by TVL
const topChains = Object.entries(currentTvl)
.filter(([k]) => !k.endsWith("-staking") && !k.endsWith("-borrowed"))
.sort(([, a], [, b]) => (b as number) - (a as number))
.slice(0, 5);
const result = [
`协议: ${data.name || protocol}`,
`类别: ${data.category || "N/A"}`,
`总 TVL: $${(totalTvl / 1e9).toFixed(2)}B`,
``,
`链分布 (Top 5):`,
...topChains.map(([chain, tvl]) =>
` ${chain}: $${((tvl as number) / 1e6).toFixed(1)}M (${((tvl as number) / totalTvl * 100).toFixed(1)}%)`
),
``,
`简介: ${(data.description || "").slice(0, 200)}`,
].join("\n");
return { content: [{ type: "text" as const, text: result }] };
}
default:
return {
content: [{ type: "text" as const, text: `未知工具: ${name}` }],
isError: true,
};
}
} catch (error: any) {
return {
content: [{
type: "text" as const,
text: `工具执行错误: ${error.message}`,
}],
isError: true,
};
}
});
// ==========================================
// 启动
// ==========================================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Web3 MCP Server running");
}
main().catch(console.error);
Claude Code 配置双 Server / Configuring Both Servers
{
"mcpServers": {
"notes-server": {
"command": "node",
"args": ["E:/code/momofinance/momoweb3/mcp-notes-server/dist/index.js"],
"env": {
"NOTES_DIR": "E:/code/momofinance/momoweb3/docs/ai"
}
},
"web3-server": {
"command": "node",
"args": ["E:/code/momofinance/momoweb3/mcp-web3-server/dist/index.js"],
"env": {
"ETHERSCAN_API_KEY": "your-key-here",
"ALCHEMY_API_KEY": "your-key-here"
}
}
}
}
知识点6:发布与分享 / Publishing & Sharing
npm 发布流程 / npm Publishing
# === Step 1: 准备发布 ===
cd mcp-notes-server
# 确保构建成功
npm run build
# 添加 shebang 到入口文件
# 在 dist/index.js 开头添加: #!/usr/bin/env node
# === Step 2: 更新 package.json ===
# 确保以下字段正确:
# "name": "@your-scope/mcp-notes-server" (带 scope 避免命名冲突)
# "version": "1.0.0"
# "main": "dist/index.js"
# "bin": { "mcp-notes-server": "dist/index.js" }
# "files": ["dist/"] (只发布编译后的文件)
# === Step 3: 登录 npm ===
npm login
# === Step 4: 发布 ===
npm publish --access public
# === Step 5: 验证 ===
# 别人可以通过以下方式安装和使用:
npx @your-scope/mcp-notes-server
README 编写 / Writing README
# MCP Notes Server
A Model Context Protocol server that lets AI assistants search
and read your learning notes.
## Features
- **search_notes**: Full-text search across all notes
- **get_note_content**: Read complete note contents
- **notes://list**: Browse all available notes
## Quick Start
### Install
\`\`\`bash
npm install -g @your-scope/mcp-notes-server
\`\`\`
### Configure with Claude Code
Add to `.claude/settings.local.json`:
\`\`\`json
{
"mcpServers": {
"notes-server": {
"command": "npx",
"args": ["@your-scope/mcp-notes-server"],
"env": {
"NOTES_DIR": "/path/to/your/notes"
}
}
}
}
\`\`\`
### Usage
In Claude Code, you can now:
- "Search my notes for LoRA fine-tuning"
- "Read my Day 7 learning notes"
- "Summarize what I learned about RAG"
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| NOTES_DIR | Path to notes directory | ./docs/ai |
## License
MIT
MCP Server Registry / 注册到社区
MCP Server 可以注册到社区发现平台:
1. Anthropic MCP Servers 列表
https://github.com/modelcontextprotocol/servers
提交 PR 添加你的 Server
2. MCP Hub
社区维护的 MCP Server 目录
提交你的 Server 信息
3. npm 搜索
用 "mcp-server" 作为关键词
其他人搜索 MCP Server 时能找到
发布清单:
✅ 代码在 GitHub 公开
✅ npm 包已发布
✅ README 清晰(安装、配置、使用)
✅ 包含使用示例
✅ 指定 MIT 或 Apache 许可证
版本管理 / Version Management
语义版本控制 (SemVer):
1.0.0 → 1.0.1 (patch)
修复 bug,不影响现有功能
例: 修复文件编码问题
1.0.0 → 1.1.0 (minor)
添加新工具或功能,向后兼容
例: 新增 analyze_learning_progress 工具
1.0.0 → 2.0.0 (major)
破坏性变更,不向后兼容
例: 改变工具的输入参数格式
发布新版本:
# 1. 修改 package.json 版本号
npm version patch # 或 minor / major
# 2. 构建
npm run build
# 3. 发布
npm publish
# 4. 打 Git tag
git tag v1.0.1
git push --tags
最佳实践:
- 每次发布前在本地测试
- 写 CHANGELOG.md 记录变更
- 重要变更发布前先发 beta 版
npm publish --tag beta
今日思考 / Today's Reflections
思考1:标准化的力量 / The Power of Standardization
Day 55 写的 Agent 工具 vs Day 56 的 MCP Server
功能完全一样,但价值截然不同
Agent 工具:
价值 = 工具本身的功能
受众 = 只有你的 Agent
维护 = 框架升级可能要改
MCP Server:
价值 = 工具功能 × 能使用的客户端数量
受众 = Claude Code, Cursor, Windsurf, 任何 MCP 客户端
维护 = 协议稳定,客户端升级不影响
标准化协议的价值:
USB 的出现让所有设备能用同一个接口
HTTP 的出现让所有浏览器能访问任何网站
MCP 的出现让所有 AI 应用能使用任何工具
Day 13 学 MCP 时觉得"又一个协议"
今天实操后理解了:
这不是"又一个协议"
这是 AI 工具的"USB标准"
思考2:开发者体验决定生态 / DX Determines Ecosystem
MCP SDK 的设计让开发变得很简单:
1. 定义工具 schema → ListToolsRequestSchema
2. 实现工具逻辑 → CallToolRequestSchema
3. 启动 Server → StdioServerTransport
整个过程不到 200 行代码就能实现一个可用的 Server
对比其他方案:
- 写 REST API → 需要 Express/路由/中间件/部署
- 写 LangChain Tool → 只能在 LangChain 生态中用
- 写 ChatGPT Plugin → OpenAI 已经放弃了
MCP 胜出的原因不是技术最先进
而是开发者体验最好 + Anthropic 的推动
PM 视角的启示:
"好用"比"强大"更重要
降低开发者门槛 = 加速生态建设
一个人3小时能写出一个 MCP Server = 生态爆发的基础
思考3:MCP 是 AI PM 的新赛道 / MCP as a New Track for AI PMs
MCP 生态创造了新的产品机会:
1. MCP Server 即产品
不需要 UI,不需要前端
一个好用的 MCP Server = 一个有价值的产品
例: 今天写的 Web3 数据 Server
2. MCP Marketplace
类似 App Store 但针对 AI 工具
PM 可以策划"AI 工具商店"
3. 企业 MCP 平台
企业内部工具封装成 MCP Server
让 AI 助手能访问企业数据
PM 可以设计"企业 AI 工具平台"
4. MCP Server 编排
多个 Server 组合使用
PM 可以设计"工具编排平台"
对于 Web3 PM 来说:
链上数据 MCP Server → 让任何 AI 都能查询链上数据
DeFi 操作 MCP Server → 让 AI 能执行 DeFi 操作
治理 MCP Server → 让 AI 能参与 DAO 治理
这是 Day 81 学的 "MCP+Web3" 的实际落地!
学习资源 / Resources
MCP 官方
- MCP 规范: https://spec.modelcontextprotocol.io/
- TypeScript SDK: https://github.com/modelcontextprotocol/typescript-sdk
- Python SDK: https://github.com/modelcontextprotocol/python-sdk
- 官方 Server 列表: https://github.com/modelcontextprotocol/servers
教程
- MCP Getting Started: https://modelcontextprotocol.io/quickstart
- Building MCP Servers: https://modelcontextprotocol.io/tutorials/building-a-server
- Claude Code MCP 配置: https://docs.anthropic.com/en/docs/claude-code/mcp
API 参考
- CoinGecko: https://docs.coingecko.com/reference/introduction
- DeFiLlama: https://defillama.com/docs/api
- Etherscan: https://docs.etherscan.io/
- Alchemy: https://docs.alchemy.com/reference/api-overview
优秀 MCP Server 示例
- Filesystem Server: https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem
- GitHub Server: https://github.com/modelcontextprotocol/servers/tree/main/src/github
- Brave Search: https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search
明日预告 / Tomorrow's Preview
Day 57: 多模态应用 — 图文理解与文档分析
从文本到视觉:
Day 51-56: 处理的都是文本
Day 57: 开始处理图片和文档
Day 10 学的多模态理论 → Day 57 的实际应用
明天将实现:
1. 图片理解:用 LLaVA 模型分析截图
2. 文档分析:PDF/PPT → 结构化信息提取
3. OCR + LLM:图片中的文字 → 智能理解
4. 实际场景:分析架构图、理解白皮书
准备工作:
ollama pull llava:7b # 下载多模态模型
pip install pymupdf # PDF 处理
pip install Pillow # 图片处理
从"读文字"到"看图片",AI 能力再次升级!
Day 56 完成! 从零开发了两个 MCP Server:笔记搜索和 Web3 数据查询。 一次开发,Claude Code / Cursor / 任何 MCP 客户端都能使用。 明天进入多模态领域,让 AI 不仅能"读"还能"看"!