预计时间
1 周
学习目标
- 理解 MCP 协议架构
- 能写一个 MCP Server
- 能接入 MCP Client
- 知道常用 MCP Server
一、MCP 是什么?
MCP = Model Context Protocol。解决的核心问题:LLM 和外部工具的标准化连接。
text
之前(没有 MCP):
每个 LLM 平台都有自己的 function calling 格式
每个工具都要为不同平台各写一套集成代码
ChatGPT 用 OpenAI tools 格式
Claude 用 Anthropic tool_use 格式
换平台 = 重写所有工具集成代码
之后(有 MCP):
MCP 是 LLM 和工具之间的"USB 协议"
MCP Server 一次编写 → 任何 MCP Client 都能用
MCP Client (Claude / Qoder / Cursor)
↕ 标准 JSON-RPC
MCP Server (GitHub / Jira / Database / ...)二、协议架构
text
┌────────────────────────────────┐
│ MCP Host │ ← Claude Desktop / Qoder / Cursor
│ ┌──────────────────────────┐ │
│ │ MCP Client │ │ ← 协议实现
│ │ - 列出可用工具 │ │
│ │ - 调用工具 │ │
│ │ - 管理连接 │ │
│ └──────────┬───────────────┘ │
└─────────────┼──────────────────┘
│ JSON-RPC over stdio / HTTP SSE
┌─────────────┼──────────────────┐
│ ┌──────────┴───────────────┐ │
│ │ MCP Server │ │ ← 你写的服务
│ │ - 注册工具 (Tools) │ │
│ │ - 处理请求 │ │
│ │ - 返回结果 │ │
│ └──────────────────────────┘ │
│ MCP Server │
└────────────────────────────────┘
通信方式:
stdio: 本地进程间通信(命令行执行 Server)
HTTP SSE: 远程通信(Server 可以部署在另一台机器)三、写一个 MCP Server
下面用 @modelcontextprotocol/sdk 写一个 GitHub MCP Server:
bash
mkdir github-mcp-server && cd github-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/nodetypescript
// src/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
// 创建 Server
const server = new Server(
{ name: 'github-mcp', version: '1.0.0' },
{ capabilities: { tools: {} } },
);
// ===== 注册工具列表 =====
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search_repos',
description: '在 GitHub 上搜索仓库',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索关键词' },
language: { type: 'string', description: '编程语言,如 typescript' },
limit: { type: 'number', description: '返回数量,默认 5' },
},
required: ['query'],
},
},
{
name: 'get_issue',
description: '获取指定仓库的 Issue',
inputSchema: {
type: 'object',
properties: {
owner: { type: 'string', description: '仓库所有者' },
repo: { type: 'string', description: '仓库名' },
issueNumber: { type: 'number', description: 'Issue 编号' },
},
required: ['owner', 'repo', 'issueNumber'],
},
},
{
name: 'create_issue',
description: '在指定仓库创建 Issue',
inputSchema: {
type: 'object',
properties: {
owner: { type: 'string' },
repo: { type: 'string' },
title: { type: 'string' },
body: { type: 'string' },
},
required: ['owner', 'repo', 'title'],
},
},
],
}));
// ===== 处理工具调用 =====
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'search_repos': {
const { query, language, limit = 5 } = args as any;
const langQuery = language ? `+language:${language}` : '';
const url = `https://api.github.com/search/repositories?q=${encodeURIComponent(query)}${langQuery}&per_page=${limit}`;
const response = await fetch(url, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
},
});
const data = await response.json();
return {
content: [{
type: 'text',
text: JSON.stringify(data.items?.map((r: any) => ({
name: r.full_name,
stars: r.stargazers_count,
description: r.description,
url: r.html_url,
})), null, 2),
}],
};
}
case 'get_issue': {
const { owner, repo, issueNumber } = args as any;
const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`;
const response = await fetch(url, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
},
});
const data = await response.json();
return {
content: [{
type: 'text',
text: JSON.stringify({ title: data.title, body: data.body, state: data.state, url: data.html_url }, null, 2),
}],
};
}
case 'create_issue': {
const { owner, repo, title, body } = args as any;
const url = `https://api.github.com/repos/${owner}/${repo}/issues`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, body }),
});
const data = await response.json();
return {
content: [{
type: 'text',
text: `Issue 创建成功:${data.html_url}`,
}],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// ===== 启动 Server =====
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('GitHub MCP Server 已启动');
}
main().catch(console.error);在 Qoder CLI 中接入
json
// ~/.qoder/settings.json 或通过 /mcp 命令添加
{
"mcpServers": {
"github": {
"command": "node",
"args": ["/path/to/github-mcp-server/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_xxxxxxxxxxxx"
}
}
}
}配置好后,LLM 就能直接搜索 GitHub 仓库、查看 Issue 了。
四、MCP 的三种角色
text
1. Tool(工具)
最常用。LLM 可以调用的外部功能。
如:搜索、查数据库、发邮件、操作文件
2. Resource(资源)
暴露数据给 LLM。
如:文件内容、数据库表结构、API 响应
3. Prompt(提示模板)
预定义的 Prompt 模板。
如:代码审查模板、翻译模板五、常用 MCP Server
| Server | 功能 | 配置难度 |
|---|---|---|
@anthropic/mcp-server-github | GitHub API | 简单 |
@anthropic/mcp-server-filesystem | 本地文件操作 | 简单 |
@anthropic/mcp-server-postgres | PostgreSQL 查询 | 中等 |
mcp-server-jira | Jira API | 中等 |
mcp-server-notion | Notion API | 中等 |
mcp-server-playwright | 浏览器自动化 | 中等 |
mcp-server-fetch | HTTP 请求 | 简单 |
实践
- 克隆一个现成的 MCP Server(如
mcp-server-filesystem),跑起来 - 写一个自己的 MCP Server ——哪怕只实现一个工具"获取当前时间"
- 接入 Qoder CLI,试试 Agent 调用你的工具