mirror of
https://github.com/executeautomation/mcp-database-server.git
synced 2025-12-09 21:12:57 +08:00
Add PostgreSQL support to MCP Database Server
Updated package.json and package-lock.json to include PostgreSQL dependencies. Enhanced README with PostgreSQL usage instructions and configuration details. Modified index.ts to handle PostgreSQL connection parameters and logging. Added PostgresqlAdapter for database interactions.
This commit is contained in:
185
src/db/postgresql-adapter.ts
Normal file
185
src/db/postgresql-adapter.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { DbAdapter } from "./adapter.js";
|
||||
import pg from 'pg';
|
||||
|
||||
/**
|
||||
* PostgreSQL database adapter implementation
|
||||
*/
|
||||
export class PostgresqlAdapter implements DbAdapter {
|
||||
private client: pg.Client | null = null;
|
||||
private config: pg.ClientConfig;
|
||||
private host: string;
|
||||
private database: string;
|
||||
|
||||
constructor(connectionInfo: {
|
||||
host: string;
|
||||
database: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
port?: number;
|
||||
ssl?: boolean | object;
|
||||
options?: any;
|
||||
}) {
|
||||
this.host = connectionInfo.host;
|
||||
this.database = connectionInfo.database;
|
||||
|
||||
// Create PostgreSQL connection config
|
||||
this.config = {
|
||||
host: connectionInfo.host,
|
||||
database: connectionInfo.database,
|
||||
port: connectionInfo.port || 5432,
|
||||
user: connectionInfo.user,
|
||||
password: connectionInfo.password,
|
||||
ssl: connectionInfo.ssl
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize PostgreSQL connection
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
try {
|
||||
console.error(`[INFO] Connecting to PostgreSQL: ${this.host}, Database: ${this.database}`);
|
||||
this.client = new pg.Client(this.config);
|
||||
await this.client.connect();
|
||||
console.error(`[INFO] PostgreSQL connection established successfully`);
|
||||
} catch (err) {
|
||||
console.error(`[ERROR] PostgreSQL connection error: ${(err as Error).message}`);
|
||||
throw new Error(`Failed to connect to PostgreSQL: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query and get all results
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
* @returns Promise with query results
|
||||
*/
|
||||
async all(query: string, params: any[] = []): Promise<any[]> {
|
||||
if (!this.client) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
try {
|
||||
// PostgreSQL uses $1, $2, etc. for parameterized queries
|
||||
const preparedQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
|
||||
|
||||
const result = await this.client.query(preparedQuery, params);
|
||||
return result.rows;
|
||||
} catch (err) {
|
||||
throw new Error(`PostgreSQL query error: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query that modifies data
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
* @returns Promise with result info
|
||||
*/
|
||||
async run(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
|
||||
if (!this.client) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
try {
|
||||
// Replace ? with numbered parameters
|
||||
const preparedQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
|
||||
|
||||
let lastID = 0;
|
||||
let changes = 0;
|
||||
|
||||
// For INSERT queries, try to get the inserted ID
|
||||
if (query.trim().toUpperCase().startsWith('INSERT')) {
|
||||
// Add RETURNING clause to get the inserted ID if it doesn't already have one
|
||||
const returningQuery = preparedQuery.includes('RETURNING')
|
||||
? preparedQuery
|
||||
: `${preparedQuery} RETURNING id`;
|
||||
|
||||
const result = await this.client.query(returningQuery, params);
|
||||
changes = result.rowCount || 0;
|
||||
lastID = result.rows[0]?.id || 0;
|
||||
} else {
|
||||
const result = await this.client.query(preparedQuery, params);
|
||||
changes = result.rowCount || 0;
|
||||
}
|
||||
|
||||
return { changes, lastID };
|
||||
} catch (err) {
|
||||
throw new Error(`PostgreSQL query error: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute multiple SQL statements
|
||||
* @param query SQL statements to execute
|
||||
* @returns Promise that resolves when execution completes
|
||||
*/
|
||||
async exec(query: string): Promise<void> {
|
||||
if (!this.client) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
try {
|
||||
await this.client.query(query);
|
||||
} catch (err) {
|
||||
throw new Error(`PostgreSQL batch error: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
if (this.client) {
|
||||
await this.client.end();
|
||||
this.client = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database metadata
|
||||
*/
|
||||
getMetadata(): { name: string, type: string, server: string, database: string } {
|
||||
return {
|
||||
name: "PostgreSQL",
|
||||
type: "postgresql",
|
||||
server: this.host,
|
||||
database: this.database
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-specific query for listing tables
|
||||
*/
|
||||
getListTablesQuery(): string {
|
||||
return "SELECT table_name as name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-specific query for describing a table
|
||||
* @param tableName Table name
|
||||
*/
|
||||
getDescribeTableQuery(tableName: string): string {
|
||||
return `
|
||||
SELECT
|
||||
c.column_name as name,
|
||||
c.data_type as type,
|
||||
CASE WHEN c.is_nullable = 'NO' THEN 1 ELSE 0 END as notnull,
|
||||
CASE WHEN pk.constraint_name IS NOT NULL THEN 1 ELSE 0 END as pk,
|
||||
c.column_default as dflt_value
|
||||
FROM
|
||||
information_schema.columns c
|
||||
LEFT JOIN
|
||||
information_schema.key_column_usage kcu
|
||||
ON c.table_name = kcu.table_name AND c.column_name = kcu.column_name
|
||||
LEFT JOIN
|
||||
information_schema.table_constraints pk
|
||||
ON kcu.constraint_name = pk.constraint_name AND pk.constraint_type = 'PRIMARY KEY'
|
||||
WHERE
|
||||
c.table_name = '${tableName}'
|
||||
AND c.table_schema = 'public'
|
||||
ORDER BY
|
||||
c.ordinal_position
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user