深入理解 Model Context Protocol (MCP):从概念到实践
目录
通过构建一个 SQLite MCP 服务器的完整案例,来学习 MCP 的核心概念和开发流程
引言#
在 AI 应用开发中,如何让大语言模型(LLM)安全、可靠地与外部数据和工具交互,是一个关键挑战。Model Context Protocol (MCP) 正是为解决这一问题而生的开放协议。
本文将通过一个实际项目——MCP SQLite Server,从零开始理解 MCP 的核心概念,并掌握完整的开发流程。
什么是 MCP?#
Model Context Protocol (MCP) 是一个开放协议,旨在标准化 AI 模型与外部数据源、工具之间的交互。到 2026 年,MCP 已成为连接 LLM 与本地/远程资源的行业标准,类似于 Web 时代的 HTTP 协议,它为 AI 提供了统一的 “外设接口”。
MCP 的核心架构#
┌─────────────────┐
│ AI Client │ ← 你的 IDE (Cursor/VSCode)、Claude Desktop 或自定义应用
│ (MCP Client) │
└────────┬────────┘
│ MCP Protocol (JSON-RPC)
│ (stdio / HTTP + SSE)
▼
┌─────────────────┐
│ MCP Server │ ← 你构建的服务,暴露工具、资源和提示词
│ - Tools │
│ - Resources │
│ - Prompts │
└─────────────────┘
三大核心概念#
1. Tools(工具)#
Tools 是 MCP 服务器暴露给 AI 的可执行函数。AI 可以根据用户需求主动调用这些工具。
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
// 注册工具:列出所有数据库表
server.tool(
'list_tables',
'List all tables in the database',
{ limit: z.number().optional().default(5) },
async ({ limit }) => {
const tables = await db.all('SELECT name FROM sqlite_master WHERE type = ? LIMIT ?', ['table', limit]);
return {
content: [{ type: 'text', text: JSON.stringify(tables) }]
};
}
);
AI 调用示例:
用户: "显示数据库中的所有表"
AI: [调用 list_tables 工具]
→ 返回:["users", "posts", "comments"]
AI: "数据库中有以下表:users、posts、comments"
2. Resources(资源)#
Resources 是 MCP 服务器提供的只读数据。AI 可以像读取文件或 URL 一样读取这些资源,将其作为上下文。
// 注册资源:特定表的 Schema
server.resource(
'table-schema',
'schema://table/{tableName}',
async (uri, { tableName }) => {
const schema = await db.get('SELECT sql FROM sqlite_master WHERE name = ?', [tableName]);
return {
contents: [{
uri: uri.href,
text: schema?.sql || 'Table not found',
mimeType: 'application/sql'
}]
};
}
);
资源 URI 示例:
schema://table/users- 用户表结构schema://table/posts- 文章表结构data://query/recent-posts- 最新文章数据
3. Prompts(提示词)#
Prompts 是预定义的交互模板,可以引导 AI 以特定方式思考或执行任务。
// 注册提示词:SQL 优化建议
server.prompt(
'optimize-query',
{ query: z.string() },
({ query }) => ({
messages: [{
role: 'user',
content: {
type: 'text',
text: `Please optimize this SQL query for SQLite: ${query}`
}
}]
})
);
实战:构建 MCP SQLite 服务器#
项目结构#
mcp-sqlite-server/
├── packages/
│ ├── server/ # MCP 服务器
│ │ ├── src/
│ │ │ ├── server.ts # MCP 服务器定义
│ │ │ ├── db.ts # 数据库连接
│ │ │ ├── stdio.ts # Stdio 传输入口
│ │ │ └── tools/ # 工具实现
│ │ └── package.json
│ │
│ └── client/ # MCP 客户端
│ ├── src/
│ │ ├── client.ts # MCP 客户端封装
│ │ ├── lmstudio.ts # LM Studio 集成
│ │ └── cli.ts # 交互式 CLI
│ └── package.json
│
├── shared/ # 共享类型
└── package.json # 工作区配置
步骤 1:初始化项目#
# 创建项目
mkdir mcp-sqlite-server
cd mcp-sqlite-server
npm init -y
# 配置工作区
# package.json
{
"workspaces": ["packages/*", "shared"]
}
# 安装核心依赖
npm install @modelcontextprotocol/sdk zod sqlite
步骤 2:创建 MCP 服务器#
// packages/server/src/server.ts
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { getDbConnection } from './db.js';
const server = new McpServer({
name: 'sqlite-server',
version: '1.0.0',
});
// 注册工具:执行查询
server.tool(
'query',
'Execute a read-only SQL query',
{ sql: z.string() },
async ({ sql }) => {
const db = await getDbConnection();
const result = await db.all(sql);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
);
// 注册资源:表结构
server.resource(
'table-schema',
new ResourceTemplate('schema://table/{tableName}', {
list: async () => {
const db = await getDbConnection();
const tables = await db.all('SELECT name FROM sqlite_master WHERE type = ?', ['table']);
return {
resources: tables.map(t => ({
uri: `schema://table/${t.name}`,
name: `${t.name} table schema`,
mimeType: 'application/sql'
}))
};
}
}),
async (uri, { tableName }) => {
const db = await getDbConnection();
const schema = await db.get(
'SELECT sql FROM sqlite_master WHERE name = ? AND type = ?',
[tableName, 'table']
);
return {
contents: [{
uri: uri.href,
text: schema?.sql || 'Table not found',
mimeType: 'application/sql'
}],
};
}
);
export { server };
步骤 3:实现传输层#
MCP 支持两种传输方式:Stdio 和 HTTP/SSE。
// packages/server/src/stdio.ts - Stdio 传输(用于本地 CLI)
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { server } from './server.js';
const transport = new StdioServerTransport();
await server.connect(transport);
// packages/server/src/http.ts - HTTP/SSE 传输(用于远程连接)
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
import { server } from './server.js';
const app = express();
let transport: SSEServerTransport | null = null;
app.get('/sse', async (req, res) => {
transport = new SSEServerTransport('/messages', res);
await server.connect(transport);
});
app.post('/messages', async (req, res) => {
if (transport) {
await transport.handlePostMessage(req, res);
} else {
res.status(400).send('No active SSE connection');
}
});
app.listen(3000);
步骤 4:创建 MCP 客户端#
// packages/client/src/client.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
export class MCPClient {
private client: Client;
private transport: StdioClientTransport | null = null;
async connect(command: string, args: string[]): Promise<void> {
this.transport = new StdioClientTransport({ command, args });
this.client = new Client({
name: 'sqlite-client',
version: '1.0.0',
});
await this.client.connect(this.transport);
}
async listTools() {
return await this.client.listTools();
}
async callTool(name: string, args: Record<string, unknown>) {
return await this.client.callTool({ name, arguments: args });
}
async disconnect() {
await this.client.close();
}
}
步骤 5:集成 AI(LM Studio)#
// packages/client/src/lmstudio.ts
export class LMStudioClient {
private baseUrl: string;
private model: string;
constructor(options: { baseUrl?: string; model?: string } = {}) {
this.baseUrl = options.baseUrl || 'http://localhost:1234/v1';
this.model = options.model || 'local-model';
}
async generateSQL(question: string, schemaContext: string): Promise<string> {
const response = await fetch(`${this.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: this.model,
messages: [
{
role: 'system',
content: `You are a SQL expert. Database schema:\n${schemaContext}`,
},
{ role: 'user', content: question },
],
}),
});
const data = await response.json();
return data.choices[0].message.content;
}
}
步骤 6:构建交互式 CLI#
// packages/client/src/cli.ts
import { MCPClient } from './client.js';
import { LMStudioClient } from './lmstudio.js';
import * as readline from 'node:readline';
class InteractiveCLI {
private mcpClient: MCPClient;
private lmClient: LMStudioClient | null = null;
private rl: readline.Interface;
constructor() {
this.mcpClient = new MCPClient();
this.lmClient = new LMStudioClient();
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
}
async run(): Promise<void> {
console.log('🚀 MCP SQLite Client\n');
// 连接 MCP 服务器
await this.mcpClient.connect('node', ['packages/server/dist/stdio.js']);
// 显示可用工具
const tools = await this.mcpClient.listTools();
console.log('Available tools:', tools.map(t => t.name).join(', '));
// 交互式循环
this.prompt();
}
private prompt(): void {
this.rl.question('> ', async (input) => {
await this.handleCommand(input);
this.prompt();
});
}
private async handleCommand(input: string): Promise<void> {
if (input.startsWith('/call ')) {
const [name, ...args] = input.slice(6).split(' ');
const result = await this.mcpClient.callTool(name, Object.fromEntries(args.map(a => a.split('='))));
console.log(JSON.stringify(result, null, 2));
} else if (input.startsWith('/ask ')) {
const sql = await this.lmClient!.generateSQL(input.slice(5), '');
console.log('Generated SQL:', sql);
// 自动执行生成的 SQL
const result = await this.mcpClient.callTool('query', { sql });
console.log('Execution Result:', JSON.stringify(result, null, 2));
}
}
}
new InteractiveCLI().run();
完整工作流示例#
场景:查询 Gmail 用户#
# 启动客户端
npm run start:client
# 用户输入
> /ask "显示所有 Gmail 邮箱的用户"
# 内部流程:
# 1. LM Studio 生成 SQL
# 2. 调用 MCP 工具执行查询
# 3. 返回结果
# 输出
SELECT * FROM users WHERE email LIKE '%@gmail.com';
执行结果:
[
{ "id": 1, "name": "Alice", "email": "alice@gmail.com" },
{ "id": 3, "name": "Charlie", "email": "charlie@gmail.com" }
]
MCP 开发最佳实践#
1. 工具设计原则#
- 单一职责:每个工具只做一件事
- 明确描述:详细描述工具的用途和参数
- 输入验证:使用 Zod 等库严格验证输入
- 错误处理:返回友好的错误信息
2. 资源设计原则#
- URI 规范:使用清晰的 URI 模式(如
schema://table/{name}) - 懒加载:只在需要时获取资源内容
- 缓存策略:对不变的资源实现缓存
3. 安全考虑#
- 最小权限:默认禁用危险操作
- 参数化查询:防止 SQL 注入
- 认证机制:敏感操作需要认证
// 示例:管理员认证
server.tool(
'execute_modification',
'Execute UPDATE operations (requires admin)',
{ sql: z.string(), adminToken: z.string() },
async ({ sql, adminToken }) => {
if (!verifyAdmin(adminToken)) {
return {
content: [{ type: 'text', text: 'Error: Admin authentication required' }],
isError: true
};
}
// 执行修改操作...
return { content: [{ type: 'text', text: 'Success' }] };
}
);
调试技巧#
1. 启用详细日志#
// 在服务器中添加日志
console.error('Tool called:', name, args);
console.error('Tool result:', result);
2. 使用 MCP Inspector#
# 安装 MCP Inspector
npx @modelcontextprotocol/inspector
# 连接到你的服务器
npx @modelcontextprotocol/inspector node packages/server/dist/stdio.js
3. 测试工具调用#
在集成到模型之前,可以编写简单的单元测试来验证工具逻辑:
// 示例:直接调用工具逻辑进行验证
const result = await server.callTool('query', { sql: 'SELECT 1' });
console.log(result);
总结#
通过本文的案例,我们学习了:
- MCP 的核心概念:了解 Tools、Resources、Prompts 的定义与区别
- 高层 SDK 开发流程:使用
McpServer快速构建标准化的 MCP 服务 - 多传输层支持:掌握 Stdio 和 HTTP/SSE 的实现与应用场景
- AI 客户端集成:结合 LM Studio 实现自然语言驱动的工具调用
- 最佳实践:从安全、性能到调试的全方位优化策略
MCP 为 AI 应用开发提供了一个标准化的接口,让 AI 能够安全、可靠地与外部世界交互。到 2026 年,掌握 MCP 开发已成为 AI 工程师的必备技能。希望本文能帮助你快速上手!
参考资源#
Read other posts