pg-lens-mcp 0.1.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SQL query builders and filter-to-WHERE clause converters
3
+ */
4
+ export interface Filter {
5
+ column: string;
6
+ operator: "=" | "!=" | "<" | ">" | "<=" | ">=" | "LIKE" | "ILIKE" | "IN" | "IS NULL" | "IS NOT NULL";
7
+ value?: string | number | boolean | null | (string | number)[];
8
+ }
9
+ /**
10
+ * Convert structured filters to parameterized WHERE clause
11
+ * Safe from SQL injection via parameterized queries
12
+ */
13
+ export declare function buildWhereClause(filters: Filter[]): {
14
+ whereClause: string;
15
+ params: unknown[];
16
+ };
17
+ /**
18
+ * Build a paginated SELECT query with optional filters and ordering
19
+ */
20
+ export declare function buildSelectQuery(options: {
21
+ schema: string;
22
+ table: string;
23
+ columns?: string[];
24
+ filters?: Filter[];
25
+ orderBy?: string;
26
+ orderDirection?: "ASC" | "DESC";
27
+ limit: number;
28
+ offset: number;
29
+ }): {
30
+ query: string;
31
+ params: unknown[];
32
+ };
33
+ /**
34
+ * Build COUNT query with same filters
35
+ */
36
+ export declare function buildCountQuery(schema: string, table: string, filters?: Filter[]): {
37
+ query: string;
38
+ params: unknown[];
39
+ };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * SQL query builders and filter-to-WHERE clause converters
3
+ */
4
+ /**
5
+ * Convert structured filters to parameterized WHERE clause
6
+ * Safe from SQL injection via parameterized queries
7
+ */
8
+ export function buildWhereClause(filters) {
9
+ if (filters.length === 0) {
10
+ return { whereClause: "", params: [] };
11
+ }
12
+ const params = [];
13
+ const conditions = [];
14
+ filters.forEach((filter) => {
15
+ const columnName = `"${filter.column}"`;
16
+ if (filter.operator === "IS NULL" || filter.operator === "IS NOT NULL") {
17
+ conditions.push(`${columnName} ${filter.operator}`);
18
+ }
19
+ else if (filter.operator === "IN") {
20
+ if (!Array.isArray(filter.value)) {
21
+ throw new Error(`IN operator requires array value for column ${filter.column}`);
22
+ }
23
+ const placeholders = filter.value.map((val) => {
24
+ params.push(val);
25
+ return `$${params.length}`;
26
+ });
27
+ conditions.push(`${columnName} IN (${placeholders.join(", ")})`);
28
+ }
29
+ else {
30
+ if (filter.value === undefined) {
31
+ throw new Error(`Operator ${filter.operator} requires a value for column ${filter.column}`);
32
+ }
33
+ params.push(filter.value);
34
+ conditions.push(`${columnName} ${filter.operator} $${params.length}`);
35
+ }
36
+ });
37
+ return {
38
+ whereClause: conditions.join(" AND "),
39
+ params,
40
+ };
41
+ }
42
+ /**
43
+ * Build a paginated SELECT query with optional filters and ordering
44
+ */
45
+ export function buildSelectQuery(options) {
46
+ const { schema, table, columns = ["*"], filters = [], orderBy, orderDirection = "ASC", limit, offset, } = options;
47
+ // Build column list
48
+ const columnList = columns[0] === "*" ? "*" : columns.map((c) => `"${c}"`).join(", ");
49
+ // Build WHERE clause
50
+ const { whereClause, params } = buildWhereClause(filters);
51
+ // Start building query
52
+ let query = `SELECT ${columnList} FROM "${schema}"."${table}"`;
53
+ // Add WHERE if filters exist
54
+ if (whereClause) {
55
+ query += ` WHERE ${whereClause}`;
56
+ }
57
+ // Add ORDER BY if specified
58
+ if (orderBy) {
59
+ query += ` ORDER BY "${orderBy}" ${orderDirection}`;
60
+ }
61
+ // Add pagination
62
+ params.push(limit, offset);
63
+ query += ` LIMIT $${params.length - 1} OFFSET $${params.length}`;
64
+ return { query, params };
65
+ }
66
+ /**
67
+ * Build COUNT query with same filters
68
+ */
69
+ export function buildCountQuery(schema, table, filters = []) {
70
+ const { whereClause, params } = buildWhereClause(filters);
71
+ let query = `SELECT COUNT(*) as total FROM "${schema}"."${table}"`;
72
+ if (whereClause) {
73
+ query += ` WHERE ${whereClause}`;
74
+ }
75
+ return { query, params };
76
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PostgreSQL MCP Server
4
+ * Entry point - handles initialization and graceful shutdown
5
+ */
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PostgreSQL MCP Server
4
+ * Entry point - handles initialization and graceful shutdown
5
+ */
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { createServer } from "./server.js";
8
+ import { healthCheck, shutdown } from "./db/pool.js";
9
+ async function main() {
10
+ try {
11
+ await healthCheck();
12
+ const server = createServer();
13
+ const transport = new StdioServerTransport();
14
+ await server.connect(transport);
15
+ console.error("✓ PostgreSQL MCP Server running on stdio");
16
+ }
17
+ catch (error) {
18
+ console.error("✗ Fatal error during startup:", error);
19
+ process.exit(1);
20
+ }
21
+ }
22
+ // Graceful shutdown handlers
23
+ process.on("SIGINT", async () => {
24
+ await shutdown();
25
+ process.exit(0);
26
+ });
27
+ process.on("SIGTERM", async () => {
28
+ await shutdown();
29
+ process.exit(0);
30
+ });
31
+ // Start the server
32
+ main().catch((error) => {
33
+ console.error("✗ Unhandled error:", error);
34
+ process.exit(1);
35
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * MCP Server setup and tool registration
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ /**
6
+ * Create and configure the MCP server with all tools
7
+ */
8
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * MCP Server setup and tool registration
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { registerListTablesTool } from "./tools/list-tables.js";
6
+ import { registerListSchemasTool } from "./tools/list-schemas.js";
7
+ import { registerSearchColumnTool } from "./tools/search-column.js";
8
+ import { registerGetTableInfoTool } from "./tools/get-table-info.js";
9
+ import { registerGetTableDataTool } from "./tools/get-table-data.js";
10
+ import { registerExecuteQueryTool } from "./tools/execute-query.js";
11
+ import { registerExplainQueryTool } from "./tools/explain-query.js";
12
+ import { registerExplainAnalyzeTool } from "./tools/explain-analyze.js";
13
+ /**
14
+ * Create and configure the MCP server with all tools
15
+ */
16
+ export function createServer() {
17
+ const server = new McpServer({
18
+ name: "postgres-server",
19
+ version: "1.0.0",
20
+ });
21
+ // Register all tools
22
+ registerListSchemasTool(server);
23
+ registerListTablesTool(server);
24
+ registerSearchColumnTool(server);
25
+ registerGetTableInfoTool(server);
26
+ registerGetTableDataTool(server);
27
+ registerExecuteQueryTool(server);
28
+ registerExplainQueryTool(server);
29
+ registerExplainAnalyzeTool(server);
30
+ return server;
31
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tool: execute_query
3
+ * Execute a read-only SQL query with database-enforced READ ONLY transaction
4
+ */
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ export declare function registerExecuteQueryTool(server: McpServer): void;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Tool: execute_query
3
+ * Execute a read-only SQL query with database-enforced READ ONLY transaction
4
+ */
5
+ import { z } from "zod";
6
+ import { executeReadOnly } from "../db/pool.js";
7
+ import { formatQueryResult, formatError } from "../utils/response.js";
8
+ export function registerExecuteQueryTool(server) {
9
+ server.tool("execute_query", "Execute a read-only SQL query. Query runs in a READ ONLY transaction for safety.", {
10
+ query: z.string().describe("SQL query to execute"),
11
+ params: z
12
+ .array(z.union([z.string(), z.number(), z.boolean(), z.null()]))
13
+ .optional()
14
+ .describe("Query parameters for parameterized queries ($1, $2, etc.)"),
15
+ }, async ({ query, params }) => {
16
+ const trimmedQuery = query.trim().toUpperCase();
17
+ // Basic sanity check (still allow SELECT and WITH/CTE)
18
+ if (!trimmedQuery.startsWith("SELECT") &&
19
+ !trimmedQuery.startsWith("WITH") &&
20
+ !trimmedQuery.startsWith("EXPLAIN")) {
21
+ return formatError("Only SELECT, WITH (CTE), and EXPLAIN queries are allowed");
22
+ }
23
+ try {
24
+ // Execute in READ ONLY transaction - PostgreSQL enforces no writes
25
+ const result = await executeReadOnly(query, params || []);
26
+ return formatQueryResult(result.rows, {
27
+ rowCount: result.rowCount,
28
+ });
29
+ }
30
+ catch (error) {
31
+ return formatError(error);
32
+ }
33
+ });
34
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tool: explain_analyze
3
+ * Execute a query and get actual execution statistics
4
+ */
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ export declare function registerExplainAnalyzeTool(server: McpServer): void;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Tool: explain_analyze
3
+ * Execute a query and get actual execution statistics
4
+ */
5
+ import { z } from "zod";
6
+ import { executeReadOnly } from "../db/pool.js";
7
+ import { formatSuccess, formatError } from "../utils/response.js";
8
+ export function registerExplainAnalyzeTool(server) {
9
+ server.tool("explain_analyze", "Execute a query with EXPLAIN ANALYZE to get actual timing and row statistics. ⚠️ WARNING: This actually executes the query, so it may be slow on large tables.", {
10
+ query: z.string().describe("SQL query to analyze"),
11
+ format: z
12
+ .enum(["text", "json"])
13
+ .optional()
14
+ .default("json")
15
+ .describe("Output format for the analysis"),
16
+ buffers: z
17
+ .boolean()
18
+ .optional()
19
+ .default(false)
20
+ .describe("Include buffer usage statistics"),
21
+ timing: z
22
+ .boolean()
23
+ .optional()
24
+ .default(true)
25
+ .describe("Include timing information"),
26
+ verbose: z
27
+ .boolean()
28
+ .optional()
29
+ .default(false)
30
+ .describe("Include verbose details"),
31
+ }, async ({ query, format, buffers, timing, verbose }) => {
32
+ try {
33
+ const options = [
34
+ "ANALYZE",
35
+ `FORMAT ${format?.toUpperCase() || "JSON"}`,
36
+ ];
37
+ if (buffers)
38
+ options.push("BUFFERS");
39
+ if (!timing)
40
+ options.push("TIMING OFF");
41
+ if (verbose)
42
+ options.push("VERBOSE");
43
+ const explainQuery = `EXPLAIN (${options.join(", ")}) ${query}`;
44
+ // Execute in READ ONLY transaction - query runs but can't modify data
45
+ const result = await executeReadOnly(explainQuery);
46
+ // For JSON format, parse and format nicely
47
+ if (format === "json" && result.rows.length > 0) {
48
+ const row = result.rows[0];
49
+ return formatSuccess(row["QUERY PLAN"]);
50
+ }
51
+ // For text, return as-is
52
+ const analysis = result.rows.map((row) => row["QUERY PLAN"]).join("\n");
53
+ return formatSuccess(analysis);
54
+ }
55
+ catch (error) {
56
+ return formatError(error);
57
+ }
58
+ });
59
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tool: explain_query
3
+ * Get the execution plan for a query without executing it
4
+ */
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ export declare function registerExplainQueryTool(server: McpServer): void;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Tool: explain_query
3
+ * Get the execution plan for a query without executing it
4
+ */
5
+ import { z } from "zod";
6
+ import { executeReadOnly } from "../db/pool.js";
7
+ import { formatSuccess, formatError } from "../utils/response.js";
8
+ export function registerExplainQueryTool(server) {
9
+ server.tool("explain_query", "Get the query execution plan (EXPLAIN) without actually running the query. Useful for understanding how PostgreSQL will execute a query.", {
10
+ query: z.string().describe("SQL query to analyze"),
11
+ format: z
12
+ .enum(["text", "json", "yaml"])
13
+ .optional()
14
+ .default("json")
15
+ .describe("Output format for the plan"),
16
+ verbose: z
17
+ .boolean()
18
+ .optional()
19
+ .default(false)
20
+ .describe("Include verbose details in the plan"),
21
+ }, async ({ query, format, verbose }) => {
22
+ try {
23
+ const options = [`FORMAT ${format?.toUpperCase() || "JSON"}`];
24
+ if (verbose) {
25
+ options.push("VERBOSE");
26
+ }
27
+ const explainQuery = `EXPLAIN (${options.join(", ")}) ${query}`;
28
+ // Execute in READ ONLY transaction for safety
29
+ const result = await executeReadOnly(explainQuery);
30
+ // For JSON format, parse and format nicely
31
+ if (format === "json" && result.rows.length > 0) {
32
+ const row = result.rows[0];
33
+ return formatSuccess(row["QUERY PLAN"]);
34
+ }
35
+ // For text/yaml, return as-is
36
+ const plan = result.rows.map((row) => row["QUERY PLAN"]).join("\n");
37
+ return formatSuccess(plan);
38
+ }
39
+ catch (error) {
40
+ return formatError(error);
41
+ }
42
+ });
43
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tool: get_table_data
3
+ * Query table data with structured filters and pagination
4
+ */
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ export declare function registerGetTableDataTool(server: McpServer): void;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tool: get_table_data
3
+ * Query table data with structured filters and pagination
4
+ */
5
+ import { z } from "zod";
6
+ import { pool } from "../db/pool.js";
7
+ import { config } from "../config.js";
8
+ import { buildSelectQuery, buildCountQuery } from "../db/queries.js";
9
+ import { formatQueryResult, formatError } from "../utils/response.js";
10
+ import { validateTableExists, validateColumns } from "../utils/validation.js";
11
+ const FilterSchema = z.object({
12
+ column: z.string().describe("Column name to filter on"),
13
+ operator: z
14
+ .enum(["=", "!=", "<", ">", "<=", ">=", "LIKE", "ILIKE", "IN", "IS NULL", "IS NOT NULL"])
15
+ .describe("Comparison operator"),
16
+ value: z
17
+ .union([
18
+ z.string(),
19
+ z.number(),
20
+ z.boolean(),
21
+ z.null(),
22
+ z.array(z.union([z.string(), z.number()])),
23
+ ])
24
+ .optional()
25
+ .describe("Value to compare (not needed for IS NULL/IS NOT NULL)"),
26
+ });
27
+ export function registerGetTableDataTool(server) {
28
+ server.tool("get_table_data", "Retrieve data from a table with optional structured filtering and pagination.", {
29
+ table_name: z.string().describe("Name of the table to query"),
30
+ schema: z
31
+ .string()
32
+ .optional()
33
+ .describe("Schema name (default: public)"),
34
+ columns: z
35
+ .array(z.string())
36
+ .optional()
37
+ .describe("Specific columns to select (default: all)"),
38
+ filters: z
39
+ .array(FilterSchema)
40
+ .optional()
41
+ .describe("Structured filters for WHERE clause. Example: [{column: 'status', operator: '=', value: 'active'}]"),
42
+ limit: z
43
+ .number()
44
+ .optional()
45
+ .describe("Maximum number of rows to return (default: 100, max: 1000)"),
46
+ offset: z
47
+ .number()
48
+ .optional()
49
+ .describe("Number of rows to skip (default: 0)"),
50
+ order_by: z
51
+ .string()
52
+ .optional()
53
+ .describe("Column name to order by"),
54
+ order_direction: z
55
+ .enum(["ASC", "DESC"])
56
+ .optional()
57
+ .describe("Sort direction (default: ASC)"),
58
+ }, async ({ table_name, schema, columns, filters, limit, offset, order_by, order_direction, }) => {
59
+ const targetSchema = schema || config.schema;
60
+ const effectiveLimit = Math.min(limit || 100, 1000);
61
+ const effectiveOffset = offset || 0;
62
+ try {
63
+ // Validate table exists
64
+ const tableExists = await validateTableExists(pool, targetSchema, table_name);
65
+ if (!tableExists) {
66
+ return formatError(`Table "${targetSchema}"."${table_name}" does not exist`);
67
+ }
68
+ // Validate columns if specified
69
+ if (columns && columns.length > 0) {
70
+ const validation = await validateColumns(pool, targetSchema, table_name, columns);
71
+ if (!validation.valid) {
72
+ return formatError(`Invalid columns: ${validation.invalidColumns.join(", ")}`);
73
+ }
74
+ }
75
+ // Build queries
76
+ const { query, params } = buildSelectQuery({
77
+ schema: targetSchema,
78
+ table: table_name,
79
+ columns,
80
+ filters: filters,
81
+ orderBy: order_by,
82
+ orderDirection: order_direction,
83
+ limit: effectiveLimit,
84
+ offset: effectiveOffset,
85
+ });
86
+ const { query: countQuery, params: countParams } = buildCountQuery(targetSchema, table_name, filters);
87
+ // Execute queries in parallel
88
+ const [dataResult, countResult] = await Promise.all([
89
+ pool.query(query, params),
90
+ pool.query(countQuery, countParams),
91
+ ]);
92
+ return formatQueryResult(dataResult.rows, {
93
+ schema: targetSchema,
94
+ table: table_name,
95
+ totalRows: parseInt(countResult.rows[0].total, 10),
96
+ returnedRows: dataResult.rows.length,
97
+ limit: effectiveLimit,
98
+ offset: effectiveOffset,
99
+ });
100
+ }
101
+ catch (error) {
102
+ return formatError(error);
103
+ }
104
+ });
105
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tool: get_table_info
3
+ * Get detailed table schema including columns, constraints, and indexes
4
+ */
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ export declare function registerGetTableInfoTool(server: McpServer): void;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Tool: get_table_info
3
+ * Get detailed table schema including columns, constraints, and indexes
4
+ */
5
+ import { z } from "zod";
6
+ import { pool } from "../db/pool.js";
7
+ import { config } from "../config.js";
8
+ import { formatSuccess, formatError } from "../utils/response.js";
9
+ export function registerGetTableInfoTool(server) {
10
+ server.tool("get_table_info", "Get detailed information about a table including columns, data types, and constraints.", {
11
+ table_name: z.string().describe("Name of the table to inspect"),
12
+ schema: z
13
+ .string()
14
+ .optional()
15
+ .describe("Schema name (default: public)"),
16
+ }, async ({ table_name, schema }) => {
17
+ const targetSchema = schema || config.schema;
18
+ // Get column information
19
+ const columnsQuery = `
20
+ SELECT
21
+ c.column_name,
22
+ c.data_type,
23
+ c.character_maximum_length,
24
+ c.numeric_precision,
25
+ c.numeric_scale,
26
+ c.is_nullable,
27
+ c.column_default,
28
+ c.udt_name
29
+ FROM information_schema.columns c
30
+ WHERE c.table_schema = $1
31
+ AND c.table_name = $2
32
+ ORDER BY c.ordinal_position
33
+ `;
34
+ // Get primary key information
35
+ const pkQuery = `
36
+ SELECT
37
+ kcu.column_name
38
+ FROM information_schema.table_constraints tc
39
+ JOIN information_schema.key_column_usage kcu
40
+ ON tc.constraint_name = kcu.constraint_name
41
+ AND tc.table_schema = kcu.table_schema
42
+ WHERE tc.table_schema = $1
43
+ AND tc.table_name = $2
44
+ AND tc.constraint_type = 'PRIMARY KEY'
45
+ `;
46
+ // Get foreign key information
47
+ const fkQuery = `
48
+ SELECT
49
+ kcu.column_name,
50
+ ccu.table_name AS foreign_table,
51
+ ccu.column_name AS foreign_column
52
+ FROM information_schema.table_constraints tc
53
+ JOIN information_schema.key_column_usage kcu
54
+ ON tc.constraint_name = kcu.constraint_name
55
+ AND tc.table_schema = kcu.table_schema
56
+ JOIN information_schema.constraint_column_usage ccu
57
+ ON ccu.constraint_name = tc.constraint_name
58
+ AND ccu.table_schema = tc.table_schema
59
+ WHERE tc.table_schema = $1
60
+ AND tc.table_name = $2
61
+ AND tc.constraint_type = 'FOREIGN KEY'
62
+ `;
63
+ // Get indexes
64
+ const indexQuery = `
65
+ SELECT
66
+ i.relname AS index_name,
67
+ a.attname AS column_name,
68
+ ix.indisunique AS is_unique,
69
+ ix.indisprimary AS is_primary
70
+ FROM pg_class t
71
+ JOIN pg_index ix ON t.oid = ix.indrelid
72
+ JOIN pg_class i ON i.oid = ix.indexrelid
73
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
74
+ JOIN pg_namespace n ON n.oid = t.relnamespace
75
+ WHERE t.relname = $2
76
+ AND n.nspname = $1
77
+ ORDER BY i.relname, a.attnum
78
+ `;
79
+ try {
80
+ const [columnsResult, pkResult, fkResult, indexResult] = await Promise.all([
81
+ pool.query(columnsQuery, [targetSchema, table_name]),
82
+ pool.query(pkQuery, [targetSchema, table_name]),
83
+ pool.query(fkQuery, [targetSchema, table_name]),
84
+ pool.query(indexQuery, [targetSchema, table_name]),
85
+ ]);
86
+ const primaryKeys = pkResult.rows.map((row) => row.column_name);
87
+ const foreignKeys = fkResult.rows.reduce((acc, row) => {
88
+ acc[row.column_name] = {
89
+ references: `${row.foreign_table}.${row.foreign_column}`,
90
+ };
91
+ return acc;
92
+ }, {});
93
+ const columns = columnsResult.rows.map((row) => ({
94
+ name: row.column_name,
95
+ type: row.data_type,
96
+ udtName: row.udt_name,
97
+ maxLength: row.character_maximum_length,
98
+ precision: row.numeric_precision,
99
+ scale: row.numeric_scale,
100
+ nullable: row.is_nullable === "YES",
101
+ default: row.column_default,
102
+ isPrimaryKey: primaryKeys.includes(row.column_name),
103
+ foreignKey: foreignKeys[row.column_name] || null,
104
+ }));
105
+ // Group indexes
106
+ const indexes = indexResult.rows.reduce((acc, row) => {
107
+ if (!acc[row.index_name]) {
108
+ acc[row.index_name] = {
109
+ name: row.index_name,
110
+ columns: [],
111
+ isUnique: row.is_unique,
112
+ isPrimary: row.is_primary,
113
+ };
114
+ }
115
+ acc[row.index_name].columns.push(row.column_name);
116
+ return acc;
117
+ }, {});
118
+ const tableInfo = {
119
+ schema: targetSchema,
120
+ table: table_name,
121
+ columns,
122
+ indexes: Object.values(indexes),
123
+ };
124
+ return formatSuccess(tableInfo);
125
+ }
126
+ catch (error) {
127
+ return formatError(error);
128
+ }
129
+ });
130
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tool: list_schemas
3
+ * List all non-system schemas in the database
4
+ */
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ export declare function registerListSchemasTool(server: McpServer): void;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Tool: list_schemas
3
+ * List all non-system schemas in the database
4
+ */
5
+ import { pool } from "../db/pool.js";
6
+ import { formatSuccess, formatError } from "../utils/response.js";
7
+ export function registerListSchemasTool(server) {
8
+ server.tool("list_schemas", "List all non-system schemas in the database.", {}, async () => {
9
+ const query = `
10
+ SELECT
11
+ schema_name,
12
+ schema_owner
13
+ FROM information_schema.schemata
14
+ WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
15
+ ORDER BY schema_name
16
+ `;
17
+ try {
18
+ const result = await pool.query(query);
19
+ return formatSuccess(result.rows, { asTable: true });
20
+ }
21
+ catch (error) {
22
+ return formatError(error);
23
+ }
24
+ });
25
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tool: list_tables
3
+ * List all tables in a schema
4
+ */
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ export declare function registerListTablesTool(server: McpServer): void;