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
|
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
|
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)
|
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
|
## 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
|
### Configuration Fields
|
||||||
|
|
||||||
**server** - Global server settings:
|
**server** - Global server settings:
|
||||||
|
|
@ -230,6 +261,9 @@ Configuration uses `config/database.json` (see `config/database.example.json` fo
|
||||||
**environments** - Database connection configurations:
|
**environments** - Database connection configurations:
|
||||||
- Each environment is an isolated connection pool with unique name
|
- Each environment is an isolated connection pool with unique name
|
||||||
- `type`: Database type (`postgres` | `sqlserver`)
|
- `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:
|
- For PostgreSQL:
|
||||||
- `connection.ssl`: SSL configuration (`{ require: true }` or `false`)
|
- `connection.ssl`: SSL configuration (`{ require: true }` or `false`)
|
||||||
- `searchPath`: Array of schemas for PostgreSQL search_path
|
- `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
|
## Key Implementation Notes
|
||||||
|
|
||||||
### Security and Authentication
|
### Security and Authentication
|
||||||
|
|
@ -467,6 +565,7 @@ The server exposes 30+ PostgreSQL tools grouped by category:
|
||||||
|
|
||||||
**Metadata Tools**:
|
**Metadata Tools**:
|
||||||
- `pg_list_environments` - List configured environments
|
- `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_schemas` - List schemas in environment
|
||||||
- `pg_list_tables` - List tables in schema
|
- `pg_list_tables` - List tables in schema
|
||||||
- `pg_describe_table` - Get table structure (columns, types, constraints)
|
- `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_analyze_query` - Analyze query performance
|
||||||
- `pg_check_connection` - Verify database connectivity
|
- `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
|
## Version History and Roadmap
|
||||||
|
|
||||||
|
|
@ -603,6 +702,18 @@ cat changelog.json
|
||||||
- Convenience classes: `PostgresMcp`, `SqlServerMcp`
|
- Convenience classes: `PostgresMcp`, `SqlServerMcp`
|
||||||
- Unit tests for both drivers (99+ tests)
|
- 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
|
### Future Roadmap
|
||||||
- ~~Multi-database support (SQL Server, MySQL adapters)~~ ✅ Completed in v1.0.2
|
- ~~Multi-database support (SQL Server, MySQL adapters)~~ ✅ Completed in v1.0.2
|
||||||
- mTLS authentication implementation
|
- mTLS authentication implementation
|
||||||
|
|
@ -636,7 +747,7 @@ npm run test:coverage # Run tests with coverage report
|
||||||
|
|
||||||
**Build Image**:
|
**Build Image**:
|
||||||
```bash
|
```bash
|
||||||
docker build -t mcp-database-server:1.0.1 .
|
docker build -t mcp-database-server:1.0.3 .
|
||||||
```
|
```
|
||||||
|
|
||||||
**Run Container**:
|
**Run Container**:
|
||||||
|
|
@ -647,7 +758,7 @@ docker run -d \
|
||||||
-v $(pwd)/config/database.json:/app/config/database.json:ro \
|
-v $(pwd)/config/database.json:/app/config/database.json:ro \
|
||||||
-e MCP_AUTH_TOKEN=your-token \
|
-e MCP_AUTH_TOKEN=your-token \
|
||||||
-e MCP_DRWORKS_PASSWORD=your-password \
|
-e MCP_DRWORKS_PASSWORD=your-password \
|
||||||
mcp-database-server:1.0.1
|
mcp-database-server:1.0.3
|
||||||
```
|
```
|
||||||
|
|
||||||
**Docker Compose**:
|
**Docker Compose**:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,21 @@
|
||||||
{
|
{
|
||||||
"currentVersion": "1.0.2",
|
"currentVersion": "1.0.3",
|
||||||
"changelog": [
|
"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",
|
"version": "1.0.2",
|
||||||
"date": "2024-12-27",
|
"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({
|
const postgresConnectionSchema = z.object({
|
||||||
host: z.string().min(1),
|
host: z.string().min(1),
|
||||||
port: z.number().min(1).max(65535).default(5432),
|
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),
|
user: z.string().min(1),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
ssl: sslConfigSchema,
|
ssl: sslConfigSchema,
|
||||||
|
|
@ -52,7 +52,7 @@ const postgresConnectionSchema = z.object({
|
||||||
const sqlServerConnectionSchema = z.object({
|
const sqlServerConnectionSchema = z.object({
|
||||||
host: z.string().min(1),
|
host: z.string().min(1),
|
||||||
port: z.number().min(1).max(65535).default(1433),
|
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),
|
user: z.string().min(1),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
encrypt: z.boolean().default(true),
|
encrypt: z.boolean().default(true),
|
||||||
|
|
@ -61,9 +61,10 @@ const sqlServerConnectionSchema = z.object({
|
||||||
requestTimeout: z.number().min(0).optional(),
|
requestTimeout: z.number().min(0).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// PostgreSQL Environment
|
// PostgreSQL Environment (base schema without refine for discriminatedUnion)
|
||||||
const postgresEnvironmentSchema = z.object({
|
const postgresEnvironmentBaseSchema = z.object({
|
||||||
type: z.literal('postgres').optional(),
|
type: z.literal('postgres').optional(),
|
||||||
|
serverMode: z.boolean().optional().default(false),
|
||||||
connection: postgresConnectionSchema,
|
connection: postgresConnectionSchema,
|
||||||
defaultSchema: z.string().default(DEFAULT_POSTGRES_ENVIRONMENT.defaultSchema!),
|
defaultSchema: z.string().default(DEFAULT_POSTGRES_ENVIRONMENT.defaultSchema!),
|
||||||
searchPath: z.array(z.string()).default(DEFAULT_POSTGRES_ENVIRONMENT.searchPath!),
|
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!),
|
mode: z.enum(['readonly', 'readwrite', 'ddl']).default(DEFAULT_POSTGRES_ENVIRONMENT.mode!),
|
||||||
});
|
});
|
||||||
|
|
||||||
// SQL Server Environment
|
// SQL Server Environment (base schema without refine for discriminatedUnion)
|
||||||
const sqlServerEnvironmentSchema = z.object({
|
const sqlServerEnvironmentBaseSchema = z.object({
|
||||||
type: z.literal('sqlserver'),
|
type: z.literal('sqlserver'),
|
||||||
|
serverMode: z.boolean().optional().default(false),
|
||||||
connection: sqlServerConnectionSchema,
|
connection: sqlServerConnectionSchema,
|
||||||
defaultSchema: z.string().default(DEFAULT_SQLSERVER_ENVIRONMENT.defaultSchema!),
|
defaultSchema: z.string().default(DEFAULT_SQLSERVER_ENVIRONMENT.defaultSchema!),
|
||||||
pool: poolConfigSchema,
|
pool: poolConfigSchema,
|
||||||
|
|
@ -84,13 +86,22 @@ const sqlServerEnvironmentSchema = z.object({
|
||||||
mode: z.enum(['readonly', 'readwrite', 'ddl']).default(DEFAULT_SQLSERVER_ENVIRONMENT.mode!),
|
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', [
|
const environmentConfigSchema = z.discriminatedUnion('type', [
|
||||||
postgresEnvironmentSchema.extend({ type: z.literal('postgres') }),
|
postgresEnvironmentBaseSchema.extend({ type: z.literal('postgres') }),
|
||||||
sqlServerEnvironmentSchema,
|
sqlServerEnvironmentBaseSchema,
|
||||||
]).or(
|
]).or(
|
||||||
// Allow environments without type (default to postgres for backward compatibility)
|
// 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
|
// Auth Configuration
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,10 @@ export type DatabaseType = 'postgres' | 'sqlserver';
|
||||||
export interface PostgresConnectionConfig {
|
export interface PostgresConnectionConfig {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
database: string;
|
/**
|
||||||
|
* Database name. Required unless serverMode is enabled.
|
||||||
|
*/
|
||||||
|
database?: string;
|
||||||
user: string;
|
user: string;
|
||||||
password: string;
|
password: string;
|
||||||
ssl?: false | SSLConfig;
|
ssl?: false | SSLConfig;
|
||||||
|
|
@ -62,6 +65,12 @@ export interface PostgresConnectionConfig {
|
||||||
|
|
||||||
export interface PostgresEnvironmentConfig {
|
export interface PostgresEnvironmentConfig {
|
||||||
type?: 'postgres'; // Optional for backward compatibility
|
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;
|
connection: PostgresConnectionConfig;
|
||||||
defaultSchema?: string;
|
defaultSchema?: string;
|
||||||
searchPath?: string[];
|
searchPath?: string[];
|
||||||
|
|
@ -76,7 +85,10 @@ export interface PostgresEnvironmentConfig {
|
||||||
export interface SqlServerConnectionConfig {
|
export interface SqlServerConnectionConfig {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
database: string;
|
/**
|
||||||
|
* Database name. Required unless serverMode is enabled.
|
||||||
|
*/
|
||||||
|
database?: string;
|
||||||
user: string;
|
user: string;
|
||||||
password: string;
|
password: string;
|
||||||
encrypt?: boolean;
|
encrypt?: boolean;
|
||||||
|
|
@ -87,6 +99,12 @@ export interface SqlServerConnectionConfig {
|
||||||
|
|
||||||
export interface SqlServerEnvironmentConfig {
|
export interface SqlServerEnvironmentConfig {
|
||||||
type: 'sqlserver';
|
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;
|
connection: SqlServerConnectionConfig;
|
||||||
defaultSchema?: string;
|
defaultSchema?: string;
|
||||||
pool?: Partial<PoolConfig>;
|
pool?: Partial<PoolConfig>;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { DatabaseDriver } from '../drivers/database-driver.js';
|
import { DatabaseDriver } from '../drivers/database-driver.js';
|
||||||
import { buildSearchPath } from './utils.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> = (
|
export type ClientCallback<T> = (
|
||||||
client: any,
|
client: any,
|
||||||
|
|
@ -10,10 +10,11 @@ export type ClientCallback<T> = (
|
||||||
/**
|
/**
|
||||||
* Connection Manager
|
* Connection Manager
|
||||||
* Manages database connection pools using a database driver
|
* Manages database connection pools using a database driver
|
||||||
|
* Supports dynamic database switching for serverMode environments
|
||||||
*/
|
*/
|
||||||
export class ConnectionManager {
|
export class ConnectionManager {
|
||||||
private readonly environments = new Map<string, EnvironmentConfig>();
|
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(
|
constructor(
|
||||||
configs: EnvironmentConfig[],
|
configs: EnvironmentConfig[],
|
||||||
|
|
@ -22,6 +23,13 @@ export class ConnectionManager {
|
||||||
configs.forEach((config) => this.environments.set(config.name, config));
|
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 {
|
getEnvironment(name: string): EnvironmentConfig {
|
||||||
const env = this.environments.get(name);
|
const env = this.environments.get(name);
|
||||||
if (!env) {
|
if (!env) {
|
||||||
|
|
@ -30,14 +38,33 @@ export class ConnectionManager {
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPool(name: string): any {
|
/**
|
||||||
if (this.pools.has(name)) {
|
* Get connection pool for environment, optionally for a specific database
|
||||||
return this.pools.get(name);
|
* @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 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;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +75,7 @@ export class ConnectionManager {
|
||||||
options?: QueryOptions,
|
options?: QueryOptions,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const env = this.getEnvironment(envName);
|
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();
|
const client = await pool.connect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -76,9 +103,9 @@ export class ConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async testConnection(envName: string): Promise<boolean> {
|
async testConnection(envName: string, database?: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const pool = this.getPool(envName);
|
const pool = this.getPool(envName, database);
|
||||||
return await this.driver.testConnection(pool);
|
return await this.driver.testConnection(pool);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -27,28 +27,24 @@ export class MetadataBrowser {
|
||||||
private readonly driver: DatabaseDriver
|
private readonly driver: DatabaseDriver
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async listDatabases(envName: string): Promise<string[]> {
|
async listDatabases(envName: string, options?: QueryOptions): Promise<string[]> {
|
||||||
// Database listing is database-specific
|
// Use driver-specific query for listing databases
|
||||||
// For PostgreSQL, we query pg_database
|
const query = this.driver.buildListDatabasesQuery();
|
||||||
const query = `
|
|
||||||
SELECT datname
|
|
||||||
FROM pg_database
|
|
||||||
WHERE datistemplate = false
|
|
||||||
ORDER BY datname;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = await this.connections.withClient(
|
const result = await this.connections.withClient(
|
||||||
envName,
|
envName,
|
||||||
undefined,
|
undefined,
|
||||||
async (client) => {
|
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 query = this.driver.buildListSchemasQuery();
|
||||||
|
|
||||||
const result = await this.connections.withClient(
|
const result = await this.connections.withClient(
|
||||||
|
|
@ -57,6 +53,7 @@ export class MetadataBrowser {
|
||||||
async (client) => {
|
async (client) => {
|
||||||
return await this.driver.execute<{ schema_name: string }>(client, query);
|
return await this.driver.execute<{ schema_name: string }>(client, query);
|
||||||
},
|
},
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.rows.map((row) => row.schema_name);
|
return result.rows.map((row) => row.schema_name);
|
||||||
|
|
@ -209,7 +206,7 @@ export class MetadataBrowser {
|
||||||
return result.rows;
|
return result.rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
async listExtensions(envName: string): Promise<string[]> {
|
async listExtensions(envName: string, options?: QueryOptions): Promise<string[]> {
|
||||||
// Extensions are PostgreSQL-specific
|
// Extensions are PostgreSQL-specific
|
||||||
const query = `
|
const query = `
|
||||||
SELECT extname
|
SELECT extname
|
||||||
|
|
@ -223,6 +220,7 @@ export class MetadataBrowser {
|
||||||
async (client) => {
|
async (client) => {
|
||||||
return await this.driver.execute<{ extname: string }>(client, query);
|
return await this.driver.execute<{ extname: string }>(client, query);
|
||||||
},
|
},
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.rows.map((row) => row.extname);
|
return result.rows.map((row) => row.extname);
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,11 @@ export interface BaseConnectionOptions {
|
||||||
* PostgreSQL-specific connection options
|
* PostgreSQL-specific connection options
|
||||||
*/
|
*/
|
||||||
export interface PostgresConnectionOptions extends BaseConnectionOptions {
|
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
|
port?: number; // Default: 5432
|
||||||
ssl?: {
|
ssl?: {
|
||||||
require?: boolean;
|
require?: boolean;
|
||||||
|
|
@ -43,7 +47,11 @@ export interface PostgresConnectionOptions extends BaseConnectionOptions {
|
||||||
* SQL Server-specific connection options
|
* SQL Server-specific connection options
|
||||||
*/
|
*/
|
||||||
export interface SqlServerConnectionOptions extends BaseConnectionOptions {
|
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
|
port?: number; // Default: 1433
|
||||||
encrypt?: boolean;
|
encrypt?: boolean;
|
||||||
trustServerCertificate?: boolean;
|
trustServerCertificate?: boolean;
|
||||||
|
|
@ -68,6 +76,12 @@ export interface PoolSettings {
|
||||||
*/
|
*/
|
||||||
export interface BaseEnvironmentConfig {
|
export interface BaseEnvironmentConfig {
|
||||||
name: string;
|
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;
|
defaultSchema?: string;
|
||||||
searchPath?: string[];
|
searchPath?: string[];
|
||||||
pool?: PoolSettings;
|
pool?: PoolSettings;
|
||||||
|
|
@ -131,6 +145,11 @@ export function getDatabaseType(config: EnvironmentConfig): DatabaseType {
|
||||||
export interface QueryOptions {
|
export interface QueryOptions {
|
||||||
schema?: SchemaInput;
|
schema?: SchemaInput;
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
|
/**
|
||||||
|
* Database name to use for this query.
|
||||||
|
* Overrides the environment's default database (useful with serverMode).
|
||||||
|
*/
|
||||||
|
database?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginationOptions {
|
export interface PaginationOptions {
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,12 @@ export interface DatabaseDriver {
|
||||||
|
|
||||||
// ========== Metadata Queries ==========
|
// ========== Metadata Queries ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build query to list all databases on the server
|
||||||
|
* @returns SQL query
|
||||||
|
*/
|
||||||
|
buildListDatabasesQuery(): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build query to list all schemas
|
* Build query to list all schemas
|
||||||
* @returns SQL query
|
* @returns SQL query
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,15 @@ export class PostgresDriver implements DatabaseDriver {
|
||||||
|
|
||||||
// ========== Metadata Queries ==========
|
// ========== Metadata Queries ==========
|
||||||
|
|
||||||
|
buildListDatabasesQuery(): string {
|
||||||
|
return `
|
||||||
|
SELECT datname
|
||||||
|
FROM pg_database
|
||||||
|
WHERE datistemplate = false
|
||||||
|
ORDER BY datname
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
buildListSchemasQuery(): string {
|
buildListSchemasQuery(): string {
|
||||||
return `
|
return `
|
||||||
SELECT schema_name
|
SELECT schema_name
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,16 @@ export class SqlServerDriver implements DatabaseDriver {
|
||||||
|
|
||||||
// ========== Metadata Queries ==========
|
// ========== 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 {
|
buildListSchemasQuery(): string {
|
||||||
return `
|
return `
|
||||||
SELECT schema_name
|
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 { z } from 'zod';
|
||||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||||
import { PostgresMcp } from '../core/index.js';
|
import { PostgresMcp } from '../core/index.js';
|
||||||
|
import {
|
||||||
|
executeSchema,
|
||||||
|
bulkInsertSchema,
|
||||||
|
bulkUpsertSchema,
|
||||||
|
buildQueryOptions,
|
||||||
|
} from './common-schemas.js';
|
||||||
|
|
||||||
const executeSchema = z.object({
|
// Export schema with database support
|
||||||
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'),
|
|
||||||
});
|
|
||||||
|
|
||||||
const exportSchema = z.object({
|
const exportSchema = z.object({
|
||||||
environment: z.string().describe('Database environment name'),
|
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'),
|
sql: z.string().describe('SQL query to export'),
|
||||||
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
params: z.array(z.any()).optional().default([]).describe('Array of parameter values'),
|
||||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
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',
|
'pg_execute',
|
||||||
'Execute any SQL statement (INSERT, UPDATE, DELETE, etc.)',
|
'Execute any SQL statement (INSERT, UPDATE, DELETE, etc.)',
|
||||||
executeSchema.shape,
|
executeSchema.shape,
|
||||||
async ({ environment, sql, params, schema }) => {
|
async ({ environment, database, sql, params, schema }) => {
|
||||||
try {
|
try {
|
||||||
const result = await pgMcp.queries.execute(
|
const result = await pgMcp.queries.execute(
|
||||||
environment,
|
environment,
|
||||||
sql,
|
sql,
|
||||||
params || [],
|
params || [],
|
||||||
schema ? { schema } : undefined
|
buildQueryOptions({ database, schema })
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
|
|
@ -70,13 +53,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
||||||
'pg_bulk_insert',
|
'pg_bulk_insert',
|
||||||
'Insert multiple rows into a table efficiently',
|
'Insert multiple rows into a table efficiently',
|
||||||
bulkInsertSchema.shape,
|
bulkInsertSchema.shape,
|
||||||
async ({ environment, table, rows, schema, chunkSize }) => {
|
async ({ environment, database, table, rows, schema, chunkSize }) => {
|
||||||
try {
|
try {
|
||||||
const results = await pgMcp.bulk.bulkInsert(
|
const results = await pgMcp.bulk.bulkInsert(
|
||||||
environment,
|
environment,
|
||||||
{ table, schema },
|
{ table, schema },
|
||||||
rows,
|
rows,
|
||||||
{ chunkSize, schema }
|
{ chunkSize, ...buildQueryOptions({ database, schema }) }
|
||||||
);
|
);
|
||||||
const totalRowCount = results.reduce((sum, r) => sum + (r.rowCount ?? 0), 0);
|
const totalRowCount = results.reduce((sum, r) => sum + (r.rowCount ?? 0), 0);
|
||||||
return {
|
return {
|
||||||
|
|
@ -102,13 +85,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
||||||
'pg_bulk_upsert',
|
'pg_bulk_upsert',
|
||||||
'Upsert (INSERT ... ON CONFLICT UPDATE) multiple rows into a table',
|
'Upsert (INSERT ... ON CONFLICT UPDATE) multiple rows into a table',
|
||||||
bulkUpsertSchema.shape,
|
bulkUpsertSchema.shape,
|
||||||
async ({ environment, table, rows, conflictColumns, updateColumns, schema, chunkSize }) => {
|
async ({ environment, database, table, rows, conflictColumns, updateColumns, schema, chunkSize }) => {
|
||||||
try {
|
try {
|
||||||
const results = await pgMcp.bulk.bulkUpsert(
|
const results = await pgMcp.bulk.bulkUpsert(
|
||||||
environment,
|
environment,
|
||||||
{ table, schema },
|
{ table, schema },
|
||||||
rows,
|
rows,
|
||||||
{ conflictColumns, updateColumns, chunkSize, schema }
|
{ conflictColumns, updateColumns, chunkSize, ...buildQueryOptions({ database, schema }) }
|
||||||
);
|
);
|
||||||
const totalRowCount = results.reduce((sum, r) => sum + (r.rowCount ?? 0), 0);
|
const totalRowCount = results.reduce((sum, r) => sum + (r.rowCount ?? 0), 0);
|
||||||
return {
|
return {
|
||||||
|
|
@ -134,13 +117,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
||||||
'pg_export_csv',
|
'pg_export_csv',
|
||||||
'Export query results as CSV',
|
'Export query results as CSV',
|
||||||
exportSchema.shape,
|
exportSchema.shape,
|
||||||
async ({ environment, sql, params, schema }) => {
|
async ({ environment, database, sql, params, schema }) => {
|
||||||
try {
|
try {
|
||||||
const csv = await pgMcp.bulk.exportToCsv(
|
const csv = await pgMcp.bulk.exportToCsv(
|
||||||
environment,
|
environment,
|
||||||
sql,
|
sql,
|
||||||
params || [],
|
params || [],
|
||||||
schema ? { schema } : undefined
|
buildQueryOptions({ database, schema })
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: csv }],
|
content: [{ type: 'text', text: csv }],
|
||||||
|
|
@ -158,13 +141,13 @@ export function registerDataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
||||||
'pg_export_json',
|
'pg_export_json',
|
||||||
'Export query results as JSON',
|
'Export query results as JSON',
|
||||||
exportSchema.shape,
|
exportSchema.shape,
|
||||||
async ({ environment, sql, params, schema }) => {
|
async ({ environment, database, sql, params, schema }) => {
|
||||||
try {
|
try {
|
||||||
const json = await pgMcp.bulk.exportToJson(
|
const json = await pgMcp.bulk.exportToJson(
|
||||||
environment,
|
environment,
|
||||||
sql,
|
sql,
|
||||||
params || [],
|
params || [],
|
||||||
schema ? { schema } : undefined
|
buildQueryOptions({ database, schema })
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: json }],
|
content: [{ type: 'text', text: json }],
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||||
import { PostgresMcp } from '../core/index.js';
|
import { PostgresMcp } from '../core/index.js';
|
||||||
|
import { baseEnvSchema, buildQueryOptions } from './common-schemas.js';
|
||||||
const envSchema = z.object({
|
|
||||||
environment: z.string().describe('Database environment name'),
|
|
||||||
});
|
|
||||||
|
|
||||||
const slowQueriesSchema = z.object({
|
const slowQueriesSchema = z.object({
|
||||||
environment: z.string().describe('Database environment name'),
|
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'),
|
limit: z.number().optional().default(20).describe('Maximum number of slow queries to return'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const vacuumSchema = z.object({
|
const vacuumSchema = z.object({
|
||||||
environment: z.string().describe('Database environment name'),
|
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)'),
|
table: z.string().optional().describe('Table name (if not specified, vacuums entire database)'),
|
||||||
schema: z.string().optional().describe('Schema name'),
|
schema: z.string().optional().describe('Schema name'),
|
||||||
verbose: z.boolean().optional().default(false).describe('Show detailed progress'),
|
verbose: z.boolean().optional().default(false).describe('Show detailed progress'),
|
||||||
|
|
@ -21,6 +20,7 @@ const vacuumSchema = z.object({
|
||||||
|
|
||||||
const analyzeSchema = z.object({
|
const analyzeSchema = z.object({
|
||||||
environment: z.string().describe('Database environment name'),
|
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'),
|
table: z.string().describe('Table name to analyze'),
|
||||||
schema: z.string().optional().describe('Schema name'),
|
schema: z.string().optional().describe('Schema name'),
|
||||||
});
|
});
|
||||||
|
|
@ -29,10 +29,10 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
||||||
server.tool(
|
server.tool(
|
||||||
'pg_active_connections',
|
'pg_active_connections',
|
||||||
'List active database connections and their current state',
|
'List active database connections and their current state',
|
||||||
envSchema.shape,
|
baseEnvSchema.shape,
|
||||||
async ({ environment }) => {
|
async ({ environment, database }) => {
|
||||||
try {
|
try {
|
||||||
const connections = await pgMcp.diagnostics.getActiveConnections(environment);
|
const connections = await pgMcp.diagnostics.getActiveConnections(environment, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(connections, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(connections, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -48,10 +48,10 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
||||||
server.tool(
|
server.tool(
|
||||||
'pg_locks',
|
'pg_locks',
|
||||||
'Show current database locks and blocking queries',
|
'Show current database locks and blocking queries',
|
||||||
envSchema.shape,
|
baseEnvSchema.shape,
|
||||||
async ({ environment }) => {
|
async ({ environment, database }) => {
|
||||||
try {
|
try {
|
||||||
const locks = await pgMcp.diagnostics.getLocks(environment);
|
const locks = await pgMcp.diagnostics.getLocks(environment, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(locks, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(locks, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -67,10 +67,10 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
||||||
server.tool(
|
server.tool(
|
||||||
'pg_replication_status',
|
'pg_replication_status',
|
||||||
'Show replication status and lag information',
|
'Show replication status and lag information',
|
||||||
envSchema.shape,
|
baseEnvSchema.shape,
|
||||||
async ({ environment }) => {
|
async ({ environment, database }) => {
|
||||||
try {
|
try {
|
||||||
const status = await pgMcp.diagnostics.getReplicationStatus(environment);
|
const status = await pgMcp.diagnostics.getReplicationStatus(environment, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(status, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(status, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -87,9 +87,9 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
||||||
'pg_slow_queries',
|
'pg_slow_queries',
|
||||||
'Get the slowest queries from pg_stat_statements',
|
'Get the slowest queries from pg_stat_statements',
|
||||||
slowQueriesSchema.shape,
|
slowQueriesSchema.shape,
|
||||||
async ({ environment, limit }) => {
|
async ({ environment, database, limit }) => {
|
||||||
try {
|
try {
|
||||||
const queries = await pgMcp.diagnostics.getSlowQueries(environment, limit);
|
const queries = await pgMcp.diagnostics.getSlowQueries(environment, limit, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(queries, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(queries, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -106,13 +106,14 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
||||||
'pg_vacuum',
|
'pg_vacuum',
|
||||||
'Run VACUUM on a table or the entire database',
|
'Run VACUUM on a table or the entire database',
|
||||||
vacuumSchema.shape,
|
vacuumSchema.shape,
|
||||||
async ({ environment, table, schema, verbose, analyze }) => {
|
async ({ environment, database, table, schema, verbose, analyze }) => {
|
||||||
try {
|
try {
|
||||||
await pgMcp.diagnostics.triggerVacuum(environment, {
|
await pgMcp.diagnostics.triggerVacuum(environment, {
|
||||||
table,
|
table,
|
||||||
schema,
|
schema,
|
||||||
verbose,
|
verbose,
|
||||||
analyze,
|
analyze,
|
||||||
|
...buildQueryOptions({ database }),
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
|
|
@ -138,9 +139,9 @@ export function registerDiagnosticsTools(server: McpServer, pgMcp: PostgresMcp):
|
||||||
'pg_analyze',
|
'pg_analyze',
|
||||||
'Run ANALYZE on a table to update statistics',
|
'Run ANALYZE on a table to update statistics',
|
||||||
analyzeSchema.shape,
|
analyzeSchema.shape,
|
||||||
async ({ environment, table, schema }) => {
|
async ({ environment, database, table, schema }) => {
|
||||||
try {
|
try {
|
||||||
await pgMcp.diagnostics.analyzeTable(environment, table, schema);
|
await pgMcp.diagnostics.analyzeTable(environment, table, schema, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,13 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||||
import { PostgresMcp } from '../core/index.js';
|
import { PostgresMcp } from '../core/index.js';
|
||||||
|
import {
|
||||||
const envSchema = z.object({
|
baseEnvSchema,
|
||||||
environment: z.string().describe('Database environment name'),
|
schemaFilterSchema,
|
||||||
});
|
tableSchema,
|
||||||
|
indexFilterSchema,
|
||||||
const schemaFilterSchema = z.object({
|
buildQueryOptions,
|
||||||
environment: z.string().describe('Database environment name'),
|
} from './common-schemas.js';
|
||||||
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'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): void {
|
||||||
server.tool(
|
server.tool(
|
||||||
|
|
@ -39,10 +25,10 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
server.tool(
|
server.tool(
|
||||||
'pg_test_connection',
|
'pg_test_connection',
|
||||||
'Test connection to a database environment',
|
'Test connection to a database environment',
|
||||||
envSchema.shape,
|
baseEnvSchema.shape,
|
||||||
async ({ environment }) => {
|
async ({ environment, database }) => {
|
||||||
try {
|
try {
|
||||||
const pool = pgMcp.connections.getPool(environment);
|
const pool = pgMcp.connections.getPool(environment, database);
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
const result = await client.query('SELECT version()');
|
const result = await client.query('SELECT version()');
|
||||||
client.release();
|
client.release();
|
||||||
|
|
@ -60,11 +46,11 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
|
|
||||||
server.tool(
|
server.tool(
|
||||||
'pg_list_databases',
|
'pg_list_databases',
|
||||||
'List all databases in the PostgreSQL server',
|
'List all databases in the database server (useful with serverMode)',
|
||||||
envSchema.shape,
|
baseEnvSchema.shape,
|
||||||
async ({ environment }) => {
|
async ({ environment, database }) => {
|
||||||
try {
|
try {
|
||||||
const databases = await pgMcp.metadata.listDatabases(environment);
|
const databases = await pgMcp.metadata.listDatabases(environment, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(databases, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(databases, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -80,10 +66,10 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
server.tool(
|
server.tool(
|
||||||
'pg_list_schemas',
|
'pg_list_schemas',
|
||||||
'List all schemas in the database',
|
'List all schemas in the database',
|
||||||
envSchema.shape,
|
baseEnvSchema.shape,
|
||||||
async ({ environment }) => {
|
async ({ environment, database }) => {
|
||||||
try {
|
try {
|
||||||
const schemas = await pgMcp.metadata.listSchemas(environment);
|
const schemas = await pgMcp.metadata.listSchemas(environment, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(schemas, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(schemas, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -100,9 +86,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
'pg_list_tables',
|
'pg_list_tables',
|
||||||
'List tables in the database, optionally filtered by schema',
|
'List tables in the database, optionally filtered by schema',
|
||||||
schemaFilterSchema.shape,
|
schemaFilterSchema.shape,
|
||||||
async ({ environment, schema }) => {
|
async ({ environment, database, schema }) => {
|
||||||
try {
|
try {
|
||||||
const tables = await pgMcp.metadata.listTables(environment, schema);
|
const tables = await pgMcp.metadata.listTables(environment, schema, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(tables, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(tables, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -119,9 +105,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
'pg_list_views',
|
'pg_list_views',
|
||||||
'List views in the database, optionally filtered by schema',
|
'List views in the database, optionally filtered by schema',
|
||||||
schemaFilterSchema.shape,
|
schemaFilterSchema.shape,
|
||||||
async ({ environment, schema }) => {
|
async ({ environment, database, schema }) => {
|
||||||
try {
|
try {
|
||||||
const views = await pgMcp.metadata.listViews(environment, schema);
|
const views = await pgMcp.metadata.listViews(environment, schema, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(views, null, 2) }],
|
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',
|
'pg_list_materialized_views',
|
||||||
'List materialized views in the database, optionally filtered by schema',
|
'List materialized views in the database, optionally filtered by schema',
|
||||||
schemaFilterSchema.shape,
|
schemaFilterSchema.shape,
|
||||||
async ({ environment, schema }) => {
|
async ({ environment, database, schema }) => {
|
||||||
try {
|
try {
|
||||||
const views = await pgMcp.metadata.listMaterializedViews(environment, schema);
|
const views = await pgMcp.metadata.listMaterializedViews(environment, schema, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(views, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(views, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -157,10 +143,10 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
'pg_list_indexes',
|
'pg_list_indexes',
|
||||||
'List indexes in the database, optionally filtered by table and schema',
|
'List indexes in the database, optionally filtered by table and schema',
|
||||||
indexFilterSchema.shape,
|
indexFilterSchema.shape,
|
||||||
async ({ environment, table, schema }) => {
|
async ({ environment, database, table, schema }) => {
|
||||||
try {
|
try {
|
||||||
const tableId = table ? { schema: schema || 'public', name: table } : undefined;
|
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 {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(indexes, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(indexes, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -177,9 +163,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
'pg_list_sequences',
|
'pg_list_sequences',
|
||||||
'List sequences in the database, optionally filtered by schema',
|
'List sequences in the database, optionally filtered by schema',
|
||||||
schemaFilterSchema.shape,
|
schemaFilterSchema.shape,
|
||||||
async ({ environment, schema }) => {
|
async ({ environment, database, schema }) => {
|
||||||
try {
|
try {
|
||||||
const sequences = await pgMcp.metadata.listSequences(environment, schema);
|
const sequences = await pgMcp.metadata.listSequences(environment, schema, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(sequences, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(sequences, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -195,10 +181,10 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
server.tool(
|
server.tool(
|
||||||
'pg_list_extensions',
|
'pg_list_extensions',
|
||||||
'List installed PostgreSQL extensions',
|
'List installed PostgreSQL extensions',
|
||||||
envSchema.shape,
|
baseEnvSchema.shape,
|
||||||
async ({ environment }) => {
|
async ({ environment, database }) => {
|
||||||
try {
|
try {
|
||||||
const extensions = await pgMcp.metadata.listExtensions(environment);
|
const extensions = await pgMcp.metadata.listExtensions(environment, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(extensions, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(extensions, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
@ -215,9 +201,9 @@ export function registerMetadataTools(server: McpServer, pgMcp: PostgresMcp): vo
|
||||||
'pg_describe_table',
|
'pg_describe_table',
|
||||||
'Get detailed table structure including columns, constraints, and indexes',
|
'Get detailed table structure including columns, constraints, and indexes',
|
||||||
tableSchema.shape,
|
tableSchema.shape,
|
||||||
async ({ environment, table, schema }) => {
|
async ({ environment, database, table, schema }) => {
|
||||||
try {
|
try {
|
||||||
const definition = await pgMcp.metadata.getTableDefinition(environment, table, schema);
|
const definition = await pgMcp.metadata.getTableDefinition(environment, table, schema, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(definition, null, 2) }],
|
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',
|
'pg_show_search_path',
|
||||||
'Show the current search_path for the session',
|
'Show the current search_path for the session',
|
||||||
schemaFilterSchema.shape,
|
schemaFilterSchema.shape,
|
||||||
async ({ environment, schema }) => {
|
async ({ environment, database, schema }) => {
|
||||||
try {
|
try {
|
||||||
const searchPath = await pgMcp.metadata.describeSearchPath(environment, schema);
|
const searchPath = await pgMcp.metadata.describeSearchPath(environment, schema, buildQueryOptions({ database }));
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(searchPath, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(searchPath, null, 2) }],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,26 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||||
import { PostgresMcp } from '../core/index.js';
|
import { PostgresMcp } from '../core/index.js';
|
||||||
|
import {
|
||||||
const querySchema = z.object({
|
querySchema,
|
||||||
environment: z.string().describe('Database environment name'),
|
paramQuerySchema,
|
||||||
sql: z.string().describe('SQL query to execute'),
|
paginatedQuerySchema,
|
||||||
schema: z.string().optional().describe('Schema to use for search_path'),
|
explainSchema,
|
||||||
});
|
buildQueryOptions,
|
||||||
|
} from './common-schemas.js';
|
||||||
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'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void {
|
export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void {
|
||||||
server.tool(
|
server.tool(
|
||||||
'pg_query',
|
'pg_query',
|
||||||
'Execute a SQL query and return results',
|
'Execute a SQL query and return results',
|
||||||
querySchema.shape,
|
querySchema.shape,
|
||||||
async ({ environment, sql, schema }) => {
|
async ({ environment, database, sql, schema }) => {
|
||||||
try {
|
try {
|
||||||
const result = await pgMcp.queries.execute(
|
const result = await pgMcp.queries.execute(
|
||||||
environment,
|
environment,
|
||||||
sql,
|
sql,
|
||||||
[],
|
[],
|
||||||
schema ? { schema } : undefined
|
buildQueryOptions({ database, schema })
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
|
|
@ -68,13 +45,13 @@ export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void
|
||||||
'pg_query_with_params',
|
'pg_query_with_params',
|
||||||
'Execute a parameterized SQL query with $1, $2, etc. placeholders',
|
'Execute a parameterized SQL query with $1, $2, etc. placeholders',
|
||||||
paramQuerySchema.shape,
|
paramQuerySchema.shape,
|
||||||
async ({ environment, sql, params, schema }) => {
|
async ({ environment, database, sql, params, schema }) => {
|
||||||
try {
|
try {
|
||||||
const result = await pgMcp.queries.execute(
|
const result = await pgMcp.queries.execute(
|
||||||
environment,
|
environment,
|
||||||
sql,
|
sql,
|
||||||
params,
|
params,
|
||||||
schema ? { schema } : undefined
|
buildQueryOptions({ database, schema })
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
|
|
@ -99,14 +76,14 @@ export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void
|
||||||
'pg_query_paginated',
|
'pg_query_paginated',
|
||||||
'Execute a SQL query with pagination (LIMIT/OFFSET)',
|
'Execute a SQL query with pagination (LIMIT/OFFSET)',
|
||||||
paginatedQuerySchema.shape,
|
paginatedQuerySchema.shape,
|
||||||
async ({ environment, sql, params, limit, offset, schema }) => {
|
async ({ environment, database, sql, params, limit, offset, schema }) => {
|
||||||
try {
|
try {
|
||||||
const result = await pgMcp.queries.executePaginated(
|
const result = await pgMcp.queries.executePaginated(
|
||||||
environment,
|
environment,
|
||||||
sql,
|
sql,
|
||||||
params || [],
|
params || [],
|
||||||
{ limit, offset },
|
{ limit, offset },
|
||||||
schema ? { schema } : undefined
|
buildQueryOptions({ database, schema })
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
|
|
@ -132,13 +109,13 @@ export function registerQueryTools(server: McpServer, pgMcp: PostgresMcp): void
|
||||||
'pg_explain',
|
'pg_explain',
|
||||||
'Get the query execution plan (EXPLAIN)',
|
'Get the query execution plan (EXPLAIN)',
|
||||||
explainSchema.shape,
|
explainSchema.shape,
|
||||||
async ({ environment, sql, params, analyze, schema }) => {
|
async ({ environment, database, sql, params, analyze, schema }) => {
|
||||||
try {
|
try {
|
||||||
const plan = await pgMcp.queries.explain(
|
const plan = await pgMcp.queries.explain(
|
||||||
environment,
|
environment,
|
||||||
sql,
|
sql,
|
||||||
params || [],
|
params || [],
|
||||||
{ analyze, schema }
|
{ analyze, ...buildQueryOptions({ database, schema }) }
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: JSON.stringify(plan, null, 2) }],
|
content: [{ type: 'text', text: JSON.stringify(plan, null, 2) }],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user