@universal-mcp-toolkit/server-postgresql 0.1.0

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,35 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/json",
3
+ "name": "postgresql",
4
+ "title": "PostgreSQL MCP Server",
5
+ "description": "Schema inspection and safe query tools for PostgreSQL.",
6
+ "version": "0.1.0",
7
+ "transports": [
8
+ "stdio",
9
+ "http+sse"
10
+ ],
11
+ "authentication": {
12
+ "mode": "environment-variables",
13
+ "required": [
14
+ "POSTGRESQL_URL"
15
+ ]
16
+ },
17
+ "capabilities": {
18
+ "tools": true,
19
+ "resources": true,
20
+ "prompts": true
21
+ },
22
+ "packageName": "@universal-mcp-toolkit/server-postgresql",
23
+ "homepage": "https://github.com/universal-mcp-toolkit/universal-mcp-toolkit#readme",
24
+ "tools": [
25
+ "list-tables",
26
+ "describe-table",
27
+ "run-query"
28
+ ],
29
+ "resources": [
30
+ "schema-overview"
31
+ ],
32
+ "prompts": [
33
+ "query-review"
34
+ ]
35
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 universal-mcp-toolkit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,96 @@
1
+ import * as _universal_mcp_toolkit_core from '@universal-mcp-toolkit/core';
2
+ import { ToolkitServer, ToolkitServerMetadata } from '@universal-mcp-toolkit/core';
3
+ import { z } from 'zod';
4
+
5
+ type JsonPrimitive = boolean | number | string | null;
6
+ interface JsonObject {
7
+ [key: string]: JsonValue;
8
+ }
9
+ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
10
+ declare const postgresqlEnvShape: {
11
+ POSTGRESQL_URL: z.ZodString;
12
+ POSTGRESQL_SCHEMA: z.ZodDefault<z.ZodString>;
13
+ POSTGRESQL_ALLOW_WRITES: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
14
+ true: "true";
15
+ false: "false";
16
+ }>>, z.ZodTransform<boolean, "true" | "false">>;
17
+ POSTGRESQL_SSL: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
18
+ true: "true";
19
+ false: "false";
20
+ }>>, z.ZodTransform<boolean, "true" | "false">>;
21
+ POSTGRESQL_MAX_RESULT_ROWS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
22
+ POSTGRESQL_RESOURCE_TABLE_LIMIT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
23
+ };
24
+ type PostgreSqlEnv = z.infer<z.ZodObject<typeof postgresqlEnvShape>>;
25
+ type SqlParameter = boolean | number | string | null;
26
+ interface PostgreSqlTableSummary {
27
+ schema: string;
28
+ name: string;
29
+ type: string;
30
+ }
31
+ interface PostgreSqlColumnSummary {
32
+ name: string;
33
+ ordinalPosition: number;
34
+ dataType: string;
35
+ isNullable: boolean;
36
+ defaultValue: string | null;
37
+ maxLength: number | null;
38
+ numericPrecision: number | null;
39
+ numericScale: number | null;
40
+ comment: string | null;
41
+ }
42
+ interface PostgreSqlConstraintSummary {
43
+ name: string;
44
+ type: string;
45
+ columns: string[];
46
+ }
47
+ interface PostgreSqlFieldSummary {
48
+ name: string;
49
+ dataType: string | null;
50
+ }
51
+ interface PostgreSqlQueryExecution {
52
+ rowCount: number | null;
53
+ fields: PostgreSqlFieldSummary[];
54
+ rows: JsonObject[];
55
+ }
56
+ interface PostgreSqlClient {
57
+ listTables(input: {
58
+ schema: string | null;
59
+ includeSystemSchemas: boolean;
60
+ limit: number;
61
+ }): Promise<{
62
+ database: string;
63
+ tables: PostgreSqlTableSummary[];
64
+ }>;
65
+ describeTable(input: {
66
+ schema: string;
67
+ table: string;
68
+ }): Promise<{
69
+ database: string;
70
+ schema: string;
71
+ table: string;
72
+ columns: PostgreSqlColumnSummary[];
73
+ constraints: PostgreSqlConstraintSummary[];
74
+ }>;
75
+ runQuery(input: {
76
+ sql: string;
77
+ params: readonly SqlParameter[];
78
+ }): Promise<PostgreSqlQueryExecution>;
79
+ close?(): Promise<void>;
80
+ }
81
+ declare const metadata: ToolkitServerMetadata;
82
+ declare const serverCard: _universal_mcp_toolkit_core.ToolkitServerCard;
83
+ declare class PostgreSqlServer extends ToolkitServer {
84
+ private readonly env;
85
+ private readonly client;
86
+ constructor(env: PostgreSqlEnv, client: PostgreSqlClient);
87
+ close(): Promise<void>;
88
+ }
89
+ interface CreatePostgreSqlServerOptions {
90
+ env?: PostgreSqlEnv;
91
+ client?: PostgreSqlClient;
92
+ }
93
+ declare function createServer(options?: CreatePostgreSqlServerOptions): PostgreSqlServer;
94
+ declare function main(argv?: readonly string[]): Promise<void>;
95
+
96
+ export { type CreatePostgreSqlServerOptions, type JsonObject, type JsonValue, type PostgreSqlClient, type PostgreSqlColumnSummary, type PostgreSqlConstraintSummary, type PostgreSqlEnv, type PostgreSqlFieldSummary, type PostgreSqlQueryExecution, PostgreSqlServer, type PostgreSqlTableSummary, createServer, main, metadata, serverCard };
package/dist/index.js ADDED
@@ -0,0 +1,556 @@
1
+ // src/index.ts
2
+ import { createRequire } from "module";
3
+ import { pathToFileURL } from "url";
4
+ import {
5
+ ExternalServiceError,
6
+ ToolkitServer,
7
+ ValidationError,
8
+ createServerCard,
9
+ defineTool,
10
+ loadEnv,
11
+ parseRuntimeOptions,
12
+ runToolkitServer
13
+ } from "@universal-mcp-toolkit/core";
14
+ import { z } from "zod";
15
+ var require2 = createRequire(import.meta.url);
16
+ var jsonValueSchema = z.lazy(
17
+ () => z.union([z.string(), z.number().finite(), z.boolean(), z.null(), z.array(jsonValueSchema), z.record(z.string(), jsonValueSchema)])
18
+ );
19
+ var jsonObjectSchema = z.record(z.string(), jsonValueSchema);
20
+ var booleanFlag = z.enum(["true", "false"]).default("false").transform((value) => value === "true");
21
+ var postgresqlEnvShape = {
22
+ POSTGRESQL_URL: z.string().min(1),
23
+ POSTGRESQL_SCHEMA: z.string().min(1).default("public"),
24
+ POSTGRESQL_ALLOW_WRITES: booleanFlag,
25
+ POSTGRESQL_SSL: booleanFlag,
26
+ POSTGRESQL_MAX_RESULT_ROWS: z.coerce.number().int().positive().max(1e3).default(200),
27
+ POSTGRESQL_RESOURCE_TABLE_LIMIT: z.coerce.number().int().positive().max(200).default(50)
28
+ };
29
+ var TOOL_NAMES = ["describe-table", "list-tables", "run-query"];
30
+ var RESOURCE_NAMES = ["schema-overview"];
31
+ var PROMPT_NAMES = ["query-review"];
32
+ var metadata = {
33
+ id: "postgresql",
34
+ title: "PostgreSQL MCP Server",
35
+ description: "Schema inspection and safe query tools for PostgreSQL.",
36
+ version: "0.1.0",
37
+ packageName: "@universal-mcp-toolkit/server-postgresql",
38
+ homepage: "https://github.com/universal-mcp-toolkit/universal-mcp-toolkit#readme",
39
+ repositoryUrl: "https://github.com/universal-mcp-toolkit/universal-mcp-toolkit",
40
+ envVarNames: ["POSTGRESQL_URL"],
41
+ transports: ["stdio", "sse"],
42
+ toolNames: TOOL_NAMES,
43
+ resourceNames: RESOURCE_NAMES,
44
+ promptNames: PROMPT_NAMES
45
+ };
46
+ var serverCard = createServerCard(metadata);
47
+ function extractErrorMessage(error) {
48
+ if (error instanceof Error) {
49
+ return error.message;
50
+ }
51
+ return "Unknown PostgreSQL error.";
52
+ }
53
+ function readString(value) {
54
+ return typeof value === "string" ? value : String(value ?? "");
55
+ }
56
+ function readNumberOrNull(value) {
57
+ if (typeof value === "number" && Number.isFinite(value)) {
58
+ return value;
59
+ }
60
+ if (typeof value === "string" && value.length > 0) {
61
+ const parsed = Number(value);
62
+ return Number.isFinite(parsed) ? parsed : null;
63
+ }
64
+ return null;
65
+ }
66
+ function readBooleanFlag(value) {
67
+ if (typeof value === "boolean") {
68
+ return value;
69
+ }
70
+ if (typeof value === "string") {
71
+ const normalized = value.toLowerCase();
72
+ return normalized === "true" || normalized === "yes";
73
+ }
74
+ return false;
75
+ }
76
+ function readStringArray(value) {
77
+ if (!Array.isArray(value)) {
78
+ return [];
79
+ }
80
+ return value.map((entry) => String(entry));
81
+ }
82
+ function hasToHexString(value) {
83
+ return "toHexString" in value && typeof value.toHexString === "function";
84
+ }
85
+ function hasToJson(value) {
86
+ return "toJSON" in value && typeof value.toJSON === "function";
87
+ }
88
+ function sanitizeJsonValue(value) {
89
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
90
+ return value;
91
+ }
92
+ if (typeof value === "number") {
93
+ return Number.isFinite(value) ? value : String(value);
94
+ }
95
+ if (typeof value === "bigint") {
96
+ return value.toString();
97
+ }
98
+ if (value instanceof Date) {
99
+ return value.toISOString();
100
+ }
101
+ if (value instanceof Uint8Array) {
102
+ return Buffer.from(value).toString("base64");
103
+ }
104
+ if (Array.isArray(value)) {
105
+ return value.map((entry) => sanitizeJsonValue(entry));
106
+ }
107
+ if (typeof value === "object") {
108
+ if (hasToHexString(value)) {
109
+ return value.toHexString();
110
+ }
111
+ if (hasToJson(value)) {
112
+ const jsonValue = value.toJSON();
113
+ if (jsonValue !== value) {
114
+ return sanitizeJsonValue(jsonValue);
115
+ }
116
+ }
117
+ const result = {};
118
+ for (const [key, entry] of Object.entries(value)) {
119
+ result[key] = sanitizeJsonValue(entry);
120
+ }
121
+ return result;
122
+ }
123
+ return String(value);
124
+ }
125
+ function normalizeSql(sql) {
126
+ return sql.replace(/^(\s|--[^\r\n]*[\r\n]+|\/\*[\s\S]*?\*\/)*/u, "").trim().replace(/\s+/gu, " ");
127
+ }
128
+ function inferStatementType(sql) {
129
+ const match = normalizeSql(sql).match(/^([a-z]+)/iu);
130
+ const statement = match?.[1];
131
+ return statement ? statement.toUpperCase() : "UNKNOWN";
132
+ }
133
+ function isPotentiallyMutatingSql(sql) {
134
+ const normalized = normalizeSql(sql);
135
+ if (normalized.length === 0) {
136
+ return false;
137
+ }
138
+ const readOnlyStart = /^(select|with|show|explain|values)\b/iu;
139
+ const writePattern = /\b(insert|update|delete|merge|create|alter|drop|truncate|grant|revoke|vacuum|call|copy|do|set|reset|refresh|reindex|cluster|comment|lock)\b/iu;
140
+ if (!readOnlyStart.test(normalized)) {
141
+ return true;
142
+ }
143
+ if (writePattern.test(normalized)) {
144
+ return true;
145
+ }
146
+ return /^select\b[\s\S]*\binto\b/iu.test(normalized);
147
+ }
148
+ function getConnectionSummary(connectionString) {
149
+ try {
150
+ const url = new URL(connectionString);
151
+ const database = url.pathname.replace(/^\/+/u, "") || "postgres";
152
+ return {
153
+ database,
154
+ host: url.host || "localhost"
155
+ };
156
+ } catch {
157
+ return {
158
+ database: "unknown",
159
+ host: "unknown"
160
+ };
161
+ }
162
+ }
163
+ var NodePostgreSqlClient = class {
164
+ constructor(env) {
165
+ this.env = env;
166
+ const clientConfig = {
167
+ connectionString: env.POSTGRESQL_URL
168
+ };
169
+ if (env.POSTGRESQL_SSL) {
170
+ clientConfig.ssl = {
171
+ rejectUnauthorized: false
172
+ };
173
+ }
174
+ const { Client } = require2("pg");
175
+ this.client = new Client(clientConfig);
176
+ this.database = getConnectionSummary(env.POSTGRESQL_URL).database;
177
+ }
178
+ database;
179
+ client;
180
+ connected = false;
181
+ async close() {
182
+ if (!this.connected) {
183
+ return;
184
+ }
185
+ await this.client.end();
186
+ this.connected = false;
187
+ }
188
+ async listTables(input) {
189
+ const result = await this.execute(
190
+ `
191
+ select
192
+ table_schema,
193
+ table_name,
194
+ table_type
195
+ from information_schema.tables
196
+ where ($1::text is null or table_schema = $1)
197
+ and ($2::boolean or table_schema not in ('information_schema', 'pg_catalog'))
198
+ order by table_schema asc, table_name asc
199
+ limit $3
200
+ `,
201
+ [input.schema, input.includeSystemSchemas, input.limit]
202
+ );
203
+ return {
204
+ database: this.database,
205
+ tables: result.rows.map((row) => ({
206
+ schema: readString(row.table_schema),
207
+ name: readString(row.table_name),
208
+ type: readString(row.table_type)
209
+ }))
210
+ };
211
+ }
212
+ async describeTable(input) {
213
+ const columnsResult = await this.execute(
214
+ `
215
+ select
216
+ cols.column_name,
217
+ cols.ordinal_position,
218
+ cols.data_type,
219
+ cols.is_nullable,
220
+ cols.column_default,
221
+ cols.character_maximum_length,
222
+ cols.numeric_precision,
223
+ cols.numeric_scale,
224
+ pgd.description as comment
225
+ from information_schema.columns cols
226
+ left join pg_catalog.pg_statio_all_tables st
227
+ on st.schemaname = cols.table_schema
228
+ and st.relname = cols.table_name
229
+ left join pg_catalog.pg_description pgd
230
+ on pgd.objoid = st.relid
231
+ and pgd.objsubid = cols.ordinal_position
232
+ where cols.table_schema = $1
233
+ and cols.table_name = $2
234
+ order by cols.ordinal_position asc
235
+ `,
236
+ [input.schema, input.table]
237
+ );
238
+ if (columnsResult.rows.length === 0) {
239
+ throw new ValidationError(`Table '${input.schema}.${input.table}' was not found.`);
240
+ }
241
+ const constraintsResult = await this.execute(
242
+ `
243
+ select
244
+ tc.constraint_name,
245
+ tc.constraint_type,
246
+ array_remove(array_agg(kcu.column_name order by kcu.ordinal_position), null) as columns
247
+ from information_schema.table_constraints tc
248
+ left join information_schema.key_column_usage kcu
249
+ on tc.constraint_name = kcu.constraint_name
250
+ and tc.table_schema = kcu.table_schema
251
+ and tc.table_name = kcu.table_name
252
+ where tc.table_schema = $1
253
+ and tc.table_name = $2
254
+ group by tc.constraint_name, tc.constraint_type
255
+ order by tc.constraint_name asc
256
+ `,
257
+ [input.schema, input.table]
258
+ );
259
+ return {
260
+ database: this.database,
261
+ schema: input.schema,
262
+ table: input.table,
263
+ columns: columnsResult.rows.map((row) => ({
264
+ name: readString(row.column_name),
265
+ ordinalPosition: readNumberOrNull(row.ordinal_position) ?? 0,
266
+ dataType: readString(row.data_type),
267
+ isNullable: readBooleanFlag(row.is_nullable),
268
+ defaultValue: row.column_default === null ? null : readString(row.column_default),
269
+ maxLength: readNumberOrNull(row.character_maximum_length),
270
+ numericPrecision: readNumberOrNull(row.numeric_precision),
271
+ numericScale: readNumberOrNull(row.numeric_scale),
272
+ comment: row.comment === null ? null : readString(row.comment)
273
+ })),
274
+ constraints: constraintsResult.rows.map((row) => ({
275
+ name: readString(row.constraint_name),
276
+ type: readString(row.constraint_type),
277
+ columns: readStringArray(row.columns)
278
+ }))
279
+ };
280
+ }
281
+ async runQuery(input) {
282
+ const result = await this.execute(input.sql, input.params);
283
+ return {
284
+ rowCount: result.rowCount,
285
+ fields: result.fields.map((field) => ({
286
+ name: field.name,
287
+ dataType: null
288
+ })),
289
+ rows: result.rows.map((row) => sanitizeJsonValue(row))
290
+ };
291
+ }
292
+ async ensureConnected() {
293
+ if (this.connected) {
294
+ return;
295
+ }
296
+ try {
297
+ await this.client.connect();
298
+ this.connected = true;
299
+ } catch (error) {
300
+ throw new ExternalServiceError("Failed to connect to PostgreSQL.", {
301
+ details: extractErrorMessage(error)
302
+ });
303
+ }
304
+ }
305
+ async execute(text, values = []) {
306
+ await this.ensureConnected();
307
+ try {
308
+ return await this.client.query(text, values);
309
+ } catch (error) {
310
+ throw new ExternalServiceError("PostgreSQL query failed.", {
311
+ details: extractErrorMessage(error)
312
+ });
313
+ }
314
+ }
315
+ };
316
+ var PostgreSqlServer = class extends ToolkitServer {
317
+ constructor(env, client) {
318
+ super(metadata);
319
+ this.env = env;
320
+ this.client = client;
321
+ this.registerTool(
322
+ defineTool({
323
+ name: "list-tables",
324
+ title: "List PostgreSQL tables",
325
+ description: "List tables and views visible to the configured PostgreSQL connection.",
326
+ inputSchema: {
327
+ schema: z.string().min(1).optional(),
328
+ includeSystemSchemas: z.boolean().default(false),
329
+ limit: z.number().int().positive().max(200).default(50)
330
+ },
331
+ outputSchema: {
332
+ database: z.string(),
333
+ schemaFilter: z.string().nullable(),
334
+ tableCount: z.number().int().nonnegative(),
335
+ tables: z.array(
336
+ z.object({
337
+ schema: z.string(),
338
+ name: z.string(),
339
+ type: z.string()
340
+ })
341
+ )
342
+ },
343
+ handler: async ({ includeSystemSchemas, limit, schema }, context) => {
344
+ await context.log("info", "Listing PostgreSQL tables.");
345
+ const result = await this.client.listTables({
346
+ schema: schema ?? null,
347
+ includeSystemSchemas,
348
+ limit
349
+ });
350
+ return {
351
+ database: result.database,
352
+ schemaFilter: schema ?? null,
353
+ tableCount: result.tables.length,
354
+ tables: result.tables
355
+ };
356
+ },
357
+ renderText: (output) => `${output.tableCount} tables found in ${output.database}.`
358
+ })
359
+ );
360
+ this.registerTool(
361
+ defineTool({
362
+ name: "describe-table",
363
+ title: "Describe PostgreSQL table",
364
+ description: "Inspect columns and constraints for a PostgreSQL table.",
365
+ inputSchema: {
366
+ schema: z.string().min(1).default(this.env.POSTGRESQL_SCHEMA),
367
+ table: z.string().min(1)
368
+ },
369
+ outputSchema: {
370
+ database: z.string(),
371
+ schema: z.string(),
372
+ table: z.string(),
373
+ columnCount: z.number().int().nonnegative(),
374
+ columns: z.array(
375
+ z.object({
376
+ name: z.string(),
377
+ ordinalPosition: z.number().int().nonnegative(),
378
+ dataType: z.string(),
379
+ isNullable: z.boolean(),
380
+ defaultValue: z.string().nullable(),
381
+ maxLength: z.number().int().nonnegative().nullable(),
382
+ numericPrecision: z.number().int().nonnegative().nullable(),
383
+ numericScale: z.number().int().nonnegative().nullable(),
384
+ comment: z.string().nullable()
385
+ })
386
+ ),
387
+ constraints: z.array(
388
+ z.object({
389
+ name: z.string(),
390
+ type: z.string(),
391
+ columns: z.array(z.string())
392
+ })
393
+ )
394
+ },
395
+ handler: async ({ schema, table }, context) => {
396
+ await context.log("info", `Describing ${schema}.${table}.`);
397
+ const result = await this.client.describeTable({ schema, table });
398
+ return {
399
+ database: result.database,
400
+ schema: result.schema,
401
+ table: result.table,
402
+ columnCount: result.columns.length,
403
+ columns: result.columns,
404
+ constraints: result.constraints
405
+ };
406
+ },
407
+ renderText: (output) => `${output.schema}.${output.table} has ${output.columnCount} columns.`
408
+ })
409
+ );
410
+ this.registerTool(
411
+ defineTool({
412
+ name: "run-query",
413
+ title: "Run PostgreSQL query",
414
+ description: "Run a PostgreSQL query with a read-only-by-default safety guard.",
415
+ inputSchema: {
416
+ sql: z.string().min(1),
417
+ params: z.array(z.union([z.string(), z.number().finite(), z.boolean(), z.null()])).default([]),
418
+ allowWrite: z.boolean().default(false)
419
+ },
420
+ outputSchema: {
421
+ statementType: z.string(),
422
+ rowCount: z.number().int().nonnegative().nullable(),
423
+ fieldCount: z.number().int().nonnegative(),
424
+ fields: z.array(
425
+ z.object({
426
+ name: z.string(),
427
+ dataType: z.string().nullable()
428
+ })
429
+ ),
430
+ returnedRows: z.number().int().nonnegative(),
431
+ truncated: z.boolean(),
432
+ rows: z.array(jsonObjectSchema)
433
+ },
434
+ handler: async ({ allowWrite, params, sql }, context) => {
435
+ const statementType = inferStatementType(sql);
436
+ const mutating = isPotentiallyMutatingSql(sql);
437
+ if (mutating && (!this.env.POSTGRESQL_ALLOW_WRITES || !allowWrite)) {
438
+ throw new ValidationError(
439
+ "Mutating SQL is blocked by default. Set POSTGRESQL_ALLOW_WRITES=true and pass allowWrite=true to run write statements."
440
+ );
441
+ }
442
+ if (mutating) {
443
+ await context.log("warning", "Executing an opt-in write query against PostgreSQL.");
444
+ } else {
445
+ await context.log("info", `Executing read-only ${statementType} query.`);
446
+ }
447
+ const execution = await this.client.runQuery({ sql, params });
448
+ const rows = execution.rows.slice(0, this.env.POSTGRESQL_MAX_RESULT_ROWS);
449
+ return {
450
+ statementType,
451
+ rowCount: execution.rowCount,
452
+ fieldCount: execution.fields.length,
453
+ fields: execution.fields,
454
+ returnedRows: rows.length,
455
+ truncated: execution.rows.length > rows.length,
456
+ rows
457
+ };
458
+ },
459
+ renderText: (output) => `${output.statementType} returned ${output.returnedRows} row(s).`
460
+ })
461
+ );
462
+ this.registerStaticResource(
463
+ "schema-overview",
464
+ "postgresql://schema-overview",
465
+ {
466
+ title: "PostgreSQL schema overview",
467
+ description: "Connection summary and a snapshot of tables from the default schema.",
468
+ mimeType: "application/json"
469
+ },
470
+ async () => {
471
+ const connection = getConnectionSummary(this.env.POSTGRESQL_URL);
472
+ const tables = await this.client.listTables({
473
+ schema: this.env.POSTGRESQL_SCHEMA,
474
+ includeSystemSchemas: false,
475
+ limit: this.env.POSTGRESQL_RESOURCE_TABLE_LIMIT
476
+ });
477
+ return this.createJsonResource("postgresql://schema-overview", {
478
+ connection,
479
+ defaultSchema: this.env.POSTGRESQL_SCHEMA,
480
+ writeQueriesEnabled: this.env.POSTGRESQL_ALLOW_WRITES,
481
+ tables: tables.tables
482
+ });
483
+ }
484
+ );
485
+ this.registerPrompt(
486
+ "query-review",
487
+ {
488
+ title: "PostgreSQL query review",
489
+ description: "Generate a review checklist for a PostgreSQL query before execution.",
490
+ argsSchema: {
491
+ objective: z.string().min(1),
492
+ sql: z.string().min(1),
493
+ allowWrite: z.boolean().default(false)
494
+ }
495
+ },
496
+ async ({ allowWrite, objective, sql }) => this.createTextPrompt(
497
+ [
498
+ "Review this PostgreSQL query before it is executed.",
499
+ `Objective: ${objective}`,
500
+ `Default schema: ${this.env.POSTGRESQL_SCHEMA}`,
501
+ `Write access enabled: ${this.env.POSTGRESQL_ALLOW_WRITES}`,
502
+ `Caller requested write execution: ${allowWrite}`,
503
+ "Check for:",
504
+ "- unexpected table scans or missing predicates",
505
+ "- whether LIMIT, ORDER BY, or EXPLAIN would improve safety",
506
+ "- lock or mutation risks",
507
+ "- whether the SQL matches the stated objective",
508
+ "",
509
+ "SQL:",
510
+ sql
511
+ ].join("\n")
512
+ )
513
+ );
514
+ }
515
+ async close() {
516
+ await this.client.close?.();
517
+ await super.close();
518
+ }
519
+ };
520
+ function createServer(options = {}) {
521
+ const env = options.env ?? loadEnv(postgresqlEnvShape);
522
+ const client = options.client ?? new NodePostgreSqlClient(env);
523
+ return new PostgreSqlServer(env, client);
524
+ }
525
+ async function main(argv = process.argv.slice(2)) {
526
+ const env = loadEnv(postgresqlEnvShape);
527
+ const runtimeOptions = parseRuntimeOptions(argv);
528
+ await runToolkitServer(
529
+ {
530
+ createServer: () => createServer({ env }),
531
+ serverCard
532
+ },
533
+ runtimeOptions
534
+ );
535
+ }
536
+ function isMainModule() {
537
+ const entryPoint = process.argv[1];
538
+ if (!entryPoint) {
539
+ return false;
540
+ }
541
+ return import.meta.url === pathToFileURL(entryPoint).href;
542
+ }
543
+ if (isMainModule()) {
544
+ void main().catch((error) => {
545
+ const message = error instanceof Error ? error.message : "Unknown startup error.";
546
+ console.error(`Failed to start PostgreSQL MCP server: ${message}`);
547
+ process.exitCode = 1;
548
+ });
549
+ }
550
+ export {
551
+ PostgreSqlServer,
552
+ createServer,
553
+ main,
554
+ metadata,
555
+ serverCard
556
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@universal-mcp-toolkit/server-postgresql",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Schema inspection and safe query tools for PostgreSQL.",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "server-postgresql": "./dist/index.js",
9
+ "umt-postgresql": "./dist/index.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/Markgatcha/universal-mcp-toolkit.git"
14
+ },
15
+ "homepage": "https://github.com/Markgatcha/universal-mcp-toolkit#readme",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ ".well-known"
26
+ ],
27
+ "keywords": [
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "ai",
31
+ "developer-tools",
32
+ "typescript",
33
+ "postgresql",
34
+ "database",
35
+ "sql",
36
+ "schema"
37
+ ],
38
+ "dependencies": {
39
+ "@universal-mcp-toolkit/core": "0.1.0",
40
+ "pg": "^8.20.0",
41
+ "zod": "^4.3.6"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup src/index.ts --format esm --dts --clean",
48
+ "dev": "tsx watch src/index.ts",
49
+ "lint": "tsc --noEmit",
50
+ "typecheck": "tsc --noEmit",
51
+ "test": "vitest run --passWithNoTests",
52
+ "clean": "rimraf dist"
53
+ }
54
+ }