@apono-io/mcp-postgres 0.2.0 → 0.3.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.
- package/README.md +1 -3
- package/dist/__tests__/db.test.d.ts +2 -0
- package/dist/__tests__/db.test.d.ts.map +1 -0
- package/dist/__tests__/db.test.js +65 -0
- package/dist/__tests__/db.test.js.map +1 -0
- package/dist/__tests__/execute.test.d.ts +2 -0
- package/dist/__tests__/execute.test.d.ts.map +1 -0
- package/dist/__tests__/execute.test.js +153 -0
- package/dist/__tests__/execute.test.js.map +1 -0
- package/dist/__tests__/helpers/database.d.ts +10 -0
- package/dist/__tests__/helpers/database.d.ts.map +1 -0
- package/dist/__tests__/helpers/database.js +28 -0
- package/dist/__tests__/helpers/database.js.map +1 -0
- package/dist/__tests__/tools.test.js +17 -1
- package/dist/__tests__/tools.test.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +7 -3
- package/dist/db.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +16 -41
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/dist/__tests__/read-only.test.d.ts +0 -2
- package/dist/__tests__/read-only.test.d.ts.map +0 -1
- package/dist/__tests__/read-only.test.js +0 -65
- package/dist/__tests__/read-only.test.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,14 +6,12 @@ Apono MCP server for PostgreSQL databases with JIT credential support.
|
|
|
6
6
|
|
|
7
7
|
| Tool | Description |
|
|
8
8
|
|------|------------|
|
|
9
|
-
| `
|
|
10
|
-
| `execute` | Execute a SQL statement that modifies data (INSERT, UPDATE, DELETE) |
|
|
9
|
+
| `execute` | Execute any SQL statement, including `SELECT` and `EXPLAIN`. Returns rows when the statement returns rows |
|
|
11
10
|
| `list_schemas` | List all schemas in the database |
|
|
12
11
|
| `list_tables` | List all tables in a schema |
|
|
13
12
|
| `describe_table` | Get column names, types, constraints, and indexes for a table |
|
|
14
13
|
| `list_extensions` | List installed PostgreSQL extensions |
|
|
15
14
|
| `get_table_indexes` | List indexes on a specific table |
|
|
16
|
-
| `explain_query` | Get the execution plan for a SQL query |
|
|
17
15
|
|
|
18
16
|
All tools accept a `database` parameter to specify which database to operate on.
|
|
19
17
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/db.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
function makePool(id) {
|
|
3
|
+
return { id, end: vi.fn(async () => undefined) };
|
|
4
|
+
}
|
|
5
|
+
let created = [];
|
|
6
|
+
const Pool = vi.fn(() => {
|
|
7
|
+
const pool = makePool(created.length);
|
|
8
|
+
created.push(pool);
|
|
9
|
+
return pool;
|
|
10
|
+
});
|
|
11
|
+
const log = vi.fn();
|
|
12
|
+
vi.mock('../logger.js', () => ({ log }));
|
|
13
|
+
vi.mock('pg', () => ({ default: { Pool } }));
|
|
14
|
+
const db = await import('../db.js');
|
|
15
|
+
const getPool = (database) => db.getPool(database);
|
|
16
|
+
const closeAllPools = db.closeAllPools;
|
|
17
|
+
describe('closeAllPools', () => {
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
await closeAllPools();
|
|
20
|
+
created = [];
|
|
21
|
+
Pool.mockClear();
|
|
22
|
+
log.mockClear();
|
|
23
|
+
});
|
|
24
|
+
it('closes every pool and empties the cache', async () => {
|
|
25
|
+
const first = getPool('apono');
|
|
26
|
+
const second = getPool('other');
|
|
27
|
+
await closeAllPools();
|
|
28
|
+
expect(first.end).toHaveBeenCalledOnce();
|
|
29
|
+
expect(second.end).toHaveBeenCalledOnce();
|
|
30
|
+
expect(getPool('apono')).not.toBe(first);
|
|
31
|
+
expect(getPool('other')).not.toBe(second);
|
|
32
|
+
});
|
|
33
|
+
it('closes the other pools when one fails to close', async () => {
|
|
34
|
+
const failing = getPool('apono');
|
|
35
|
+
failing.end.mockRejectedValueOnce(new Error('connection terminated'));
|
|
36
|
+
const second = getPool('other');
|
|
37
|
+
const third = getPool('third');
|
|
38
|
+
await closeAllPools();
|
|
39
|
+
expect(second.end).toHaveBeenCalledOnce();
|
|
40
|
+
expect(third.end).toHaveBeenCalledOnce();
|
|
41
|
+
});
|
|
42
|
+
it('resolves when a pool fails to close', async () => {
|
|
43
|
+
getPool('apono').end.mockRejectedValueOnce(new Error('connection terminated'));
|
|
44
|
+
await expect(closeAllPools()).resolves.toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
it('forgets a pool that failed to close, so the next call builds a new one', async () => {
|
|
47
|
+
const failing = getPool('apono');
|
|
48
|
+
failing.end.mockRejectedValueOnce(new Error('connection terminated'));
|
|
49
|
+
await closeAllPools();
|
|
50
|
+
expect(getPool('apono')).not.toBe(failing);
|
|
51
|
+
});
|
|
52
|
+
it('logs the reason a pool failed to close', async () => {
|
|
53
|
+
const reason = new Error('connection terminated');
|
|
54
|
+
getPool('apono').end.mockRejectedValueOnce(reason);
|
|
55
|
+
await closeAllPools();
|
|
56
|
+
expect(log).toHaveBeenCalledWith('Failed to close pool:', reason);
|
|
57
|
+
});
|
|
58
|
+
it('logs nothing when every pool closes', async () => {
|
|
59
|
+
getPool('apono');
|
|
60
|
+
getPool('other');
|
|
61
|
+
await closeAllPools();
|
|
62
|
+
expect(log).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=db.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.test.js","sourceRoot":"","sources":["../../src/__tests__/db.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/execute.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
import { USER, PASSWORD, connection, useTestCredentials, withMaintenance } from './helpers/database.js';
|
|
4
|
+
const DATABASE = 'mcp_postgres_execute_test';
|
|
5
|
+
describe('execute', () => {
|
|
6
|
+
let admin;
|
|
7
|
+
let tools;
|
|
8
|
+
let closeAllPools;
|
|
9
|
+
async function rowCount() {
|
|
10
|
+
const result = await admin.query('SELECT COUNT(*)::int AS n FROM users');
|
|
11
|
+
return result.rows[0].n;
|
|
12
|
+
}
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
await withMaintenance(async (client) => {
|
|
15
|
+
await client.query(`DROP DATABASE IF EXISTS ${DATABASE}`);
|
|
16
|
+
await client.query(`CREATE DATABASE ${DATABASE}`);
|
|
17
|
+
});
|
|
18
|
+
admin = new pg.Pool(connection(DATABASE));
|
|
19
|
+
useTestCredentials();
|
|
20
|
+
const toolsModule = await import('../tools.js');
|
|
21
|
+
closeAllPools = (await import('../db.js')).closeAllPools;
|
|
22
|
+
tools = new Map();
|
|
23
|
+
toolsModule.registerTools({
|
|
24
|
+
tool: (name, _d, _s, handler) => {
|
|
25
|
+
tools.set(name, handler);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
afterAll(async () => {
|
|
30
|
+
await closeAllPools?.();
|
|
31
|
+
await admin?.end();
|
|
32
|
+
await withMaintenance(async (client) => {
|
|
33
|
+
await client.query(`DROP DATABASE IF EXISTS ${DATABASE}`);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
beforeEach(async () => {
|
|
37
|
+
await admin.query('DROP TABLE IF EXISTS users');
|
|
38
|
+
await admin.query('CREATE TABLE users (id INT PRIMARY KEY)');
|
|
39
|
+
await admin.query('INSERT INTO users VALUES (1)');
|
|
40
|
+
});
|
|
41
|
+
it('leaves execute outside the read-only transaction', async () => {
|
|
42
|
+
const result = await tools.get('execute')({ database: DATABASE, sql: 'DELETE FROM users' });
|
|
43
|
+
expect(result.isError).toBeUndefined();
|
|
44
|
+
expect(await rowCount()).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
it('returns rows for a row-returning statement', async () => {
|
|
47
|
+
const result = await tools.get('execute')({ database: DATABASE, sql: 'SELECT id FROM users' });
|
|
48
|
+
expect(result.isError).toBeUndefined();
|
|
49
|
+
expect(result.content[0].text).not.toContain('Rows affected');
|
|
50
|
+
expect(result.content[0].text).toContain('1');
|
|
51
|
+
});
|
|
52
|
+
it('reports the permission hint for a SELECT that matches nothing', async () => {
|
|
53
|
+
const result = await tools.get('execute')({ database: DATABASE, sql: 'SELECT id FROM users WHERE id = 999' });
|
|
54
|
+
expect(result.isError).toBeUndefined();
|
|
55
|
+
expect(result.content[0].text).toContain('permissions');
|
|
56
|
+
});
|
|
57
|
+
it('returns the plan for an EXPLAIN, as text rather than a row per line', async () => {
|
|
58
|
+
const result = await tools.get('execute')({ database: DATABASE, sql: 'EXPLAIN SELECT * FROM users' });
|
|
59
|
+
expect(result.isError).toBeUndefined();
|
|
60
|
+
expect(result.content[0].text).toContain('Scan');
|
|
61
|
+
expect(result.content[0].text).not.toContain('QUERY PLAN');
|
|
62
|
+
});
|
|
63
|
+
it('reports an empty FETCH as an empty result, not as rows affected', async () => {
|
|
64
|
+
const result = await tools.get('execute')({
|
|
65
|
+
database: DATABASE,
|
|
66
|
+
sql: 'BEGIN; DECLARE c CURSOR FOR SELECT * FROM users WHERE false; FETCH ALL FROM c; COMMIT',
|
|
67
|
+
});
|
|
68
|
+
expect(result.isError).toBeUndefined();
|
|
69
|
+
expect(result.content[0].text).toContain('permissions');
|
|
70
|
+
});
|
|
71
|
+
it('reports a CREATE TABLE AS as a write, though postgres tags it SELECT', async () => {
|
|
72
|
+
const result = await tools.get('execute')({
|
|
73
|
+
database: DATABASE,
|
|
74
|
+
sql: 'CREATE TABLE users_copy AS SELECT * FROM users',
|
|
75
|
+
});
|
|
76
|
+
expect(result.isError).toBeUndefined();
|
|
77
|
+
expect(result.content[0].text).not.toContain('permissions');
|
|
78
|
+
expect(result.content[0].text).toContain('Rows affected: 1');
|
|
79
|
+
});
|
|
80
|
+
it('reports a SELECT INTO as a write, though postgres tags it SELECT', async () => {
|
|
81
|
+
const result = await tools.get('execute')({
|
|
82
|
+
database: DATABASE,
|
|
83
|
+
sql: 'SELECT * INTO users_into FROM users',
|
|
84
|
+
});
|
|
85
|
+
expect(result.isError).toBeUndefined();
|
|
86
|
+
expect(result.content[0].text).not.toContain('permissions');
|
|
87
|
+
expect(result.content[0].text).toContain('Rows affected: 1');
|
|
88
|
+
});
|
|
89
|
+
it('reports the affected count for a write', async () => {
|
|
90
|
+
const result = await tools.get('execute')({ database: DATABASE, sql: 'DELETE FROM users' });
|
|
91
|
+
expect(result.content[0].text).toContain('Rows affected: 1');
|
|
92
|
+
});
|
|
93
|
+
it('reports one result per statement for a multi-statement string', async () => {
|
|
94
|
+
const result = await tools.get('execute')({
|
|
95
|
+
database: DATABASE,
|
|
96
|
+
sql: 'SELECT count(*) AS n FROM users; DELETE FROM users',
|
|
97
|
+
});
|
|
98
|
+
expect(result.isError).toBeUndefined();
|
|
99
|
+
expect(result.content[0].text).toContain('statement 1');
|
|
100
|
+
expect(result.content[0].text).toContain('statement 2');
|
|
101
|
+
expect(await rowCount()).toBe(0);
|
|
102
|
+
});
|
|
103
|
+
describe('what a READ_ONLY grant reaches', () => {
|
|
104
|
+
const ROLE = 'mcp_read_only_grant_test';
|
|
105
|
+
const SEQUENCE = 'grant_test_seq';
|
|
106
|
+
beforeAll(async () => {
|
|
107
|
+
await admin.query(`CREATE SEQUENCE ${SEQUENCE}`);
|
|
108
|
+
await admin.query(`SELECT nextval('${SEQUENCE}')`);
|
|
109
|
+
await admin.query(`DROP ROLE IF EXISTS ${ROLE}`);
|
|
110
|
+
await admin.query(`CREATE ROLE ${ROLE} LOGIN PASSWORD '${PASSWORD}'`);
|
|
111
|
+
await admin.query(`GRANT CONNECT ON DATABASE ${DATABASE} TO ${ROLE}`);
|
|
112
|
+
await admin.query(`GRANT USAGE ON SCHEMA public TO ${ROLE}`);
|
|
113
|
+
await admin.query(`GRANT SELECT, USAGE ON SEQUENCE ${SEQUENCE} TO ${ROLE}`);
|
|
114
|
+
await closeAllPools();
|
|
115
|
+
process.env.POSTGRES_USER = ROLE;
|
|
116
|
+
});
|
|
117
|
+
afterAll(async () => {
|
|
118
|
+
await closeAllPools();
|
|
119
|
+
process.env.POSTGRES_USER = USER;
|
|
120
|
+
await admin.query(`DROP OWNED BY ${ROLE}`).catch(() => undefined);
|
|
121
|
+
await admin.query(`DROP ROLE IF EXISTS ${ROLE}`).catch(() => undefined);
|
|
122
|
+
await admin.query(`DROP SEQUENCE IF EXISTS ${SEQUENCE}`);
|
|
123
|
+
});
|
|
124
|
+
beforeEach(async () => {
|
|
125
|
+
await admin.query(`GRANT SELECT ON users TO ${ROLE}`);
|
|
126
|
+
});
|
|
127
|
+
it.each([
|
|
128
|
+
['INSERT INTO users VALUES (2)'],
|
|
129
|
+
['UPDATE users SET id = 2'],
|
|
130
|
+
['DELETE FROM users'],
|
|
131
|
+
['CREATE TABLE staging (id INT)'],
|
|
132
|
+
['DROP TABLE users'],
|
|
133
|
+
['TRUNCATE users'],
|
|
134
|
+
])('refuses %s, because the role holds no table write privilege', async (sql) => {
|
|
135
|
+
const result = await tools.get('execute')({ database: DATABASE, sql });
|
|
136
|
+
expect(result.isError, `"${sql}" was not refused`).toBe(true);
|
|
137
|
+
expect(await rowCount()).toBe(1);
|
|
138
|
+
});
|
|
139
|
+
it('still serves a read', async () => {
|
|
140
|
+
const result = await tools.get('execute')({ database: DATABASE, sql: 'SELECT id FROM users' });
|
|
141
|
+
expect(result.isError).toBeUndefined();
|
|
142
|
+
expect(result.content[0].text).toContain('1');
|
|
143
|
+
});
|
|
144
|
+
it('reaches nextval, which USAGE allows and the catalog grades a write', async () => {
|
|
145
|
+
const before = await admin.query(`SELECT last_value FROM ${SEQUENCE}`);
|
|
146
|
+
const result = await tools.get('execute')({ database: DATABASE, sql: `SELECT nextval('${SEQUENCE}')` });
|
|
147
|
+
expect(result.isError).toBeUndefined();
|
|
148
|
+
const after = await admin.query(`SELECT last_value FROM ${SEQUENCE}`);
|
|
149
|
+
expect(Number(after.rows[0].last_value)).toBeGreaterThan(Number(before.rows[0].last_value));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
//# sourceMappingURL=execute.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.test.js","sourceRoot":"","sources":["../../src/__tests__/execute.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
export declare const HOST: string;
|
|
3
|
+
export declare const PORT: number;
|
|
4
|
+
export declare const USER: string;
|
|
5
|
+
export declare const PASSWORD: string;
|
|
6
|
+
export declare const MAINTENANCE_DB = "postgres";
|
|
7
|
+
export declare function connection(database: string): pg.ClientConfig;
|
|
8
|
+
export declare function withMaintenance(run: (client: pg.Client) => Promise<void>): Promise<void>;
|
|
9
|
+
export declare function useTestCredentials(): void;
|
|
10
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,eAAO,MAAM,IAAI,QAAgD,CAAC;AAClE,eAAO,MAAM,IAAI,QAAiD,CAAC;AACnE,eAAO,MAAM,IAAI,QAA+C,CAAC;AACjE,eAAO,MAAM,QAAQ,QAAmD,CAAC;AACzE,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,YAAY,CAE5D;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ9F;AAGD,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
export const HOST = process.env.POSTGRES_TEST_HOST ?? '127.0.0.1';
|
|
3
|
+
export const PORT = Number(process.env.POSTGRES_TEST_PORT ?? 5432);
|
|
4
|
+
export const USER = process.env.POSTGRES_TEST_USER ?? 'postgres';
|
|
5
|
+
export const PASSWORD = process.env.POSTGRES_TEST_PASSWORD ?? 'postgres';
|
|
6
|
+
export const MAINTENANCE_DB = 'postgres';
|
|
7
|
+
export function connection(database) {
|
|
8
|
+
return { host: HOST, port: PORT, user: USER, password: PASSWORD, database, ssl: false };
|
|
9
|
+
}
|
|
10
|
+
export async function withMaintenance(run) {
|
|
11
|
+
const client = new pg.Client(connection(MAINTENANCE_DB));
|
|
12
|
+
await client.connect();
|
|
13
|
+
try {
|
|
14
|
+
await run(client);
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
await client.end();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// The server builds its pools from these, so they are set before it is imported.
|
|
21
|
+
export function useTestCredentials() {
|
|
22
|
+
process.env.POSTGRES_HOST = HOST;
|
|
23
|
+
process.env.POSTGRES_PORT = String(PORT);
|
|
24
|
+
process.env.POSTGRES_USER = USER;
|
|
25
|
+
process.env.POSTGRES_PASSWORD = PASSWORD;
|
|
26
|
+
process.env.PGSSLMODE = 'disable';
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../../src/__tests__/helpers/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,MAAM,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,WAAW,CAAC;AAClE,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,UAAU,CAAC;AACjE,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,UAAU,CAAC;AACzE,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AAEzC,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAC1F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAyC;IAC7E,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;IACzD,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IACrB,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,QAAQ,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;AACpC,CAAC"}
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { registerTools } from '../tools.js';
|
|
4
|
+
function registeredToolNames() {
|
|
5
|
+
const names = [];
|
|
6
|
+
registerTools({ tool: (name) => names.push(name) });
|
|
7
|
+
return names.sort();
|
|
8
|
+
}
|
|
4
9
|
describe('registerTools', () => {
|
|
5
|
-
it('registers all
|
|
10
|
+
it('registers all 7 postgres tools without error', () => {
|
|
6
11
|
const server = new McpServer({ name: 'test', version: '0.0.1' });
|
|
7
12
|
expect(() => registerTools(server)).not.toThrow();
|
|
8
13
|
});
|
|
14
|
+
it('exposes execute as the only SQL tool', () => {
|
|
15
|
+
expect(registeredToolNames(), 'a SQL tool graded `read` in the managed MCP catalog runs with no approval (DVL-12108)').toEqual([
|
|
16
|
+
'describe_table',
|
|
17
|
+
'execute',
|
|
18
|
+
'get_table_indexes',
|
|
19
|
+
'list_databases',
|
|
20
|
+
'list_extensions',
|
|
21
|
+
'list_schemas',
|
|
22
|
+
'list_tables',
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
9
25
|
});
|
|
10
26
|
//# sourceMappingURL=tools.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.test.js","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"tools.test.js","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,SAAS,mBAAmB;IAC1B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,IAAI,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAW,CAAC,CAAC;IACrE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CACJ,mBAAmB,EAAE,EACrB,uFAAuF,CACxF,CAAC,OAAO,CAAC;YACR,gBAAgB;YAChB,SAAS;YACT,mBAAmB;YACnB,gBAAgB;YAChB,iBAAiB;YACjB,cAAc;YACd,aAAa;SACd,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAiBpB,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,CAOjD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAOnD"}
|
package/dist/db.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pg from 'pg';
|
|
2
|
+
import { log } from './logger.js';
|
|
2
3
|
const pools = new Map();
|
|
3
4
|
function getPoolConfig() {
|
|
4
5
|
return {
|
|
@@ -20,9 +21,12 @@ export function getPool(database) {
|
|
|
20
21
|
return pool;
|
|
21
22
|
}
|
|
22
23
|
export async function closeAllPools() {
|
|
23
|
-
|
|
24
|
-
await pool.end();
|
|
25
|
-
}
|
|
24
|
+
const open = [...pools.values()];
|
|
26
25
|
pools.clear();
|
|
26
|
+
const results = await Promise.allSettled(open.map((pool) => pool.end()));
|
|
27
|
+
for (const result of results) {
|
|
28
|
+
if (result.status === 'rejected')
|
|
29
|
+
log('Failed to close pool:', result.reason);
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmB,CAAC;AAEzC,SAAS,aAAa;IACpB,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa;QAC/B,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI;QAChF,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa;QAC/B,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;QACvC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE;QAChF,GAAG,EAAE,CAAC;QACN,iBAAiB,EAAE,MAAM;KAC1B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,IAAI,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,aAAa,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC3D,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACzE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU;YAAE,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAChF,CAAC;AACH,CAAC"}
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA0CpE,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwJrD"}
|
package/dist/tools.js
CHANGED
|
@@ -15,23 +15,22 @@ function formatRows(rows, emptyHint) {
|
|
|
15
15
|
}
|
|
16
16
|
return JSON.stringify(rows, null, 2);
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const result = await client.query(sql);
|
|
25
|
-
await client.query('COMMIT');
|
|
26
|
-
return result.rows;
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
30
|
-
throw err;
|
|
18
|
+
function formatPlan(rows) {
|
|
19
|
+
return rows.map((row) => String(row['QUERY PLAN'])).join('\n');
|
|
20
|
+
}
|
|
21
|
+
function formatStatementResult(result) {
|
|
22
|
+
if (result.command === 'EXPLAIN' && result.rows.length > 0) {
|
|
23
|
+
return formatPlan(result.rows);
|
|
31
24
|
}
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
if (result.fields.length > 0) {
|
|
26
|
+
return formatRows(result.rows);
|
|
34
27
|
}
|
|
28
|
+
return `Statement executed successfully. Rows affected: ${result.rowCount ?? 0}`;
|
|
29
|
+
}
|
|
30
|
+
function formatExecuteResult(result) {
|
|
31
|
+
if (!Array.isArray(result))
|
|
32
|
+
return formatStatementResult(result);
|
|
33
|
+
return result.map((one, index) => `-- statement ${index + 1}\n${formatStatementResult(one)}`).join('\n\n');
|
|
35
34
|
}
|
|
36
35
|
export function registerTools(server) {
|
|
37
36
|
server.tool('list_databases', 'List all databases on the PostgreSQL server.', {}, async () => {
|
|
@@ -48,21 +47,12 @@ export function registerTools(server) {
|
|
|
48
47
|
return errorContent('list_databases', err);
|
|
49
48
|
}
|
|
50
49
|
});
|
|
51
|
-
server.tool('
|
|
52
|
-
log(`Tool called: query on ${database}`);
|
|
53
|
-
try {
|
|
54
|
-
return textContent(formatRows(await queryReadOnly(database, sql)));
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
return errorContent('query', err);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
server.tool('execute', 'Execute a SQL statement that modifies data (INSERT, UPDATE, DELETE).', { database: z.string().describe('The database to execute against'), sql: z.string().describe('The SQL statement to execute') }, async ({ database, sql }) => {
|
|
50
|
+
server.tool('execute', 'Execute a SQL statement. Use this for every statement, including SELECT.', { database: z.string().describe('The database to execute against'), sql: z.string().describe('The SQL statement to execute') }, async ({ database, sql }) => {
|
|
61
51
|
log(`Tool called: execute on ${database}`);
|
|
62
52
|
try {
|
|
63
53
|
const pool = getPool(database);
|
|
64
54
|
const result = await pool.query(sql);
|
|
65
|
-
return textContent(
|
|
55
|
+
return textContent(formatExecuteResult(result));
|
|
66
56
|
}
|
|
67
57
|
catch (err) {
|
|
68
58
|
return errorContent('execute', err);
|
|
@@ -144,20 +134,5 @@ export function registerTools(server) {
|
|
|
144
134
|
return errorContent('get_table_indexes', err);
|
|
145
135
|
}
|
|
146
136
|
});
|
|
147
|
-
server.tool('explain_query', 'Get the execution plan for a SQL query.', {
|
|
148
|
-
database: z.string().describe('The database to explain the query against'),
|
|
149
|
-
sql: z.string().describe('The SQL query to explain'),
|
|
150
|
-
analyze: z.boolean().default(false).describe('Whether to actually execute the query for real timing data. Writes stay rejected because the plan runs in a read-only transaction.'),
|
|
151
|
-
}, async ({ database, sql, analyze }) => {
|
|
152
|
-
log(`Tool called: explain_query on ${database}`);
|
|
153
|
-
try {
|
|
154
|
-
const explainSql = analyze ? `EXPLAIN ANALYZE ${sql}` : `EXPLAIN ${sql}`;
|
|
155
|
-
const rows = await queryReadOnly(database, explainSql);
|
|
156
|
-
return textContent(rows.map((row) => row['QUERY PLAN']).join('\n'));
|
|
157
|
-
}
|
|
158
|
-
catch (err) {
|
|
159
|
-
return errorContent('explain_query', err);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
137
|
}
|
|
163
138
|
//# sourceMappingURL=tools.js.map
|
package/dist/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"read-only.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/read-only.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
const client = {
|
|
3
|
-
query: vi.fn(),
|
|
4
|
-
release: vi.fn(),
|
|
5
|
-
};
|
|
6
|
-
const pool = {
|
|
7
|
-
connect: vi.fn(async () => client),
|
|
8
|
-
query: vi.fn(),
|
|
9
|
-
};
|
|
10
|
-
vi.mock('../db.js', () => ({ getPool: () => pool }));
|
|
11
|
-
const { registerTools } = await import('../tools.js');
|
|
12
|
-
function collectTools() {
|
|
13
|
-
const handlers = new Map();
|
|
14
|
-
registerTools({
|
|
15
|
-
tool: (name, _description, _schema, handler) => {
|
|
16
|
-
handlers.set(name, handler);
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
return handlers;
|
|
20
|
-
}
|
|
21
|
-
describe('read-only transaction', () => {
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
client.query.mockReset();
|
|
24
|
-
client.release.mockReset();
|
|
25
|
-
pool.connect.mockClear();
|
|
26
|
-
});
|
|
27
|
-
it('wraps query in a read-only transaction and commits', async () => {
|
|
28
|
-
client.query.mockResolvedValue({ rows: [{ id: 1 }] });
|
|
29
|
-
const result = await collectTools().get('query')({ database: 'apono', sql: 'SELECT 1' });
|
|
30
|
-
const issued = client.query.mock.calls.map((call) => call[0]);
|
|
31
|
-
expect(issued[0]).toBe('BEGIN TRANSACTION READ ONLY');
|
|
32
|
-
expect(issued).toContain('SELECT 1');
|
|
33
|
-
expect(issued).toContain('COMMIT');
|
|
34
|
-
expect(result.isError).toBeUndefined();
|
|
35
|
-
expect(client.release).toHaveBeenCalledOnce();
|
|
36
|
-
});
|
|
37
|
-
it('surfaces the database rejection when a write is attempted and rolls back', async () => {
|
|
38
|
-
client.query.mockImplementation(async (sql) => {
|
|
39
|
-
if (sql === 'BEGIN TRANSACTION READ ONLY')
|
|
40
|
-
return { rows: [] };
|
|
41
|
-
if (sql === 'ROLLBACK')
|
|
42
|
-
return { rows: [] };
|
|
43
|
-
throw new Error('cannot execute DELETE in a read-only transaction');
|
|
44
|
-
});
|
|
45
|
-
const result = await collectTools().get('query')({ database: 'apono', sql: 'DELETE FROM users' });
|
|
46
|
-
expect(result.isError).toBe(true);
|
|
47
|
-
expect(result.content[0].text).toContain('read-only transaction');
|
|
48
|
-
expect(client.query.mock.calls.map((call) => call[0])).toContain('ROLLBACK');
|
|
49
|
-
expect(client.release).toHaveBeenCalledOnce();
|
|
50
|
-
});
|
|
51
|
-
it('runs explain_query with analyze inside the read-only transaction', async () => {
|
|
52
|
-
client.query.mockResolvedValue({ rows: [{ 'QUERY PLAN': 'Seq Scan on users' }] });
|
|
53
|
-
await collectTools().get('explain_query')({ database: 'apono', sql: 'SELECT 1', analyze: true });
|
|
54
|
-
const issued = client.query.mock.calls.map((call) => call[0]);
|
|
55
|
-
expect(issued[0]).toBe('BEGIN TRANSACTION READ ONLY');
|
|
56
|
-
expect(issued).toContain('EXPLAIN ANALYZE SELECT 1');
|
|
57
|
-
});
|
|
58
|
-
it('leaves execute outside the read-only transaction', async () => {
|
|
59
|
-
pool.query.mockResolvedValue({ rowCount: 1 });
|
|
60
|
-
await collectTools().get('execute')({ database: 'apono', sql: 'DELETE FROM users' });
|
|
61
|
-
expect(pool.connect).not.toHaveBeenCalled();
|
|
62
|
-
expect(pool.query).toHaveBeenCalledWith('DELETE FROM users');
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
//# sourceMappingURL=read-only.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"read-only.test.js","sourceRoot":"","sources":["../../src/__tests__/read-only.test.ts"],"names":[],"mappings":"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"}
|