v1.0.3
This commit is contained in:
parent
81d95862d4
commit
481eb2de17
117
CLAUDE.md
117
CLAUDE.md
|
|
@ -24,6 +24,7 @@ MCP Database Server is a WebSocket/SSE-based database tooling service that expos
|
|||
4. **Concurrency Model**: Per-client session isolation with independent connection pools
|
||||
5. **Code Separation**: Complete separation from original STDIO-based codebase; this is a standalone server implementation
|
||||
6. **Database Driver Abstraction** (v1.0.2): DatabaseDriver interface allows pluggable database backends (PostgreSQL, SQL Server)
|
||||
7. **Server Mode** (v1.0.3): Single server-level configuration with dynamic database discovery, eliminating per-database environment configuration
|
||||
|
||||
## Build and Development Commands
|
||||
|
||||
|
|
@ -217,6 +218,36 @@ Configuration uses `config/database.json` (see `config/database.example.json` fo
|
|||
}
|
||||
```
|
||||
|
||||
**Server Mode Example (v1.0.3+):**
|
||||
|
||||
Server mode allows configuring a single database server connection and dynamically accessing any database on that server, eliminating the need to configure each database as a separate environment.
|
||||
|
||||
```json
|
||||
{
|
||||
"environments": {
|
||||
"pg-server": {
|
||||
"type": "postgres",
|
||||
"serverMode": true,
|
||||
"connection": {
|
||||
"host": "47.99.124.43",
|
||||
"port": 5432,
|
||||
"user": "postgres",
|
||||
"password": "ENV:MCP_PG_PASSWORD",
|
||||
"ssl": { "require": true }
|
||||
},
|
||||
"defaultSchema": "dbo",
|
||||
"pool": { "max": 100 },
|
||||
"mode": "ddl"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Key differences in server mode:
|
||||
- `serverMode: true` - Enables server-level configuration
|
||||
- `database` field is **optional** - Not required in connection config
|
||||
- All tools accept optional `database` parameter to specify target database dynamically
|
||||
|
||||
### Configuration Fields
|
||||
|
||||
**server** - Global server settings:
|
||||
|
|
@ -230,6 +261,9 @@ Configuration uses `config/database.json` (see `config/database.example.json` fo
|
|||
**environments** - Database connection configurations:
|
||||
- Each environment is an isolated connection pool with unique name
|
||||
- `type`: Database type (`postgres` | `sqlserver`)
|
||||
- `serverMode`: Enable server-level connection (v1.0.3+, default: false)
|
||||
- When `true`, `database` field becomes optional
|
||||
- Connection pools are created dynamically per `(envName, database)` pair
|
||||
- For PostgreSQL:
|
||||
- `connection.ssl`: SSL configuration (`{ require: true }` or `false`)
|
||||
- `searchPath`: Array of schemas for PostgreSQL search_path
|
||||
|
|
@ -300,6 +334,70 @@ await client.callTool('pg_query', {
|
|||
});
|
||||
```
|
||||
|
||||
### Server Mode and Dynamic Database Discovery (v1.0.3+)
|
||||
|
||||
Server mode eliminates the need to configure each database as a separate environment. Instead, configure a single server-level connection and dynamically specify the database at query time.
|
||||
|
||||
**Scenario 1: Discover all databases on server**
|
||||
```typescript
|
||||
// List all databases on the server
|
||||
const databases = await client.callTool('pg_discover_databases', {
|
||||
environment: 'pg-server'
|
||||
});
|
||||
// Returns: ["shcis_drworks_cpoe_pg", "shcis_ipworkstation", "postgres", ...]
|
||||
```
|
||||
|
||||
**Scenario 2: Query specific database dynamically**
|
||||
```typescript
|
||||
// List tables in a specific database
|
||||
await client.callTool('pg_list_tables', {
|
||||
environment: 'pg-server',
|
||||
database: 'shcis_drworks_cpoe_pg',
|
||||
schema: 'dbo'
|
||||
});
|
||||
|
||||
// Execute query in a specific database
|
||||
await client.callTool('pg_query', {
|
||||
environment: 'pg-server',
|
||||
database: 'shcis_ipworkstation',
|
||||
sql: 'SELECT * FROM users LIMIT 10'
|
||||
});
|
||||
```
|
||||
|
||||
**Scenario 3: Mix traditional and server mode environments**
|
||||
```json
|
||||
{
|
||||
"environments": {
|
||||
"prod-drworks": {
|
||||
"type": "postgres",
|
||||
"connection": {
|
||||
"host": "prod-db.example.com",
|
||||
"database": "shcis_drworks_cpoe_pg",
|
||||
"user": "readonly_user",
|
||||
"password": "ENV:PROD_PASSWORD"
|
||||
},
|
||||
"mode": "readonly"
|
||||
},
|
||||
"dev-server": {
|
||||
"type": "postgres",
|
||||
"serverMode": true,
|
||||
"connection": {
|
||||
"host": "dev-db.example.com",
|
||||
"user": "admin",
|
||||
"password": "ENV:DEV_PASSWORD"
|
||||
},
|
||||
"mode": "ddl"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Connection Pool Behavior in Server Mode:**
|
||||
- Pools are cached by composite key: `envName:database`
|
||||
- First query to a database creates a new pool
|
||||
- Subsequent queries to the same database reuse the cached pool
|
||||
- When no database is specified, defaults to `postgres` (PostgreSQL) or `master` (SQL Server)
|
||||
|
||||
## Key Implementation Notes
|
||||
|
||||
### Security and Authentication
|
||||
|
|
@ -467,6 +565,7 @@ The server exposes 30+ PostgreSQL tools grouped by category:
|
|||
|
||||
**Metadata Tools**:
|
||||
- `pg_list_environments` - List configured environments
|
||||
- `pg_discover_databases` - List all databases on the server (v1.0.3+, for serverMode environments)
|
||||
- `pg_list_schemas` - List schemas in environment
|
||||
- `pg_list_tables` - List tables in schema
|
||||
- `pg_describe_table` - Get table structure (columns, types, constraints)
|
||||
|
|
@ -496,7 +595,7 @@ The server exposes 30+ PostgreSQL tools grouped by category:
|
|||
- `pg_analyze_query` - Analyze query performance
|
||||
- `pg_check_connection` - Verify database connectivity
|
||||
|
||||
All tools require `environment` parameter to specify which database to use.
|
||||
All tools require `environment` parameter to specify which database to use. In server mode (v1.0.3+), tools also accept an optional `database` parameter to dynamically target a specific database.
|
||||
|
||||
## Version History and Roadmap
|
||||
|
||||
|
|
@ -603,6 +702,18 @@ cat changelog.json
|
|||
- Convenience classes: `PostgresMcp`, `SqlServerMcp`
|
||||
- Unit tests for both drivers (99+ tests)
|
||||
|
||||
### v1.0.3 (2024-12-28)
|
||||
- **Server Mode Support**: Dynamic database discovery without per-database configuration
|
||||
- New `serverMode` configuration option for server-level connections
|
||||
- `database` field now optional in connection config when `serverMode: true`
|
||||
- All MCP tools accept optional `database` parameter for dynamic database switching
|
||||
- New `pg_discover_databases` tool to list all databases on a server
|
||||
- ConnectionManager supports dynamic connection pools keyed by `(envName, database)`
|
||||
- PostgreSQL driver: `buildListDatabasesQuery()` using `pg_database` catalog
|
||||
- SQL Server driver: `buildListDatabasesQuery()` using `sys.databases`
|
||||
- Configuration validation: `database` required unless `serverMode` is enabled
|
||||
- Backward compatible: existing configurations work without modification
|
||||
|
||||
### Future Roadmap
|
||||
- ~~Multi-database support (SQL Server, MySQL adapters)~~ ✅ Completed in v1.0.2
|
||||
- mTLS authentication implementation
|
||||
|
|
@ -636,7 +747,7 @@ npm run test:coverage # Run tests with coverage report
|
|||
|
||||
**Build Image**:
|
||||
```bash
|
||||
docker build -t mcp-database-server:1.0.1 .
|
||||
docker build -t mcp-database-server:1.0.3 .
|
||||
```
|
||||
|
||||
**Run Container**:
|
||||
|
|
@ -647,7 +758,7 @@ docker run -d \
|
|||
-v $(pwd)/config/database.json:/app/config/database.json:ro \
|
||||
-e MCP_AUTH_TOKEN=your-token \
|
||||
-e MCP_DRWORKS_PASSWORD=your-password \
|
||||
mcp-database-server:1.0.1
|
||||
mcp-database-server:1.0.3
|
||||
```
|
||||
|
||||
**Docker Compose**:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
{
|
||||
"currentVersion": "1.0.2",
|
||||
"currentVersion": "1.0.3",
|
||||
"changelog": [
|
||||
{
|
||||
"version": "1.0.3",
|
||||
"date": "2024-12-28",
|
||||
"description": "服务器模式支持 - 动态数据库发现",
|
||||
"changes": [
|
||||
"新增 serverMode 配置项,支持服务器级别连接",
|
||||
"database 字段在 serverMode 下变为可选",
|
||||
"所有 MCP 工具新增可选 database 参数",
|
||||
"ConnectionManager 支持按 (envName, database) 动态创建连接池",
|
||||
"新增 buildListDatabasesQuery 驱动接口方法",
|
||||
"PostgreSQL 和 SQL Server 驱动实现数据库列表查询",
|
||||
"配置验证支持 serverMode 和 database 联合检查",
|
||||
"向后兼容:现有配置无需修改即可继续使用"
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.0.2",
|
||||
"date": "2024-12-27",
|
||||
|
|
|
|||
24
docs/1.0.3版本需求/1.0.3版本需求.md
Normal file
24
docs/1.0.3版本需求/1.0.3版本需求.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
## mcp 服务器支持sql server数据库
|
||||
现在的配置文件格式为:
|
||||
{
|
||||
"type": "postgres",
|
||||
"connection": {
|
||||
"host": "47.99.124.43",
|
||||
"port": 5432,
|
||||
"database": "shcis_ipworkstation",
|
||||
"user": "postgres",
|
||||
"password": "passw0rd!",
|
||||
"ssl":false
|
||||
},
|
||||
"defaultSchema": "dbo",
|
||||
"searchPath": ["dbo"],
|
||||
"pool": {
|
||||
"max": 100,
|
||||
"idleTimeoutMs": 10000,
|
||||
"connectionTimeoutMs": 10000
|
||||
},
|
||||
"statementTimeoutMs": 60000,
|
||||
"mode": "ddl"
|
||||
}
|
||||
|
||||
由于项目的增多,导致服务器上的数据库会有十几个,如果每个库都需要配置,那么后期添加一个库就得修改一下配置。能不能我配置对应的服务器,直接读取所有的库呢?
|
||||
|
|
@ -42,7 +42,7 @@ const poolConfigSchema = z.object({
|
|||
const postgresConnectionSchema = z.object({
|
||||
host: z.string().min(1),
|
||||
port: z.number().min(1).max(65535).default(5432),
|
||||
database: z.string().min(1),
|
||||
database: z.string().min(1).optional(), // Optional when serverMode is enabled
|
||||
user: z.string().min(1),
|
||||
password: z.string(),
|
||||
ssl: sslConfigSchema,
|
||||
|
|
@ -52,7 +52,7 @@ const postgresConnectionSchema = z.object({
|
|||
const sqlServerConnectionSchema = z.object({
|
||||
host: z.string().min(1),
|
||||
port: z.number().min(1).max(65535).default(1433),
|
||||
database: z.string().min(1),
|
||||
database: z.string().min(1).optional(), // Optional when serverMode is enabled
|
||||
user: z.string().min(1),
|
||||
password: z.string(),
|
||||
encrypt: z.boolean().default(true),
|
||||
|
|
@ -61,9 +61,10 @@ const sqlServerConnectionSchema = z.object({
|
|||
requestTimeout: z.number().min(0).optional(),
|
||||
});
|
||||
|
||||
// PostgreSQL Environment
|
||||
const postgresEnvironmentSchema = z.object({
|
||||
// PostgreSQL Environment (base schema without refine for discriminatedUnion)
|
||||
const postgresEnvironmentBaseSchema = z.object({
|
||||
type: z.literal('postgres').optional(),
|
||||
serverMode: z.boolean().optional().default(false),
|
||||
connection: postgresConnectionSchema,
|
||||
defaultSchema: z.string().default(DEFAULT_POSTGRES_ENVIRONMENT.defaultSchema!),
|
||||
searchPath: z.array(z.string()).default(DEFAULT_POSTGRES_ENVIRONMENT.searchPath!),
|
||||
|
|
@ -73,9 +74,10 @@ const postgresEnvironmentSchema = z.object({
|
|||
mode: z.enum(['readonly', 'readwrite', 'ddl']).default(DEFAULT_POSTGRES_ENVIRONMENT.mode!),
|
||||
});
|
||||
|
||||
// SQL Server Environment
|
||||
const sqlServerEnvironmentSchema = z.object({
|
||||
// SQL Server Environment (base schema without refine for discriminatedUnion)
|
||||
const sqlServerEnvironmentBaseSchema = z.object({
|
||||
type: z.literal('sqlserver'),
|
||||
serverMode: z.boolean().optional().default(false),
|
||||
connection: sqlServerConnectionSchema,
|
||||
defaultSchema: z.string().default(DEFAULT_SQLSERVER_ENVIRONMENT.defaultSchema!),
|
||||
pool: poolConfigSchema,
|
||||
|
|
@ -84,13 +86,22 @@ const sqlServerEnvironmentSchema = z.object({
|
|||
mode: z.enum(['readonly', 'readwrite', 'ddl']).default(DEFAULT_SQLSERVER_ENVIRONMENT.mode!),
|
||||
});
|
||||
|
||||
// Combined Environment Schema (discriminated union)
|
||||
// Validation function for serverMode and database
|
||||
const validateServerModeAndDatabase = (config: { serverMode?: boolean; connection: { database?: string } }) => {
|
||||
// database is required unless serverMode is enabled
|
||||
return config.serverMode === true || !!config.connection.database;
|
||||
};
|
||||
|
||||
// Combined Environment Schema (discriminated union with validation)
|
||||
const environmentConfigSchema = z.discriminatedUnion('type', [
|
||||
postgresEnvironmentSchema.extend({ type: z.literal('postgres') }),
|
||||
sqlServerEnvironmentSchema,
|
||||
postgresEnvironmentBaseSchema.extend({ type: z.literal('postgres') }),
|
||||
sqlServerEnvironmentBaseSchema,
|
||||
]).or(
|
||||
// Allow environments without type (default to postgres for backward compatibility)
|
||||
postgresEnvironmentSchema
|
||||
postgresEnvironmentBaseSchema
|
||||
).refine(
|
||||
validateServerModeAndDatabase,
|
||||
{ message: 'connection.database is required unless serverMode is true' }
|
||||
);
|
||||
|
||||
// Auth Configuration
|
||||
|
|
|
|||
|
|
@ -54,7 +54,10 @@ export type DatabaseType = 'postgres' | 'sqlserver';
|
|||
export interface PostgresConnectionConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
/**
|
||||
* Database name. Required unless serverMode is enabled.
|
||||
*/
|
||||
database?: string;
|
||||
user: string;
|
||||
password: string;
|
||||
ssl?: false | SSLConfig;
|
||||
|
|
@ -62,6 +65,12 @@ export interface PostgresConnectionConfig {
|
|||
|
||||
export interface PostgresEnvironmentConfig {
|
||||
type?: 'postgres'; // Optional for backward compatibility
|
||||
/**
|
||||
* Server mode: when true, the environment connects to the database server
|
||||
* without specifying a particular database, allowing dynamic database switching.
|
||||
* @default false
|
||||
*/
|
||||
serverMode?: boolean;
|
||||
connection: PostgresConnectionConfig;
|
||||
defaultSchema?: string;
|
||||
searchPath?: string[];
|
||||
|
|
@ -76,7 +85,10 @@ export interface PostgresEnvironmentConfig {
|
|||
export interface SqlServerConnectionConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
/**
|
||||
* Database name. Required unless serverMode is enabled.
|
||||
*/
|
||||
database?: string;
|
||||
user: string;
|
||||
password: string;
|
||||
encrypt?: boolean;
|
||||
|
|
@ -87,6 +99,12 @@ export interface SqlServerConnectionConfig {
|
|||
|
||||
export interface SqlServerEnvironmentConfig {
|
||||
type: 'sqlserver';
|
||||
/**
|
||||
* Server mode: when true, the environment connects to the database server
|
||||
* without specifying a particular database, allowing dynamic database switching.
|
||||
* @default false
|
||||
*/
|
||||
serverMode?: boolean;
|
||||
connection: SqlServerConnectionConfig;
|
||||
defaultSchema?: string;
|
||||
pool?: Partial<PoolConfig>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { DatabaseDriver } from '../drivers/database-driver.js';
|
||||
import { buildSearchPath } from './utils.js';
|
||||
import { EnvironmentConfig, QueryOptions, SchemaInput } from './types.js';
|
||||
import { EnvironmentConfig, QueryOptions, SchemaInput, isPostgresConfig } from './types.js';
|
||||
|
||||
export type ClientCallback<T> = (
|
||||
client: any,
|
||||
|
|
@ -10,10 +10,11 @@ export type ClientCallback<T> = (
|
|||
/**
|
||||
* Connection Manager
|
||||
* Manages database connection pools using a database driver
|
||||
* Supports dynamic database switching for serverMode environments
|
||||
*/
|
||||
export class ConnectionManager {
|
||||
private readonly environments = new Map<string, EnvironmentConfig>();
|
||||
private readonly pools = new Map<string, any>();
|
||||
private readonly pools = new Map<string, any>(); // key: "env" or "env:database"
|
||||
|
||||
constructor(
|
||||
configs: EnvironmentConfig[],
|
||||
|
|
@ -22,6 +23,13 @@ export class ConnectionManager {
|
|||
configs.forEach((config) => this.environments.set(config.name, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build cache key for connection pool
|
||||
*/
|
||||
private buildPoolKey(envName: string, database?: string): string {
|
||||
return database ? `${envName}:${database}` : envName;
|
||||
}
|
||||
|
||||
getEnvironment(name: string): EnvironmentConfig {
|
||||
const env = this.environments.get(name);
|
||||
if (!env) {
|
||||
|
|
@ -30,14 +38,33 @@ export class ConnectionManager {
|
|||
return env;
|
||||
}
|
||||
|
||||
getPool(name: string): any {
|
||||
if (this.pools.has(name)) {
|
||||
return this.pools.get(name);
|
||||
/**
|
||||
* Get connection pool for environment, optionally for a specific database
|
||||
* @param name Environment name
|
||||
* @param database Optional database name (overrides environment default)
|
||||
*/
|
||||
getPool(name: string, database?: string): any {
|
||||
const key = this.buildPoolKey(name, database);
|
||||
|
||||
if (this.pools.has(key)) {
|
||||
return this.pools.get(key);
|
||||
}
|
||||
|
||||
const env = this.getEnvironment(name);
|
||||
const pool = this.driver.createConnectionPool(env.connection);
|
||||
this.pools.set(name, pool);
|
||||
|
||||
// Build connection config with optional database override
|
||||
let connectionConfig = env.connection;
|
||||
if (database) {
|
||||
// Use specified database
|
||||
connectionConfig = { ...env.connection, database };
|
||||
} else if (!env.connection.database) {
|
||||
// serverMode without specified database: use default system database
|
||||
const defaultDb = isPostgresConfig(env) ? 'postgres' : 'master';
|
||||
connectionConfig = { ...env.connection, database: defaultDb };
|
||||
}
|
||||
|
||||
const pool = this.driver.createConnectionPool(connectionConfig);
|
||||
this.pools.set(key, pool);
|
||||
return pool;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +75,7 @@ export class ConnectionManager {
|
|||
options?: QueryOptions,
|
||||
): Promise<T> {
|
||||
const env = this.getEnvironment(envName);
|
||||
const pool = this.getPool(envName);
|
||||
const pool = this.getPool(envName, options?.database); // Support dynamic database
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
|
|
@ -76,9 +103,9 @@ export class ConnectionManager {
|
|||
}
|
||||
}
|
||||
|
||||
async testConnection(envName: string): Promise<boolean> {
|
||||
async testConnection(envName: string, database?: string): Promise<boolean> {
|
||||
try {
|
||||
const pool = this.getPool(envName);
|
||||
const pool = this.getPool(envName, database);
|
||||
return await this.driver.testConnection(pool);
|
||||
} catch (error) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -27,28 +27,24 @@ export class MetadataBrowser {
|
|||
private readonly driver: DatabaseDriver
|
||||
) {}
|
||||
|
||||
async listDatabases(envName: string): Promise<string[]> {
|
||||
// Database listing is database-specific
|
||||
// For PostgreSQL, we query pg_database
|
||||
const query = `
|
||||
SELECT datname
|
||||
FROM pg_database
|
||||
WHERE datistemplate = false
|
||||
ORDER BY datname;
|
||||
`;
|
||||
async listDatabases(envName: string, options?: QueryOptions): Promise<string[]> {
|
||||
// Use driver-specific query for listing databases
|
||||
const query = this.driver.buildListDatabasesQuery();
|
||||
|
||||
const result = await this.connections.withClient(
|
||||
envName,
|
||||
undefined,
|
||||
async (client) => {
|
||||
return await this.driver.execute<{ datname: string }>(client, query);
|
||||
return await this.driver.execute<{ datname?: string; name?: string }>(client, query);
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return result.rows.map((row) => row.datname);
|
||||
// PostgreSQL uses 'datname', SQL Server uses 'name'
|
||||
return result.rows.map((row) => row.datname ?? row.name ?? '');
|
||||
}
|
||||
|
||||
async listSchemas(envName: string): Promise<string[]> {
|
||||
async listSchemas(envName: string, options?: QueryOptions): Promise<string[]> {
|
||||
const query = this.driver.buildListSchemasQuery();
|
||||
|
||||
const result = await this.connections.withClient(
|
||||
|
|
@ -57,6 +53,7 @@ export class MetadataBrowser {
|
|||
async (client) => {
|
||||
return await this.driver.execute<{ schema_name: string }>(client, query);
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return result.rows.map((row) => row.schema_name);
|
||||
|
|
@ -209,7 +206,7 @@ export class MetadataBrowser {
|
|||
return result.rows;
|
||||
}
|
||||
|
||||
async listExtensions(envName: string): Promise<string[]> {
|
||||
async listExtensions(envName: string, options?: QueryOptions): Promise<string[]> {
|
||||
// Extensions are PostgreSQL-specific
|
||||
const query = `
|
||||
SELECT extname
|
||||
|
|
@ -223,6 +220,7 @@ export class MetadataBrowser {
|
|||
async (client) => {
|
||||
return await this.driver.execute<{ extname: string }>(client, query);
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return result.rows.map((row) => row.extname);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@ export interface BaseConnectionOptions {
|
|||
* PostgreSQL-specific connection options
|
||||
*/
|
||||
export interface PostgresConnectionOptions extends BaseConnectionOptions {
|
||||
database: string;
|
||||
/**
|
||||
* Database name. Required unless serverMode is enabled.
|
||||
* When serverMode is true, this can be omitted and specified dynamically per-query.
|
||||
*/
|
||||
database?: string;
|
||||
port?: number; // Default: 5432
|
||||
ssl?: {
|
||||
require?: boolean;
|
||||
|
|
@ -43,7 +47,11 @@ export interface PostgresConnectionOptions extends BaseConnectionOptions {
|
|||
* SQL Server-specific connection options
|
||||
*/
|
||||
export interface SqlServerConnectionOptions extends BaseConnectionOptions {
|
||||
database: string;
|
||||
/**
|
||||
* Database name. Required unless serverMode is enabled.
|
||||
* When serverMode is true, this can be omitted and specified dynamically per-query.
|
||||
*/
|
||||
database?: string;
|
||||
port?: number; // Default: 1433
|
||||
encrypt?: boolean;
|
||||
trustServerCertificate?: boolean;
|
||||
|
|
@ -68,6 +76,12 @@ export interface PoolSettings {
|
|||
*/
|
||||
export interface BaseEnvironmentConfig {
|
||||
name: string;
|
||||
/**
|
||||
* Server mode: when true, the environment connects to the database server
|
||||
* without specifying a particular database, allowing dynamic database switching.
|
||||
* @default false
|
||||
*/
|
||||
serverMode?: boolean;
|
||||
defaultSchema?: string;
|
||||
searchPath?: string[];
|
||||
pool?: PoolSettings;
|
||||
|
|
@ -131,6 +145,11 @@ export function getDatabaseType(config: EnvironmentConfig): DatabaseType {
|
|||
export interface QueryOptions {
|
||||
schema?: SchemaInput;
|
||||
timeoutMs?: number;
|
||||
/**
|
||||
* Database name to use for this query.
|
||||
* Overrides the environment's default database (useful with serverMode).
|
||||
*/
|
||||
database?: string;
|
||||
}
|
||||
|
||||
export interface PaginationOptions {
|
||||
|
|
|
|||
|
|
@ -175,6 +175,12 @@ export interface DatabaseDriver {
|
|||
|
||||
// ========== Metadata Queries ==========
|
||||
|
||||
/**
|
||||
* Build query to list all databases on the server
|
||||
* @returns SQL query
|
||||
*/
|
||||
buildListDatabasesQuery(): string;
|
||||
|
||||
/**
|
||||
* Build query to list all schemas
|
||||
* @returns SQL query
|
||||
|
|
|
|||
|
|
@ -180,6 +180,15 @@ export class PostgresDriver implements DatabaseDriver {
|
|||
|
||||
// ========== Metadata Queries ==========
|
||||
|
||||
buildListDatabasesQuery(): string {
|
||||
return `
|
||||
SELECT datname
|
||||
FROM pg_database
|
||||
WHERE datistemplate = false
|
||||
ORDER BY datname
|
||||
`;
|
||||
}
|
||||
|
||||
buildListSchemasQuery(): string {
|
||||
return `
|
||||
SELECT schema_name
|
||||
|
|
|
|||
|
|
@ -216,6 +216,16 @@ export class SqlServerDriver implements DatabaseDriver {
|
|||
|
||||
// ========== Metadata Queries ==========
|
||||
|
||||
buildListDatabasesQuery(): string {
|
||||
return `
|
||||
SELECT name
|
||||
FROM sys.databases
|
||||
WHERE state = 0
|
||||
AND name NOT IN ('master', 'tempdb', 'model', 'msdb')
|
||||
ORDER BY name
|
||||
`;
|
||||
}
|
||||
|
||||
buildListSchemasQuery(): string {
|
||||
return `
|
||||
SELECT schema_name
|
||||
|
|
|
|||
159
src/tools/common-schemas.ts
Normal file
159
src/tools/common-schemas.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* Common Zod schemas for MCP tools
|
||||
* Provides reusable schema definitions with database parameter support
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ========== Base Schemas ==========
|
||||
|
||||
/**
|
||||
* Base environment schema with optional database parameter
|
||||
*/
|
||||
export const baseEnvSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default, useful with serverMode)'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema with schema filter (for listing objects in a specific schema)
|
||||
*/
|
||||
export const schemaFilterSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
schema: z.string().optional().describe('Schema name to filter'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for table operations
|
||||
*/
|
||||
export const tableSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
table: z.string().describe('Table name'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for index filtering
|
||||
*/
|
||||
export const indexFilterSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
table: z.string().optional().describe('Table name to filter indexes'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
});
|
||||
|
||||
// ========== Query Schemas ==========
|
||||
|
||||
/**
|
||||
* Basic query schema
|
||||
*/
|
||||
export const querySchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
sql: z.string().describe('SQL query to execute'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Parameterized query schema
|
||||
*/
|
||||
export const paramQuerySchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
sql: z.string().describe('SQL query with $1, $2, etc. placeholders'),
|
||||
params: z.array(z.any()).describe('Array of parameter values'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Paginated query schema
|
||||
*/
|
||||
export const paginatedQuerySchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
sql: z.string().describe('SQL query (without LIMIT/OFFSET)'),
|
||||
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
||||
limit: z.number().default(100).describe('Maximum number of rows to return'),
|
||||
offset: z.number().default(0).describe('Number of rows to skip'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
/**
|
||||
* EXPLAIN query schema
|
||||
*/
|
||||
export const explainSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
sql: z.string().describe('SQL query to explain'),
|
||||
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
||||
analyze: z.boolean().optional().default(false).describe('Run EXPLAIN ANALYZE'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
// ========== Data Operation Schemas ==========
|
||||
|
||||
/**
|
||||
* Execute statement schema
|
||||
*/
|
||||
export const executeSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
sql: z.string().describe('SQL statement to execute'),
|
||||
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Bulk insert schema
|
||||
*/
|
||||
export const bulkInsertSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
table: z.string().describe('Target table name'),
|
||||
rows: z.array(z.record(z.any())).describe('Array of row objects to insert'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
chunkSize: z.number().optional().default(500).describe('Number of rows per INSERT'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Bulk upsert schema
|
||||
*/
|
||||
export const bulkUpsertSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
table: z.string().describe('Target table name'),
|
||||
rows: z.array(z.record(z.any())).describe('Array of row objects to upsert'),
|
||||
conflictColumns: z.array(z.string()).describe('Columns to use for conflict detection'),
|
||||
updateColumns: z.array(z.string()).optional().describe('Columns to update on conflict'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
chunkSize: z.number().optional().default(200).describe('Number of rows per UPSERT'),
|
||||
});
|
||||
|
||||
// ========== Transaction Schemas ==========
|
||||
|
||||
/**
|
||||
* Transaction operation schema
|
||||
*/
|
||||
export const transactionSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
});
|
||||
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
/**
|
||||
* Build QueryOptions from tool parameters
|
||||
*/
|
||||
export function buildQueryOptions(params: {
|
||||
database?: string;
|
||||
schema?: string;
|
||||
timeoutMs?: number;
|
||||
}): { database?: string; schema?: string; timeoutMs?: number } {
|
||||
const options: { database?: string; schema?: string; timeoutMs?: number } = {};
|
||||
if (params.database) options.database = params.database;
|
||||
if (params.schema) options.schema = params.schema;
|
||||
if (params.timeoutMs) options.timeoutMs = params.timeoutMs;
|
||||
return options;
|
||||
}
|
||||
|
|
@ -1,34 +1,17 @@
|
|||
import { z } from 'zod';
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { PostgresMcp } from '../core/index.js';
|
||||
import {
|
||||
executeSchema,
|
||||
bulkInsertSchema,
|
||||
bulkUpsertSchema,
|
||||
buildQueryOptions,
|
||||
} from './common-schemas.js';
|
||||
|
||||
const executeSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
sql: z.string().describe('SQL statement to execute'),
|
||||
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
const bulkInsertSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
table: z.string().describe('Target table name'),
|
||||
rows: z.array(z.record(z.any())).describe('Array of row objects to insert'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
chunkSize: z.number().optional().default(500).describe('Number of rows per INSERT statement'),
|
||||
});
|
||||
|
||||
const bulkUpsertSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
table: z.string().describe('Target table name'),
|
||||
rows: z.array(z.record(z.any())).describe('Array of row objects to upsert'),
|
||||
conflictColumns: z.array(z.string()).describe('Columns to use for conflict detection'),
|
||||
updateColumns: z.array(z.string()).optional().describe('Columns to update on conflict (defaults to all non-conflict columns)'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
chunkSize: z.number().optional().default(200).describe('Number of rows per UPSERT statement'),
|
||||
});
|
||||
|
||||
// Export schema with database support
|
||||
const exportSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
sql: z.string().describe('SQL query to export'),
|
||||
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
|
|
@ -39,13 +22,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
|||
'pg_execute',
|
||||
'Execute any SQL statement (INSERT, UPDATE, DELETE, etc.)',
|
||||
executeSchema.shape,
|
||||
async ({ environment, sql, params, schema }) => {
|
||||
async ({ environment, database, sql, params, schema }) => {
|
||||
try {
|
||||
const result = await pgMcp.queries.execute(
|
||||
environment,
|
||||
sql,
|
||||
params || [],
|
||||
schema ? { schema } : undefined
|
||||
buildQueryOptions({ database, schema })
|
||||
);
|
||||
return {
|
||||
content: [{
|
||||
|
|
@ -70,13 +53,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
|||
'pg_bulk_insert',
|
||||
'Insert multiple rows into a table efficiently',
|
||||
bulkInsertSchema.shape,
|
||||
async ({ environment, table, rows, schema, chunkSize }) => {
|
||||
async ({ environment, database, table, rows, schema, chunkSize }) => {
|
||||
try {
|
||||
const results = await pgMcp.bulk.bulkInsert(
|
||||
environment,
|
||||
{ table, schema },
|
||||
rows,
|
||||
{ chunkSize, schema }
|
||||
{ chunkSize, ...buildQueryOptions({ database, schema }) }
|
||||
);
|
||||
const totalRowCount = results.reduce((sum, r) => sum + (r.rowCount ?? 0), 0);
|
||||
return {
|
||||
|
|
@ -102,13 +85,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
|||
'pg_bulk_upsert',
|
||||
'Upsert (INSERT ... ON CONFLICT UPDATE) multiple rows into a table',
|
||||
bulkUpsertSchema.shape,
|
||||
async ({ environment, table, rows, conflictColumns, updateColumns, schema, chunkSize }) => {
|
||||
async ({ environment, database, table, rows, conflictColumns, updateColumns, schema, chunkSize }) => {
|
||||
try {
|
||||
const results = await pgMcp.bulk.bulkUpsert(
|
||||
environment,
|
||||
{ table, schema },
|
||||
rows,
|
||||
{ conflictColumns, updateColumns, chunkSize, schema }
|
||||
{ conflictColumns, updateColumns, chunkSize, ...buildQueryOptions({ database, schema }) }
|
||||
);
|
||||
const totalRowCount = results.reduce((sum, r) => sum + (r.rowCount ?? 0), 0);
|
||||
return {
|
||||
|
|
@ -134,13 +117,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
|||
'pg_export_csv',
|
||||
'Export query results as CSV',
|
||||
exportSchema.shape,
|
||||
async ({ environment, sql, params, schema }) => {
|
||||
async ({ environment, database, sql, params, schema }) => {
|
||||
try {
|
||||
const csv = await pgMcp.bulk.exportToCsv(
|
||||
environment,
|
||||
sql,
|
||||
params || [],
|
||||
schema ? { schema } : undefined
|
||||
buildQueryOptions({ database, schema })
|
||||
);
|
||||
return {
|
||||
content: [{ type: 'text', text: csv }],
|
||||
|
|
@ -158,13 +141,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
|||
'pg_export_json',
|
||||
'Export query results as JSON',
|
||||
exportSchema.shape,
|
||||
async ({ environment, sql, params, schema }) => {
|
||||
async ({ environment, database, sql, params, schema }) => {
|
||||
try {
|
||||
const json = await pgMcp.bulk.exportToJson(
|
||||
environment,
|
||||
sql,
|
||||
params || [],
|
||||
schema ? { schema } : undefined
|
||||
buildQueryOptions({ database, schema })
|
||||
);
|
||||
return {
|
||||
content: [{ type: 'text', text: json }],
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import { z } from 'zod';
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { PostgresMcp } from '../core/index.js';
|
||||
|
||||
const envSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
});
|
||||
import { baseEnvSchema, buildQueryOptions } from './common-schemas.js';
|
||||
|
||||
const slowQueriesSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
limit: z.number().optional().default(20).describe('Maximum number of slow queries to return'),
|
||||
});
|
||||
|
||||
const vacuumSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
table: z.string().optional().describe('Table name (if not specified, vacuums entire database)'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
verbose: z.boolean().optional().default(false).describe('Show detailed progress'),
|
||||
|
|
@ -21,6 +20,7 @@ const vacuumSchema = z.object({
|
|||
|
||||
const analyzeSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
database: z.string().optional().describe('Database name (overrides environment default)'),
|
||||
table: z.string().describe('Table name to analyze'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
});
|
||||
|
|
@ -29,10 +29,10 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
|||
server.tool(
|
||||
'pg_active_connections',
|
||||
'List active database connections and their current state',
|
||||
envSchema.shape,
|
||||
async ({ environment }) => {
|
||||
baseEnvSchema.shape,
|
||||
async ({ environment, database }) => {
|
||||
try {
|
||||
const connections = await pgMcp.diagnostics.getActiveConnections(environment);
|
||||
const connections = await pgMcp.diagnostics.getActiveConnections(environment, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(connections, null, 2) }],
|
||||
};
|
||||
|
|
@ -48,10 +48,10 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
|||
server.tool(
|
||||
'pg_locks',
|
||||
'Show current database locks and blocking queries',
|
||||
envSchema.shape,
|
||||
async ({ environment }) => {
|
||||
baseEnvSchema.shape,
|
||||
async ({ environment, database }) => {
|
||||
try {
|
||||
const locks = await pgMcp.diagnostics.getLocks(environment);
|
||||
const locks = await pgMcp.diagnostics.getLocks(environment, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(locks, null, 2) }],
|
||||
};
|
||||
|
|
@ -67,10 +67,10 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
|||
server.tool(
|
||||
'pg_replication_status',
|
||||
'Show replication status and lag information',
|
||||
envSchema.shape,
|
||||
async ({ environment }) => {
|
||||
baseEnvSchema.shape,
|
||||
async ({ environment, database }) => {
|
||||
try {
|
||||
const status = await pgMcp.diagnostics.getReplicationStatus(environment);
|
||||
const status = await pgMcp.diagnostics.getReplicationStatus(environment, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(status, null, 2) }],
|
||||
};
|
||||
|
|
@ -87,9 +87,9 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
|||
'pg_slow_queries',
|
||||
'Get the slowest queries from pg_stat_statements',
|
||||
slowQueriesSchema.shape,
|
||||
async ({ environment, limit }) => {
|
||||
async ({ environment, database, limit }) => {
|
||||
try {
|
||||
const queries = await pgMcp.diagnostics.getSlowQueries(environment, limit);
|
||||
const queries = await pgMcp.diagnostics.getSlowQueries(environment, limit, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(queries, null, 2) }],
|
||||
};
|
||||
|
|
@ -106,13 +106,14 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
|||
'pg_vacuum',
|
||||
'Run VACUUM on a table or the entire database',
|
||||
vacuumSchema.shape,
|
||||
async ({ environment, table, schema, verbose, analyze }) => {
|
||||
async ({ environment, database, table, schema, verbose, analyze }) => {
|
||||
try {
|
||||
await pgMcp.diagnostics.triggerVacuum(environment, {
|
||||
table,
|
||||
schema,
|
||||
verbose,
|
||||
analyze,
|
||||
...buildQueryOptions({ database }),
|
||||
});
|
||||
return {
|
||||
content: [{
|
||||
|
|
@ -138,9 +139,9 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
|||
'pg_analyze',
|
||||
'Run ANALYZE on a table to update statistics',
|
||||
analyzeSchema.shape,
|
||||
async ({ environment, table, schema }) => {
|
||||
async ({ environment, database, table, schema }) => {
|
||||
try {
|
||||
await pgMcp.diagnostics.analyzeTable(environment, table, schema);
|
||||
await pgMcp.diagnostics.analyzeTable(environment, table, schema, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
|
|
|
|||
|
|
@ -1,27 +1,13 @@
|
|||
import { z } from 'zod';
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { PostgresMcp } from '../core/index.js';
|
||||
|
||||
const envSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
});
|
||||
|
||||
const schemaFilterSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
schema: z.string().optional().describe('Schema name to filter'),
|
||||
});
|
||||
|
||||
const tableSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
table: z.string().describe('Table name'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
});
|
||||
|
||||
const indexFilterSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
table: z.string().optional().describe('Table name to filter indexes'),
|
||||
schema: z.string().optional().describe('Schema name'),
|
||||
});
|
||||
import {
|
||||
baseEnvSchema,
|
||||
schemaFilterSchema,
|
||||
tableSchema,
|
||||
indexFilterSchema,
|
||||
buildQueryOptions,
|
||||
} from './common-schemas.js';
|
||||
|
||||
export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
||||
server.tool(
|
||||
|
|
@ -39,10 +25,10 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
server.tool(
|
||||
'pg_test_connection',
|
||||
'Test connection to a database environment',
|
||||
envSchema.shape,
|
||||
async ({ environment }) => {
|
||||
baseEnvSchema.shape,
|
||||
async ({ environment, database }) => {
|
||||
try {
|
||||
const pool = pgMcp.connections.getPool(environment);
|
||||
const pool = pgMcp.connections.getPool(environment, database);
|
||||
const client = await pool.connect();
|
||||
const result = await client.query('SELECT version()');
|
||||
client.release();
|
||||
|
|
@ -60,11 +46,11 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
|
||||
server.tool(
|
||||
'pg_list_databases',
|
||||
'List all databases in the PostgreSQL server',
|
||||
envSchema.shape,
|
||||
async ({ environment }) => {
|
||||
'List all databases in the database server (useful with serverMode)',
|
||||
baseEnvSchema.shape,
|
||||
async ({ environment, database }) => {
|
||||
try {
|
||||
const databases = await pgMcp.metadata.listDatabases(environment);
|
||||
const databases = await pgMcp.metadata.listDatabases(environment, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(databases, null, 2) }],
|
||||
};
|
||||
|
|
@ -80,10 +66,10 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
server.tool(
|
||||
'pg_list_schemas',
|
||||
'List all schemas in the database',
|
||||
envSchema.shape,
|
||||
async ({ environment }) => {
|
||||
baseEnvSchema.shape,
|
||||
async ({ environment, database }) => {
|
||||
try {
|
||||
const schemas = await pgMcp.metadata.listSchemas(environment);
|
||||
const schemas = await pgMcp.metadata.listSchemas(environment, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(schemas, null, 2) }],
|
||||
};
|
||||
|
|
@ -100,9 +86,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
'pg_list_tables',
|
||||
'List tables in the database, optionally filtered by schema',
|
||||
schemaFilterSchema.shape,
|
||||
async ({ environment, schema }) => {
|
||||
async ({ environment, database, schema }) => {
|
||||
try {
|
||||
const tables = await pgMcp.metadata.listTables(environment, schema);
|
||||
const tables = await pgMcp.metadata.listTables(environment, schema, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(tables, null, 2) }],
|
||||
};
|
||||
|
|
@ -119,9 +105,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
'pg_list_views',
|
||||
'List views in the database, optionally filtered by schema',
|
||||
schemaFilterSchema.shape,
|
||||
async ({ environment, schema }) => {
|
||||
async ({ environment, database, schema }) => {
|
||||
try {
|
||||
const views = await pgMcp.metadata.listViews(environment, schema);
|
||||
const views = await pgMcp.metadata.listViews(environment, schema, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(views, null, 2) }],
|
||||
};
|
||||
|
|
@ -138,9 +124,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
'pg_list_materialized_views',
|
||||
'List materialized views in the database, optionally filtered by schema',
|
||||
schemaFilterSchema.shape,
|
||||
async ({ environment, schema }) => {
|
||||
async ({ environment, database, schema }) => {
|
||||
try {
|
||||
const views = await pgMcp.metadata.listMaterializedViews(environment, schema);
|
||||
const views = await pgMcp.metadata.listMaterializedViews(environment, schema, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(views, null, 2) }],
|
||||
};
|
||||
|
|
@ -157,10 +143,10 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
'pg_list_indexes',
|
||||
'List indexes in the database, optionally filtered by table and schema',
|
||||
indexFilterSchema.shape,
|
||||
async ({ environment, table, schema }) => {
|
||||
async ({ environment, database, table, schema }) => {
|
||||
try {
|
||||
const tableId = table ? { schema: schema || 'public', name: table } : undefined;
|
||||
const indexes = await pgMcp.metadata.listIndexes(environment, tableId);
|
||||
const indexes = await pgMcp.metadata.listIndexes(environment, tableId, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(indexes, null, 2) }],
|
||||
};
|
||||
|
|
@ -177,9 +163,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
'pg_list_sequences',
|
||||
'List sequences in the database, optionally filtered by schema',
|
||||
schemaFilterSchema.shape,
|
||||
async ({ environment, schema }) => {
|
||||
async ({ environment, database, schema }) => {
|
||||
try {
|
||||
const sequences = await pgMcp.metadata.listSequences(environment, schema);
|
||||
const sequences = await pgMcp.metadata.listSequences(environment, schema, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(sequences, null, 2) }],
|
||||
};
|
||||
|
|
@ -195,10 +181,10 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
server.tool(
|
||||
'pg_list_extensions',
|
||||
'List installed PostgreSQL extensions',
|
||||
envSchema.shape,
|
||||
async ({ environment }) => {
|
||||
baseEnvSchema.shape,
|
||||
async ({ environment, database }) => {
|
||||
try {
|
||||
const extensions = await pgMcp.metadata.listExtensions(environment);
|
||||
const extensions = await pgMcp.metadata.listExtensions(environment, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(extensions, null, 2) }],
|
||||
};
|
||||
|
|
@ -215,9 +201,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
'pg_describe_table',
|
||||
'Get detailed table structure including columns, constraints, and indexes',
|
||||
tableSchema.shape,
|
||||
async ({ environment, table, schema }) => {
|
||||
async ({ environment, database, table, schema }) => {
|
||||
try {
|
||||
const definition = await pgMcp.metadata.getTableDefinition(environment, table, schema);
|
||||
const definition = await pgMcp.metadata.getTableDefinition(environment, table, schema, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(definition, null, 2) }],
|
||||
};
|
||||
|
|
@ -234,9 +220,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
|||
'pg_show_search_path',
|
||||
'Show the current search_path for the session',
|
||||
schemaFilterSchema.shape,
|
||||
async ({ environment, schema }) => {
|
||||
async ({ environment, database, schema }) => {
|
||||
try {
|
||||
const searchPath = await pgMcp.metadata.describeSearchPath(environment, schema);
|
||||
const searchPath = await pgMcp.metadata.describeSearchPath(environment, schema, buildQueryOptions({ database }));
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(searchPath, null, 2) }],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,49 +1,26 @@
|
|||
import { z } from 'zod';
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { PostgresMcp } from '../core/index.js';
|
||||
|
||||
const querySchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
sql: z.string().describe('SQL query to execute'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
const paramQuerySchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
sql: z.string().describe('SQL query with $1, $2, etc. placeholders'),
|
||||
params: z.array(z.any()).describe('Array of parameter values'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
const paginatedQuerySchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
sql: z.string().describe('SQL query (without LIMIT/OFFSET)'),
|
||||
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
||||
limit: z.number().default(100).describe('Maximum number of rows to return'),
|
||||
offset: z.number().default(0).describe('Number of rows to skip'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
|
||||
const explainSchema = z.object({
|
||||
environment: z.string().describe('Database environment name'),
|
||||
sql: z.string().describe('SQL query to explain'),
|
||||
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
||||
analyze: z.boolean().optional().default(false).describe('Run EXPLAIN ANALYZE (actually executes the query)'),
|
||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
||||
});
|
||||
import {
|
||||
querySchema,
|
||||
paramQuerySchema,
|
||||
paginatedQuerySchema,
|
||||
explainSchema,
|
||||
buildQueryOptions,
|
||||
} from './common-schemas.js';
|
||||
|
||||
export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void {
|
||||
server.tool(
|
||||
'pg_query',
|
||||
'Execute a SQL query and return results',
|
||||
querySchema.shape,
|
||||
async ({ environment, sql, schema }) => {
|
||||
async ({ environment, database, sql, schema }) => {
|
||||
try {
|
||||
const result = await pgMcp.queries.execute(
|
||||
environment,
|
||||
sql,
|
||||
[],
|
||||
schema ? { schema } : undefined
|
||||
buildQueryOptions({ database, schema })
|
||||
);
|
||||
return {
|
||||
content: [{
|
||||
|
|
@ -68,13 +45,13 @@ export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void
|
|||
'pg_query_with_params',
|
||||
'Execute a parameterized SQL query with $1, $2, etc. placeholders',
|
||||
paramQuerySchema.shape,
|
||||
async ({ environment, sql, params, schema }) => {
|
||||
async ({ environment, database, sql, params, schema }) => {
|
||||
try {
|
||||
const result = await pgMcp.queries.execute(
|
||||
environment,
|
||||
sql,
|
||||
params,
|
||||
schema ? { schema } : undefined
|
||||
buildQueryOptions({ database, schema })
|
||||
);
|
||||
return {
|
||||
content: [{
|
||||
|
|
@ -99,14 +76,14 @@ export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void
|
|||
'pg_query_paginated',
|
||||
'Execute a SQL query with pagination (LIMIT/OFFSET)',
|
||||
paginatedQuerySchema.shape,
|
||||
async ({ environment, sql, params, limit, offset, schema }) => {
|
||||
async ({ environment, database, sql, params, limit, offset, schema }) => {
|
||||
try {
|
||||
const result = await pgMcp.queries.executePaginated(
|
||||
environment,
|
||||
sql,
|
||||
params || [],
|
||||
{ limit, offset },
|
||||
schema ? { schema } : undefined
|
||||
buildQueryOptions({ database, schema })
|
||||
);
|
||||
return {
|
||||
content: [{
|
||||
|
|
@ -132,13 +109,13 @@ export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void
|
|||
'pg_explain',
|
||||
'Get the query execution plan (EXPLAIN)',
|
||||
explainSchema.shape,
|
||||
async ({ environment, sql, params, analyze, schema }) => {
|
||||
async ({ environment, database, sql, params, analyze, schema }) => {
|
||||
try {
|
||||
const plan = await pgMcp.queries.explain(
|
||||
environment,
|
||||
sql,
|
||||
params || [],
|
||||
{ analyze, schema }
|
||||
{ analyze, ...buildQueryOptions({ database, schema }) }
|
||||
);
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(plan, null, 2) }],
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user