mpx-db 1.0.3 → 1.1.2

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/src/mcp.js ADDED
@@ -0,0 +1,307 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Server
3
+ *
4
+ * Exposes mpx-db capabilities as MCP tools for AI agent integration.
5
+ * Runs over stdio transport.
6
+ */
7
+
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import {
11
+ ListToolsRequestSchema,
12
+ CallToolRequestSchema
13
+ } from '@modelcontextprotocol/sdk/types.js';
14
+
15
+ import { readFileSync } from 'fs';
16
+ import { fileURLToPath } from 'url';
17
+ import { dirname, join } from 'path';
18
+ import { createConnection } from './db/connection.js';
19
+ import { getConnection } from './utils/config.js';
20
+ import { getSchema } from './schema.js';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
25
+
26
+ /**
27
+ * Resolve connection string from target (name or URL)
28
+ */
29
+ function resolveConnection(target) {
30
+ const saved = getConnection(target);
31
+ if (saved) {
32
+ return saved.url;
33
+ }
34
+
35
+ if (target.includes('://')) {
36
+ return target;
37
+ }
38
+
39
+ throw new Error(`Connection "${target}" not found`);
40
+ }
41
+
42
+ export async function startMCPServer() {
43
+ const server = new Server(
44
+ { name: 'mpx-db', version: pkg.version },
45
+ { capabilities: { tools: {} } }
46
+ );
47
+
48
+ // List available tools
49
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
50
+ return {
51
+ tools: [
52
+ {
53
+ name: 'query',
54
+ description: 'Execute a SQL query or statement on a database. Returns rows for SELECT queries, or execution details for INSERT/UPDATE/DELETE/DDL statements.',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ target: {
59
+ type: 'string',
60
+ description: 'Connection name (saved) or connection URL (sqlite://, postgres://, mysql://)'
61
+ },
62
+ sql: {
63
+ type: 'string',
64
+ description: 'SQL query or statement to execute'
65
+ }
66
+ },
67
+ required: ['target', 'sql']
68
+ }
69
+ },
70
+ {
71
+ name: 'list_tables',
72
+ description: 'List all tables in the database with row counts.',
73
+ inputSchema: {
74
+ type: 'object',
75
+ properties: {
76
+ target: {
77
+ type: 'string',
78
+ description: 'Connection name or URL'
79
+ }
80
+ },
81
+ required: ['target']
82
+ }
83
+ },
84
+ {
85
+ name: 'describe_table',
86
+ description: 'Show the schema/structure of a specific table (columns, types, constraints).',
87
+ inputSchema: {
88
+ type: 'object',
89
+ properties: {
90
+ target: {
91
+ type: 'string',
92
+ description: 'Connection name or URL'
93
+ },
94
+ table: {
95
+ type: 'string',
96
+ description: 'Table name'
97
+ }
98
+ },
99
+ required: ['target', 'table']
100
+ }
101
+ },
102
+ {
103
+ name: 'get_info',
104
+ description: 'Get database information (type, version, size, table count).',
105
+ inputSchema: {
106
+ type: 'object',
107
+ properties: {
108
+ target: {
109
+ type: 'string',
110
+ description: 'Connection name or URL'
111
+ }
112
+ },
113
+ required: ['target']
114
+ }
115
+ },
116
+ {
117
+ name: 'export_table',
118
+ description: 'Export all data from a table as JSON.',
119
+ inputSchema: {
120
+ type: 'object',
121
+ properties: {
122
+ target: {
123
+ type: 'string',
124
+ description: 'Connection name or URL'
125
+ },
126
+ table: {
127
+ type: 'string',
128
+ description: 'Table name'
129
+ }
130
+ },
131
+ required: ['target', 'table']
132
+ }
133
+ },
134
+ {
135
+ name: 'get_schema',
136
+ description: 'Get the full JSON schema describing all mpx-db commands, flags, and output formats.',
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {}
140
+ }
141
+ }
142
+ ]
143
+ };
144
+ });
145
+
146
+ // Handle tool calls
147
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
148
+ const { name, arguments: args } = request.params;
149
+
150
+ try {
151
+ switch (name) {
152
+ case 'query': {
153
+ const connectionString = resolveConnection(args.target);
154
+ const db = await createConnection(connectionString);
155
+
156
+ try {
157
+ const sql = args.sql.trim();
158
+ const isSelectQuery = /^(SELECT|PRAGMA|EXPLAIN|WITH|SHOW|DESCRIBE|DESC)\s/i.test(sql);
159
+
160
+ const startTime = Date.now();
161
+ let result;
162
+
163
+ if (isSelectQuery) {
164
+ const rows = await db.query(sql);
165
+ const duration = Date.now() - startTime;
166
+ result = {
167
+ success: true,
168
+ type: 'query',
169
+ rows,
170
+ rowCount: rows.length,
171
+ duration
172
+ };
173
+ } else {
174
+ const execResult = await db.execute(sql);
175
+ const duration = Date.now() - startTime;
176
+ result = {
177
+ success: true,
178
+ type: 'statement',
179
+ affectedRows: execResult.affectedRows,
180
+ insertId: execResult.insertId,
181
+ duration
182
+ };
183
+ }
184
+
185
+ await db.disconnect();
186
+
187
+ return {
188
+ content: [{
189
+ type: 'text',
190
+ text: JSON.stringify(result, null, 2)
191
+ }]
192
+ };
193
+ } catch (err) {
194
+ await db.disconnect();
195
+ throw err;
196
+ }
197
+ }
198
+
199
+ case 'list_tables': {
200
+ const connectionString = resolveConnection(args.target);
201
+ const db = await createConnection(connectionString);
202
+
203
+ try {
204
+ const tables = await db.getTables();
205
+ await db.disconnect();
206
+
207
+ return {
208
+ content: [{
209
+ type: 'text',
210
+ text: JSON.stringify({ tables }, null, 2)
211
+ }]
212
+ };
213
+ } catch (err) {
214
+ await db.disconnect();
215
+ throw err;
216
+ }
217
+ }
218
+
219
+ case 'describe_table': {
220
+ const connectionString = resolveConnection(args.target);
221
+ const db = await createConnection(connectionString);
222
+
223
+ try {
224
+ const schema = await db.getTableSchema(args.table);
225
+ await db.disconnect();
226
+
227
+ return {
228
+ content: [{
229
+ type: 'text',
230
+ text: JSON.stringify({ table: args.table, columns: schema }, null, 2)
231
+ }]
232
+ };
233
+ } catch (err) {
234
+ await db.disconnect();
235
+ throw err;
236
+ }
237
+ }
238
+
239
+ case 'get_info': {
240
+ const connectionString = resolveConnection(args.target);
241
+ const db = await createConnection(connectionString);
242
+
243
+ try {
244
+ const info = await db.getInfo();
245
+ await db.disconnect();
246
+
247
+ return {
248
+ content: [{
249
+ type: 'text',
250
+ text: JSON.stringify(info, null, 2)
251
+ }]
252
+ };
253
+ } catch (err) {
254
+ await db.disconnect();
255
+ throw err;
256
+ }
257
+ }
258
+
259
+ case 'export_table': {
260
+ const connectionString = resolveConnection(args.target);
261
+ const db = await createConnection(connectionString);
262
+
263
+ try {
264
+ const rows = await db.query(`SELECT * FROM ${args.table}`);
265
+ await db.disconnect();
266
+
267
+ return {
268
+ content: [{
269
+ type: 'text',
270
+ text: JSON.stringify({ table: args.table, rows, count: rows.length }, null, 2)
271
+ }]
272
+ };
273
+ } catch (err) {
274
+ await db.disconnect();
275
+ throw err;
276
+ }
277
+ }
278
+
279
+ case 'get_schema': {
280
+ return {
281
+ content: [{
282
+ type: 'text',
283
+ text: JSON.stringify(getSchema(), null, 2)
284
+ }]
285
+ };
286
+ }
287
+
288
+ default:
289
+ return {
290
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
291
+ isError: true
292
+ };
293
+ }
294
+ } catch (err) {
295
+ return {
296
+ content: [{
297
+ type: 'text',
298
+ text: JSON.stringify({ error: err.message, code: 'ERR_QUERY' }, null, 2)
299
+ }],
300
+ isError: true
301
+ };
302
+ }
303
+ });
304
+
305
+ const transport = new StdioServerTransport();
306
+ await server.connect(transport);
307
+ }