第20讲|MCP 协议:让 AI 工具直接操作你的数据库和 API

1 阅读1分钟

金句:MCP 让 AI 从"会说话的建议者"变成了"会动手的执行者"——它不再只告诉你怎么查数据库,而是直接帮你查。


一、MCP 是什么?为什么重要?

Model Context Protocol(MCP),由 Anthropic 于 2024 年底发布,是一个开放协议,让 AI 模型可以安全地连接外部工具、数据源和服务。

在 MCP 之前,AI 只能处理你粘贴给它的文本。
在 MCP 之后,AI 可以主动查询数据库、调用 API、读写文件系统、执行代码

这是 AI 编程工具能力的重大跃升。


二、MCP 架构原理

┌─────────────────────────────────────────────────────────────┐
│                      MCP 架构                                │
│                                                             │
│  ┌─────────────┐    MCP Protocol    ┌──────────────────┐   │
│  │  AI 客户端   │ ←───────────────→ │  MCP Server      │   │
│  │  (Cursor/   │                    │                  │   │
│  │   Claude)   │                    │  提供:          │   │
│  └─────────────┘                    │  • Tools(工具)  │   │
│                                     │  • Resources     │   │
│                                     │  • Prompts       │   │
│                                     └──────────────────┘   │
│                                              ↓              │
│                                     ┌──────────────────┐   │
│                                     │  外部系统          │   │
│                                     │  • 数据库         │   │
│                                     │  • REST API      │   │
│                                     │  • 文件系统       │   │
│                                     │  • 第三方服务     │   │
│                                     └──────────────────┘   │
└─────────────────────────────────────────────────────────────┘

核心概念

  • MCP Client:AI 工具(Cursor、Claude Desktop)
  • MCP Server:提供特定功能的服务器(可以是本地进程)
  • Tools:AI 可以调用的函数(如 query_database、call_api)
  • Resources:AI 可以读取的数据(如文件、数据库记录)

三、实战:构建数据库 MCP Server

场景:让 AI 直接查询你的 PostgreSQL 数据库

安装依赖

npm init -y
npm install @modelcontextprotocol/sdk pg
npm install -D typescript @types/pg

mcp-postgres-server.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 { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 5,
});

const server = new Server(
  {
    name: 'postgres-mcp-server',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// 注册可用工具
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'query_database',
        description: '执行 SQL 查询并返回结果。只允许 SELECT 语句。',
        inputSchema: {
          type: 'object',
          properties: {
            sql: {
              type: 'string',
              description: '要执行的 SQL SELECT 语句',
            },
            limit: {
              type: 'number',
              description: '最大返回行数(默认100)',
              default: 100,
            },
          },
          required: ['sql'],
        },
      },
      {
        name: 'list_tables',
        description: '列出数据库中的所有表及其结构',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'describe_table',
        description: '获取指定表的详细结构(列名、类型、约束)',
        inputSchema: {
          type: 'object',
          properties: {
            table_name: {
              type: 'string',
              description: '表名',
            },
          },
          required: ['table_name'],
        },
      },
    ],
  };
});

// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case 'query_database': {
      const sql = args?.sql as string;
      
      // 安全检查:只允许 SELECT 语句
      if (!sql.trim().toUpperCase().startsWith('SELECT')) {
        return {
          content: [{ type: 'text', text: '错误:只允许 SELECT 查询' }],
          isError: true,
        };
      }
      
      const limit = (args?.limit as number) || 100;
      const safeSql = `SELECT * FROM (${sql}) AS subquery LIMIT ${limit}`;
      
      try {
        const result = await pool.query(safeSql);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                rowCount: result.rowCount,
                columns: result.fields.map(f => f.name),
                rows: result.rows,
              }, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          content: [{ type: 'text', text: `查询失败:${error}` }],
          isError: true,
        };
      }
    }

    case 'list_tables': {
      const result = await pool.query(`
        SELECT 
          table_name,
          pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) as size,
          (SELECT count(*) FROM information_schema.columns 
           WHERE table_name = t.table_name) as column_count
        FROM information_schema.tables t
        WHERE table_schema = 'public'
        ORDER BY table_name
      `);
      
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result.rows, null, 2),
          },
        ],
      };
    }

    case 'describe_table': {
      const tableName = args?.table_name as string;
      const result = await pool.query(`
        SELECT 
          column_name,
          data_type,
          is_nullable,
          column_default,
          character_maximum_length
        FROM information_schema.columns
        WHERE table_name = $1 AND table_schema = 'public'
        ORDER BY ordinal_position
      `, [tableName]);
      
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result.rows, null, 2),
          },
        ],
      };
    }

    default:
      return {
        content: [{ type: 'text', text: `未知工具:${name}` }],
        isError: true,
      };
  }
});

// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);

在 Cursor 中配置

~/.cursor/mcp.json

{
  "mcpServers": {
    "postgres": {
      "command": "node",
      "args": ["/path/to/mcp-postgres-server.js"],
      "env": {
        "DATABASE_URL": "postgresql://user:password@localhost:5432/mydb"
      }
    }
  }
}

使用效果

配置完成后,在 Cursor 中你可以:

你:帮我分析一下过去30天的用户注册趋势,
    看看每周的新增用户数量变化

Cursor(调用 MCP):
→ list_tables() 
→ describe_table("users")
→ query_database("SELECT DATE_TRUNC('week', created_at) as week, 
                  COUNT(*) as new_users 
                  FROM users 
                  WHERE created_at > NOW() - INTERVAL '30 days'
                  GROUP BY week ORDER BY week")

Cursor:根据查询结果,过去30天用户注册趋势如下:
第1周:234人,第2周:312人...(增长35%)
主要增长出现在周三和周五...

四、其他实用 MCP Server

官方 MCP Servers 生态

# 文件系统操作
npx @modelcontextprotocol/server-filesystem /path/to/workspace

# GitHub 集成
npx @modelcontextprotocol/server-github

# Google Drive
npx @modelcontextprotocol/server-gdrive

# Slack
npx @modelcontextprotocol/server-slack

# Puppeteer(浏览器自动化)
npx @modelcontextprotocol/server-puppeteer

社区热门 MCP Servers

名称功能
mcp-server-sqliteSQLite 数据库操作
mcp-server-redisRedis 缓存读写
mcp-server-jiraJira 任务管理
mcp-server-notionNotion 文档操作
mcp-server-awsAWS 服务操作

五、MCP 安全最佳实践

MCP 给了 AI 强大的能力,也带来了安全风险:

✅ 安全配置原则

1. 最小权限原则
   - 数据库 MCP:只允许 SELECT,禁止 INSERT/UPDATE/DELETE
   - 文件系统 MCP:限制可访问的目录范围
   - API MCP:只开放必要的端点

2. 敏感数据保护
   - 数据库连接信息用环境变量,不硬编码
   - 个人信息字段在返回前脱敏

3. 审计日志
   - 记录所有 MCP 工具调用
   - 异常访问模式告警

4. 沙箱隔离
   - 生产环境不暴露 MCP 接口
   - 使用只读账号进行数据查询

章节小结:MCP 是 AI 编程工具的"手脚"——让 AI 从"说教者"变成"行动者"。通过构建数据库 MCP Server,你可以让 AI 直接分析你的真实数据,提供更精准的业务洞察和代码建议。这是 Vibe Coding 迈向企业级应用的关键一步。