๐๏ธ PostgreSQL์ฉ MCP ์๋ฒ ๊ตฌ์ถ: Claude ๋ฐ ๋ชจ๋ AI ํด๋ผ์ด์ธํธ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌํ๊ธฐ ์ฆ๊ฒ๊ฒ ๋น๋ํ์ธ์
Source: Dev.to
์๊ฐ
Model Context Protocol (MCP)๋ AI ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ฐ์ดํฐ ์์ค์ ์ํธ ์์ฉํ๋ ๋ฐฉ์์ ํ์ ํ๊ณ ์์ต๋๋ค. ๋ ์จ ์๋ฒ์ ๊ธฐ๋ณธ ์์ ๋ ์ข์ ์์์ ์ด์ง๋ง, ๋๋ถ๋ถ์ ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ์ด ํ์ํฉ๋๋ค. ์ด ๊ฐ์ด๋์์๋ Claude, VSโฏCode ๋ฐ ๋ชจ๋ MCP ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์์ ํ๊ณ ํจ์จ์ ์ผ๋ก ์ฟผ๋ฆฌํ ์ ์๋ ํ๋ก๋์ ์์ค์ PostgreSQL MCP ์๋ฒ๋ฅผ ๊ตฌ์ถํฉ๋๋ค.
๋๊น์ง ์งํํ๋ฉด ๋ค์์ ๊ฐ๊ฒ ๋ฉ๋๋ค:
- ์์ ํ ๊ธฐ๋ฅ์ ๊ฐ์ถ PostgreSQL MCP ์๋ฒ
- SELECT ์ฟผ๋ฆฌ, ์ง๊ณ ๋ฐ ๋ฐ์ดํฐ ๊ฒ์ ์์
- ํ๋ก๋์ ์ ๋ฐฐํฌํ ์ ์๋ ๊ณต๊ฐ ์ ์ฅ์
- Claude Desktop ๋ฐ ๊ธฐํ MCP ํด๋ผ์ด์ธํธ์์ ํตํฉ
์ด๊ฒ์ด ํด๊ฒฐํ๋ ๋ฌธ์ ๋ ๋ฌด์์ธ๊ฐ์?
Claude์๊ฒ ๋ค์๊ณผ ๊ฐ์ด ๋ฌผ์ด๋ณธ๋ค๊ณ ์์ํด ๋ณด์ธ์:
โ์ด๋ฒ ๋ถ๊ธฐ ๋งค์ถ ๊ธฐ์ค ์์ 10๋ช ์ ๊ณ ๊ฐ์ ๋๊ตฌ์ธ๊ฐ์?โ
๋๋:
โ์ง๋ 7์ผ ๋์ ์ฌ๊ฐ๋โฏโcriticalโ์ธ ๋ชจ๋ ์ธ์๋ํธ๋ฅผ ๋ณด์ฌ ์ฃผ์ธ์.โ
PostgreSQL MCP ์๋ฒ๋ฅผ ์ฌ์ฉํ๋ฉด Claude๋:
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ฐ๊ฒฐ
- ์์ ํ๊ณ ์ฌ์ ์ ์๋ ์ฟผ๋ฆฌ ์คํ
- ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ ๋ฐํ
- ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํ๊ณ ์กฐ์น๋ฅผ ์ทจํ๋๋ก ๋์
์ด๋ ๋ค์์ ์ ํฉํฉ๋๋ค:
- AI ๊ธฐ๋ฐ BI ๋์๋ณด๋
- ์ธ์๋ํธ ๊ด๋ฆฌ ์๋ํ
- ๊ณ ๊ฐ ๋ฐ์ดํฐ ๋ถ์
- ์ค์๊ฐ ๋ณด๊ณ ๋ฐ ์ธ์ฌ์ดํธ
์ํคํ ์ฒ ๊ฐ์
Our MCP Server acts as a bridge between MCP clients (Claude, VSโฏCode) and PostgreSQL:
MCP Client (Claude)
|
MCP Protocol
|
MCP Server (TypeScript)
|
pg (PostgreSQL Driver)
|
PostgreSQL Database
์ฌ์ ์๊ตฌ ์ฌํญ
- Node.jsโฏ18 ์ด์
- TypeScript ์ง์
- PostgreSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค
- ๊ธฐ๋ณธ MCP ์ดํด
๋จ๊ณ 1: ํ๋ก์ ํธ ์ค์
Create the project:
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
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
}
}
๋จ๊ณ 2: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋น์ค
Create 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];
}
}
๋จ๊ณ 3: MCP ์๋ฒ ๊ตฌํ
Create 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);
๋จ๊ณ 4: ์ค์
Create .env:
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
Update package.json scripts:
{
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
"dev": "tsc && npm start"
},
"type": "module"
}
๋จ๊ณ 5: ๋ก์ปฌ์์ ์คํ
npm run build
npm start
๋จ๊ณ 6: Claude Desktop ์ฐ๊ฒฐ
Edit your Claude Desktop config file:
- 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์ ์ฌ์์ํ์ธ์. ์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ด์ฝ์ด ํ์๋๊ณ ์ด ๋๊ตฌ๋ค์ ์ ๊ทผํ ์ ์์ต๋๋ค!
ํ๋ก๋์ ์ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
- ์ฐ๊ฒฐ ํ๋ง:
pgPool ์ฌ์ฉ (์ด๋ฏธ ์ฝ๋์ ํฌํจ๋จ) - ํ๋ผ๋ฏธํฐํ๋ ์ฟผ๋ฆฌ: ํญ์
$1,$2์๋ฆฌํ์์ ์ฌ์ฉ - ์ค๋ฅ ์ฒ๋ฆฌ: ์๋ฏธ ์๋ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ๊ณ ๋ฐํ
- ๊ฒ์ฆ: ์ฟผ๋ฆฌ ์ ์ ๋๊ตฌ ์ ๋ ฅ ๊ฒ์ฆ
- ๋ชจ๋ํฐ๋ง: ๋ชจ๋ ์ฟผ๋ฆฌ์ ์๋ต ์๊ฐ์ ๊ธฐ๋ก
- ์๋ ์ ํ: ๋๋ ์์ฒญ์ ๋ํ ๋ฐฑํ๋ ์ ๊ตฌํ
๊ณต๊ฐ ์ ์ฅ์
์ ์ฒด ์์ค ์ฝ๋๋ ๋ค์์์ ํ์ธํ ์ ์์ต๋๋ค:
GitHub Repository: mcpโpostgresโserver
์ ์ฅ์์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค:
- ์ ์ฒด TypeScript ๊ตฌํ
- Docker ์ง์
- ๋ฐฐํฌ ์คํฌ๋ฆฝํธ
- ์์ ์คํค๋ง
- ํตํฉ ํ ์คํธ
๋ฐฐํฌ ์ต์
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