@apono-io/mcp-mysql 0.2.0 → 0.4.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 CHANGED
@@ -6,16 +6,43 @@ Apono MCP server for MySQL databases with JIT credential support.
6
6
 
7
7
  | Tool | Description |
8
8
  |------|------------|
9
- | `query` | Execute a read-only SQL SELECT query and return results |
10
- | `execute` | Execute a SQL statement that modifies data (INSERT, UPDATE, DELETE) |
9
+ | `execute` | Execute a SQL statement. Returns rows for a statement that produces a result set (`SELECT`, `SHOW`, `DESCRIBE`, `EXPLAIN`, ...), and the affected row count for one that changes data or schema |
11
10
  | `list_databases` | List all databases on the server |
12
11
  | `list_tables` | List all tables in a database |
13
12
  | `describe_table` | Get column names, types, constraints, and indexes for a table |
14
13
  | `get_table_indexes` | List indexes on a specific table |
15
- | `explain_query` | Get the execution plan for a SQL query |
16
14
 
17
15
  All tools (except `list_databases`) accept a `database` parameter.
18
16
 
17
+ ## How SQL is authorized
18
+
19
+ `execute` is the only tool that runs caller-supplied SQL. The server does not
20
+ classify that SQL itself. The Apono Agentic Gateway reads the `sql` argument
21
+ and matches it against the keyword rules declared for the tool in
22
+ `managed-mcp-configs/configs/mysql.json`, then applies the policy for the
23
+ resulting category (`read`, `create`, `update`, `delete` or `admin`). Rules are
24
+ evaluated from the most restrictive category to the least, so the first match
25
+ decides. A statement matching no rule takes the declared default, `update`.
26
+
27
+ The rules match the SQL text, so a read whose text contains a write keyword, as in
28
+ `WHERE op = 'delete'`, grades at that keyword's category. It errs toward asking for
29
+ approval rather than allowing.
30
+
31
+ Earlier versions also exposed `query` and `explain_query`, graded `read` and so
32
+ runnable with no approval, which relied on the server to enforce read-only.
33
+ That enforcement was not sound: MySQL commits any open transaction by itself
34
+ before DDL, so `START TRANSACTION READ ONLY` did not stop `CREATE`, `ALTER`,
35
+ `DROP`, `TRUNCATE` or `RENAME TABLE`. Both tools were removed in 0.3.0
36
+ (DVL-12107) so that all SQL is graded through one mechanism. Send the same SQL
37
+ to `execute` instead.
38
+
39
+ The `list_*` and `describe_table` tools stay safe to grade `read` because they
40
+ run fixed `information_schema` queries with parameter placeholders; a caller
41
+ supplies only a database or table name, never the statement.
42
+
43
+ A call carries one statement. `multipleStatements` is off by default in the
44
+ driver, so a call holding several is rejected.
45
+
19
46
  ## Environment Variables
20
47
 
21
48
  | Variable | Description |
@@ -24,3 +51,16 @@ All tools (except `list_databases`) accept a `database` parameter.
24
51
  | `MYSQL_PORT` | MySQL server port (default: 3306) |
25
52
  | `MYSQL_USER` | Database username |
26
53
  | `MYSQL_PASSWORD` | Database password |
54
+ | `MYSQL_MAX_EXECUTION_TIME_MS` | How long a `SELECT` may run, in milliseconds (default: 600000, `0` keeps the server's own setting) |
55
+
56
+ ## How long a statement may run
57
+
58
+ Every `SELECT` runs with `max_execution_time` applied, so one that outlives its
59
+ client still ends at the server rather than running to completion. A pooled
60
+ connection keeps whatever a caller `SET` on it, so the limit is reapplied on each
61
+ checkout.
62
+
63
+ MySQL has no general statement timeout. `max_execution_time` is the nearest
64
+ equivalent and covers read-only `SELECT` only, so a long write is not bounded by it.
65
+ A caller can also raise it for the duration of its own statement.
66
+
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=db.test.d.ts.map
@@ -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, on: vi.fn(), end: vi.fn(async () => undefined) };
4
+ }
5
+ let created = [];
6
+ const createPool = 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('mysql2/promise', () => ({ default: { createPool } }));
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
+ createPool.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 reset'));
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 reset'));
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 reset'));
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 reset');
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=execute.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/execute.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
2
+ import { connect, useTestCredentials } from './helpers/database.js';
3
+ const DATABASE = 'mcp_mysql_execute_test';
4
+ describe('execute', () => {
5
+ let admin;
6
+ let tools;
7
+ let closeAllPools;
8
+ async function rowCount() {
9
+ const [rows] = await admin.query('SELECT COUNT(*) AS n FROM users');
10
+ return Number(rows[0].n);
11
+ }
12
+ beforeAll(async () => {
13
+ admin = await connect();
14
+ await admin.query(`DROP DATABASE IF EXISTS \`${DATABASE}\``);
15
+ await admin.query(`CREATE DATABASE \`${DATABASE}\``);
16
+ await admin.changeUser({ database: DATABASE });
17
+ useTestCredentials();
18
+ const toolsModule = await import('../tools.js');
19
+ closeAllPools = (await import('../db.js')).closeAllPools;
20
+ tools = new Map();
21
+ toolsModule.registerTools({
22
+ tool: (name, _d, _s, handler) => {
23
+ tools.set(name, handler);
24
+ },
25
+ });
26
+ });
27
+ afterAll(async () => {
28
+ await closeAllPools?.();
29
+ await admin?.query(`DROP DATABASE IF EXISTS \`${DATABASE}\``);
30
+ await admin?.end();
31
+ });
32
+ beforeEach(async () => {
33
+ await admin.query('DROP TABLE IF EXISTS users');
34
+ await admin.query('CREATE TABLE users (id INT PRIMARY KEY)');
35
+ await admin.query('INSERT INTO users VALUES (1)');
36
+ });
37
+ it('leaves execute outside the read-only transaction', async () => {
38
+ const result = await tools.get('execute')({ database: DATABASE, sql: 'DELETE FROM users' });
39
+ expect(result.isError).toBeUndefined();
40
+ expect(await rowCount()).toBe(0);
41
+ });
42
+ it('returns the rows of a statement that produces a result set', async () => {
43
+ const result = await tools.get('execute')({ database: DATABASE, sql: 'SELECT id FROM users' });
44
+ expect(result.isError).toBeUndefined();
45
+ expect(result.content[0].text).not.toContain('Rows affected');
46
+ expect(result.content[0].text).toContain('1');
47
+ });
48
+ it('reports the affected row count of a statement that changes data', async () => {
49
+ const result = await tools.get('execute')({ database: DATABASE, sql: 'DELETE FROM users' });
50
+ expect(result.content[0].text).toContain('Rows affected: 1');
51
+ });
52
+ });
53
+ //# 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,8 @@
1
+ import mysql from 'mysql2/promise';
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 function connect(): Promise<mysql.Connection>;
7
+ export declare function useTestCredentials(): void;
8
+ //# 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,KAAK,MAAM,gBAAgB,CAAC;AAEnC,eAAO,MAAM,IAAI,QAA6C,CAAC;AAC/D,eAAO,MAAM,IAAI,QAA8C,CAAC;AAChE,eAAO,MAAM,IAAI,QAAwC,CAAC;AAC1D,eAAO,MAAM,QAAQ,QAAkD,CAAC;AAExE,wBAAgB,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAEnD;AAGD,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC"}
@@ -0,0 +1,16 @@
1
+ import mysql from 'mysql2/promise';
2
+ export const HOST = process.env.MYSQL_TEST_HOST ?? '127.0.0.1';
3
+ export const PORT = Number(process.env.MYSQL_TEST_PORT ?? 3306);
4
+ export const USER = process.env.MYSQL_TEST_USER ?? 'root';
5
+ export const PASSWORD = process.env.MYSQL_TEST_PASSWORD ?? 'apono-test';
6
+ export function connect() {
7
+ return mysql.createConnection({ host: HOST, port: PORT, user: USER, password: PASSWORD });
8
+ }
9
+ // The server builds its pools from these, so they are set before it is imported.
10
+ export function useTestCredentials() {
11
+ process.env.MYSQL_HOST = HOST;
12
+ process.env.MYSQL_PORT = String(PORT);
13
+ process.env.MYSQL_USER = USER;
14
+ process.env.MYSQL_PASSWORD = PASSWORD;
15
+ }
16
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../../src/__tests__/helpers/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,gBAAgB,CAAC;AAEnC,MAAM,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,WAAW,CAAC;AAC/D,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC;AAChE,MAAM,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,MAAM,CAAC;AAC1D,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,YAAY,CAAC;AAExE,MAAM,UAAU,OAAO;IACrB,OAAO,KAAK,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC5F,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,QAAQ,CAAC;AACxC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=timeout.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeout.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/timeout.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
2
+ import { connect, useTestCredentials } from './helpers/database.js';
3
+ const DATABASE = 'mcp_mysql_timeout_test';
4
+ describe('execution time limit', () => {
5
+ let admin;
6
+ let tools;
7
+ let closeAllPools;
8
+ async function execute(sql) {
9
+ return tools.get('execute')({ database: DATABASE, sql });
10
+ }
11
+ async function withLimit(value, run) {
12
+ if (value === undefined)
13
+ delete process.env.MYSQL_MAX_EXECUTION_TIME_MS;
14
+ else
15
+ process.env.MYSQL_MAX_EXECUTION_TIME_MS = value;
16
+ await closeAllPools();
17
+ try {
18
+ await run();
19
+ }
20
+ finally {
21
+ delete process.env.MYSQL_MAX_EXECUTION_TIME_MS;
22
+ await closeAllPools();
23
+ }
24
+ }
25
+ beforeAll(async () => {
26
+ admin = await connect();
27
+ await admin.query(`DROP DATABASE IF EXISTS \`${DATABASE}\``);
28
+ await admin.query(`CREATE DATABASE \`${DATABASE}\``);
29
+ useTestCredentials();
30
+ const toolsModule = await import('../tools.js');
31
+ closeAllPools = (await import('../db.js')).closeAllPools;
32
+ tools = new Map();
33
+ toolsModule.registerTools({
34
+ tool: (name, _d, _s, handler) => {
35
+ tools.set(name, handler);
36
+ },
37
+ });
38
+ });
39
+ afterEach(async () => {
40
+ await closeAllPools?.();
41
+ });
42
+ afterAll(async () => {
43
+ await closeAllPools?.();
44
+ await admin?.query(`DROP DATABASE IF EXISTS \`${DATABASE}\``);
45
+ await admin?.end();
46
+ });
47
+ it('carries an execution time limit by default', async () => {
48
+ const result = await execute('SELECT @@max_execution_time AS limit_ms');
49
+ expect(result.isError).toBeUndefined();
50
+ expect(result.content[0].text).toContain('600000');
51
+ });
52
+ it('lets the server abort a SELECT that outruns the limit', async () => {
53
+ await withLimit('300', async () => {
54
+ const result = await execute('SELECT COUNT(*) FROM information_schema.columns a, information_schema.columns b, information_schema.columns c');
55
+ expect(result.isError).toBe(true);
56
+ expect(result.content[0].text).toMatch(/exceeded|timeout/i);
57
+ });
58
+ });
59
+ it('cuts a sleeping SELECT short at the limit', async () => {
60
+ await withLimit('300', async () => {
61
+ const result = await execute('SELECT SLEEP(5) AS interrupted');
62
+ expect(result.content[0].text).toContain('1');
63
+ });
64
+ });
65
+ it('aborts the SELECT without the client giving up first', async () => {
66
+ await withLimit('300', async () => {
67
+ const started = Date.now();
68
+ await execute('SELECT SLEEP(5)');
69
+ expect(Date.now() - started).toBeLessThan(4_000);
70
+ });
71
+ });
72
+ it('takes a limit from the environment', async () => {
73
+ await withLimit('2000', async () => {
74
+ const result = await execute('SELECT @@max_execution_time AS limit_ms');
75
+ expect(result.content[0].text).toContain('2000');
76
+ });
77
+ });
78
+ it('leaves the server setting alone when the limit is zero', async () => {
79
+ await withLimit('0', async () => {
80
+ const result = await execute('SELECT @@max_execution_time AS limit_ms');
81
+ expect(result.content[0].text).toContain('"limit_ms": 0');
82
+ });
83
+ });
84
+ it('refuses a value past what the server accepts, rather than breaking the pool', async () => {
85
+ await withLimit('9999999999', async () => {
86
+ const result = await execute('SELECT @@max_execution_time AS limit_ms');
87
+ expect(result.isError).toBeUndefined();
88
+ expect(result.content[0].text).toContain('600000');
89
+ });
90
+ });
91
+ it('falls back to the default when the variable is only whitespace', async () => {
92
+ await withLimit(' ', async () => {
93
+ const result = await execute('SELECT @@max_execution_time AS limit_ms');
94
+ expect(result.content[0].text).toContain('600000');
95
+ });
96
+ });
97
+ it('restores the limit for the next call after a caller clears it', async () => {
98
+ await execute('SET SESSION max_execution_time = 0');
99
+ const result = await execute('SELECT @@max_execution_time AS limit_ms');
100
+ expect(result.content[0].text).toContain('600000');
101
+ });
102
+ it('still aborts a long SELECT after a caller clears the limit', async () => {
103
+ await withLimit('300', async () => {
104
+ await execute('SET SESSION max_execution_time = 0');
105
+ const started = Date.now();
106
+ const result = await execute('SELECT SLEEP(5) AS interrupted');
107
+ expect(Date.now() - started).toBeLessThan(4_000);
108
+ expect(result.content[0].text).toContain('1');
109
+ });
110
+ });
111
+ it('falls back to the default when the variable is not a limit', async () => {
112
+ await withLimit('soon', async () => {
113
+ const result = await execute('SELECT @@max_execution_time AS limit_ms');
114
+ expect(result.content[0].text).toContain('600000');
115
+ });
116
+ });
117
+ });
118
+ //# sourceMappingURL=timeout.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeout.test.js","sourceRoot":"","sources":["../../src/__tests__/timeout.test.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,18 @@
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 7 mysql tools without error', () => {
10
+ it('registers all 5 mysql 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-12107)').toEqual(['describe_table', 'execute', 'get_table_indexes', 'list_databases', 'list_tables']);
16
+ });
9
17
  });
10
18
  //# 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,2CAA2C,EAAE,GAAG,EAAE;QACnD,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,2CAA2C,EAAE,GAAG,EAAE;QACnD,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,CAAC,gBAAgB,EAAE,SAAS,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC;IACjG,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,KAAK,MAAM,gBAAgB,CAAC;AAenC,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC,IAAI,CAOpD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAKnD"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,gBAAgB,CAAC;AAoDnC,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC,IAAI,CAQpD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAOnD"}
package/dist/db.js CHANGED
@@ -1,5 +1,21 @@
1
1
  import mysql from 'mysql2/promise';
2
+ import { log } from './logger.js';
2
3
  const pools = new Map();
4
+ const DEFAULT_MAX_EXECUTION_TIME_MS = 10 * 60 * 1_000;
5
+ const MAX_EXECUTION_TIME_MS = 4_294_967_295;
6
+ function timeoutMillis(variable, fallback) {
7
+ const raw = process.env[variable];
8
+ if (raw === undefined)
9
+ return fallback;
10
+ const trimmed = raw.trim();
11
+ if (trimmed === '')
12
+ return fallback;
13
+ if (!/^\d+$/.test(trimmed) || Number(trimmed) > MAX_EXECUTION_TIME_MS) {
14
+ log(`Ignoring ${variable}=${raw}, expected whole milliseconds up to ${MAX_EXECUTION_TIME_MS}`);
15
+ return fallback;
16
+ }
17
+ return Number(trimmed);
18
+ }
3
19
  function getPoolConfig() {
4
20
  return {
5
21
  host: process.env.MYSQL_HOST,
@@ -10,18 +26,36 @@ function getPoolConfig() {
10
26
  connectionLimit: 5,
11
27
  };
12
28
  }
29
+ function reapplyOnAcquire(pool) {
30
+ const millis = timeoutMillis('MYSQL_MAX_EXECUTION_TIME_MS', DEFAULT_MAX_EXECUTION_TIME_MS);
31
+ if (millis === 0)
32
+ return;
33
+ let reported = false;
34
+ pool.on('acquire', (connection) => {
35
+ connection.query(`SET SESSION max_execution_time = ${millis}`, (err) => {
36
+ if (!err || reported)
37
+ return;
38
+ reported = true;
39
+ log('Failed to apply max_execution_time:', err);
40
+ });
41
+ });
42
+ }
13
43
  export function getPool(database) {
14
44
  const existing = pools.get(database);
15
45
  if (existing)
16
46
  return existing;
17
47
  const pool = mysql.createPool({ ...getPoolConfig(), database });
48
+ reapplyOnAcquire(pool);
18
49
  pools.set(database, pool);
19
50
  return pool;
20
51
  }
21
52
  export async function closeAllPools() {
22
- for (const pool of pools.values()) {
23
- await pool.end();
24
- }
53
+ const open = [...pools.values()];
25
54
  pools.clear();
55
+ const results = await Promise.allSettled(open.map((pool) => pool.end()));
56
+ for (const result of results) {
57
+ if (result.status === 'rejected')
58
+ log('Failed to close pool:', result.reason);
59
+ }
26
60
  }
27
61
  //# 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,KAAK,MAAM,gBAAgB,CAAC;AAEnC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;AAE5C,SAAS,aAAa;IACpB,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU;QAC5B,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI;QAC1E,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU;QAC5B,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;QACpC,kBAAkB,EAAE,IAAI;QACxB,eAAe,EAAE,CAAC;KACnB,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,KAAK,CAAC,UAAU,CAAC,EAAE,GAAG,aAAa,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChE,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QAClC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;IACnB,CAAC;IACD,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAuCpE,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuIrD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBpE,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAyGrD"}
package/dist/tools.js CHANGED
@@ -15,39 +15,15 @@ function formatRows(rows, emptyHint) {
15
15
  }
16
16
  return JSON.stringify(rows, null, 2);
17
17
  }
18
- // Runs sql inside a read-only transaction so MySQL itself rejects any write,
19
- // including writes reached through EXPLAIN ANALYZE.
20
- async function queryReadOnly(database, sql) {
21
- const connection = await getPool(database).getConnection();
22
- try {
23
- await connection.query('START TRANSACTION READ ONLY');
24
- const [rows] = await connection.query(sql);
25
- await connection.query('COMMIT');
26
- return rows;
27
- }
28
- catch (err) {
29
- await connection.query('ROLLBACK').catch(() => undefined);
30
- throw err;
31
- }
32
- finally {
33
- connection.release();
34
- }
35
- }
36
18
  export function registerTools(server) {
37
- server.tool('query', 'Execute a read-only SQL SELECT query and return results. Runs in a read-only transaction, so any write is rejected by the database.', { database: z.string().describe('The database to query'), sql: z.string().describe('The SQL SELECT query to execute') }, async ({ database, sql }) => {
38
- log(`Tool called: query on ${database}`);
39
- try {
40
- return textContent(formatRows(await queryReadOnly(database, sql)));
41
- }
42
- catch (err) {
43
- return errorContent('query', err);
44
- }
45
- });
46
- 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 }) => {
19
+ server.tool('execute', 'Execute a SQL statement. Returns the rows for a statement that produces a result set, such as SELECT, SHOW, DESCRIBE or EXPLAIN, and the affected row count for a statement that changes data or schema.', { database: z.string().describe('The database to execute against'), sql: z.string().describe('The SQL statement to execute') }, async ({ database, sql }) => {
47
20
  log(`Tool called: execute on ${database}`);
48
21
  try {
49
22
  const pool = getPool(database);
50
23
  const [result] = await pool.query(sql);
24
+ if (Array.isArray(result)) {
25
+ return textContent(formatRows(result));
26
+ }
51
27
  const affectedRows = result.affectedRows ?? 0;
52
28
  return textContent(`Statement executed successfully. Rows affected: ${affectedRows}`);
53
29
  }
@@ -112,19 +88,5 @@ export function registerTools(server) {
112
88
  return errorContent('get_table_indexes', err);
113
89
  }
114
90
  });
115
- server.tool('explain_query', 'Get the execution plan for a SQL query.', {
116
- database: z.string().describe('The database to explain the query against'),
117
- sql: z.string().describe('The SQL query to explain'),
118
- 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.'),
119
- }, async ({ database, sql, analyze }) => {
120
- log(`Tool called: explain_query on ${database}`);
121
- try {
122
- const explainSql = analyze ? `EXPLAIN ANALYZE ${sql}` : `EXPLAIN ${sql}`;
123
- return textContent(formatRows(await queryReadOnly(database, explainSql)));
124
- }
125
- catch (err) {
126
- return errorContent('explain_query', err);
127
- }
128
- });
129
91
  }
130
92
  //# 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":"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"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apono-io/mcp-mysql",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Apono MCP server for MySQL databases with JIT credential support",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=read-only.test.d.ts.map
@@ -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 connection = {
3
- query: vi.fn(),
4
- release: vi.fn(),
5
- };
6
- const pool = {
7
- getConnection: vi.fn(async () => connection),
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
- connection.query.mockReset();
24
- connection.release.mockReset();
25
- pool.getConnection.mockClear();
26
- });
27
- it('wraps query in a read-only transaction and commits', async () => {
28
- connection.query.mockResolvedValue([[{ id: 1 }]]);
29
- const result = await collectTools().get('query')({ database: 'apono', sql: 'SELECT 1' });
30
- const issued = connection.query.mock.calls.map((call) => call[0]);
31
- expect(issued[0]).toBe('START TRANSACTION READ ONLY');
32
- expect(issued).toContain('SELECT 1');
33
- expect(issued).toContain('COMMIT');
34
- expect(result.isError).toBeUndefined();
35
- expect(connection.release).toHaveBeenCalledOnce();
36
- });
37
- it('surfaces the database rejection when a write is attempted and rolls back', async () => {
38
- connection.query.mockImplementation(async (sql) => {
39
- if (sql === 'START TRANSACTION READ ONLY')
40
- return [[]];
41
- if (sql === 'ROLLBACK')
42
- return [[]];
43
- throw new Error('Cannot execute statement 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(connection.query.mock.calls.map((call) => call[0])).toContain('ROLLBACK');
49
- expect(connection.release).toHaveBeenCalledOnce();
50
- });
51
- it('runs explain_query with analyze inside the read-only transaction', async () => {
52
- connection.query.mockResolvedValue([[{ EXPLAIN: 'scan' }]]);
53
- await collectTools().get('explain_query')({ database: 'apono', sql: 'SELECT 1', analyze: true });
54
- const issued = connection.query.mock.calls.map((call) => call[0]);
55
- expect(issued[0]).toBe('START 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([{ affectedRows: 1 }]);
60
- await collectTools().get('execute')({ database: 'apono', sql: 'DELETE FROM users' });
61
- expect(pool.getConnection).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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"}