🗄️ 为 PostgreSQL 构建 MCP 服务器:使用 Claude 与任意 AI 客户端查询您的数据库 祝构建愉快

发布: (2025年12月3日 GMT+8 12:37)
6 min read
原文: Dev.to

Source: Dev.to

介绍

模型上下文协议(MCP)正在改变 AI 应用与数据源交互的方式。虽然天气服务器和基础示例是很好的起点,但大多数真实世界的应用都需要数据库访问。在本指南中,我们将构建一个面向生产的 PostgreSQL MCP 服务器,使 Claude、VS Code 以及任何 MCP 客户端能够安全、高效地查询你的数据库。

完成后,你将拥有:

  • 一个功能完整的 PostgreSQL MCP 服务器
  • SELECT 查询、聚合和数据检索的示例
  • 一个可以部署到生产环境的公共仓库
  • 与 Claude Desktop 和其他 MCP 客户端的集成

这解决了什么问题?

想象一下向 Claude 提问:

“本季度收入最高的前 10 位客户是谁?”

或者:

“显示过去 7 天内所有严重程度为 critical 的事件。”

有了 PostgreSQL MCP 服务器,Claude 可以:

  • 直接连接到你的数据库
  • 执行安全的预定义查询
  • 返回结构化数据
  • 实时帮助你分析并对数据采取行动

这非常适用于:

  • 由 AI 驱动的 BI 仪表板
  • 事件管理自动化
  • 客户数据分析
  • 实时报告和洞察

架构概览

我们的 MCP 服务器充当 MCP 客户端(Claude、VS Code)与 PostgreSQL 之间的桥梁:

MCP Client (Claude)
       |
    MCP Protocol
       |
    MCP Server (TypeScript)
       |
  pg (PostgreSQL Driver)
       |
  PostgreSQL Database

前置条件

  • Node.js 18+
  • TypeScript 基础
  • PostgreSQL 数据库
  • 基本的 MCP 概念

第一步:项目设置

创建项目:

mkdir mcp-postgres-server
cd mcp-postgres-server
npm init -y
npm install @modelcontextprotocol/sdk pg dotenv
npm install -D typescript @types/node @types/pg

创建 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "node",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  }
}

第二步:数据库服务

创建 src/database.ts

import { Pool } from 'pg';
import dotenv from 'dotenv';

dotenv.config();

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

export class DatabaseService {
  async queryUsers(): Promise {
    const result = await pool.query(
      'SELECT id, name, email FROM users LIMIT 10'
    );
    return result.rows;
  }

  async queryIncidents(status: string): Promise {
    const result = await pool.query(
      'SELECT id, title, status, severity FROM incidents WHERE status = $1 ORDER BY created_at DESC',
      [status]
    );
    return result.rows;
  }

  async getStats(): Promise {
    const result = await pool.query(`
      SELECT 
        COUNT(*) as total_incidents,
        SUM(CASE WHEN severity = 'critical' THEN 1 ELSE 0 END) as critical_count,
        SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open_count
      FROM incidents
    `);
    return result.rows[0];
  }
}

第三步:MCP 服务器实现

创建 src/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 { DatabaseService } from './database.js';

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

const db = new DatabaseService();

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'get_users',
      description: 'Retrieve recent users from the database',
      inputSchema: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
    {
      name: 'get_open_incidents',
      description: 'Get all open incidents sorted by date',
      inputSchema: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
    {
      name: 'get_database_stats',
      description: 'Get incident statistics (total, critical, open)',
      inputSchema: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name } = request.params;

  try {
    if (name === 'get_users') {
      const users = await db.queryUsers();
      return {
        content: [{ type: 'text', text: JSON.stringify(users, null, 2) }],
      };
    }

    if (name === 'get_open_incidents') {
      const incidents = await db.queryIncidents('open');
      return {
        content: [{ type: 'text', text: JSON.stringify(incidents, null, 2) }],
      };
    }

    if (name === 'get_database_stats') {
      const stats = await db.getStats();
      return {
        content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }],
      };
    }

    return {
      content: [{ type: 'text', text: `Unknown tool: ${name}` }],
      isError: true,
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error: ${error instanceof Error ? error.message : String(error)}`,
        },
      ],
      isError: true,
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('PostgreSQL MCP Server running on stdio');
}

main().catch(console.error);

第四步:配置

创建 .env

DATABASE_URL=postgresql://user:password@localhost:5432/mydb

更新 package.json 脚本:

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/server.js",
    "dev": "tsc && npm start"
  },
  "type": "module"
}

第五步:本地运行

npm run build
npm start

第六步:连接 Claude Desktop

编辑你的 Claude Desktop 配置文件:

  • macOS~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows%APPDATA%\\Claude\\claude_desktop_config.json
{
  "mcpServers": {
    "postgres": {
      "command": "node",
      "args": ["/absolute/path/to/dist/server.js"],
      "env": {
        "DATABASE_URL": "postgresql://user:password@localhost:5432/mydb"
      }
    }
  }
}

重启 Claude Desktop。现在你应该能看到数据库图标并访问这些工具!

生产环境最佳实践

  • 连接池:使用 pg Pool(代码中已实现)
  • 参数化查询:始终使用 $1$2 占位符
  • 错误处理:捕获并返回有意义的错误信息
  • 输入校验:在查询前验证工具输入
  • 监控:记录所有查询及响应时间
  • 限流:对高并发请求实现背压

公共仓库

完整源码可在以下地址获取:
GitHub Repository: mcp‑postgres‑server

仓库包含:

  • 完整的 TypeScript 实现
  • Docker 支持
  • 部署脚本
  • 示例 schema
  • 集成测试

部署选项

Google Cloud Run

gcloud run deploy mcp-postgres \
  --source . \
  --set-env-vars DATABASE_URL=your_connection_string

本地 Docker

docker build -t mcp-postgres .
docker run -e DATABA
Back to Blog

相关文章

阅读更多 »