通过构建一个 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 支持两种传输方式:StdioHTTP/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);

总结#

通过本文的案例,我们学习了:

  1. MCP 的核心概念:了解 Tools、Resources、Prompts 的定义与区别
  2. 高层 SDK 开发流程:使用 McpServer 快速构建标准化的 MCP 服务
  3. 多传输层支持:掌握 Stdio 和 HTTP/SSE 的实现与应用场景
  4. AI 客户端集成:结合 LM Studio 实现自然语言驱动的工具调用
  5. 最佳实践:从安全、性能到调试的全方位优化策略

MCP 为 AI 应用开发提供了一个标准化的接口,让 AI 能够安全、可靠地与外部世界交互。到 2026 年,掌握 MCP 开发已成为 AI 工程师的必备技能。希望本文能帮助你快速上手!

参考资源#