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/README.md CHANGED
@@ -13,6 +13,7 @@ Stop juggling multiple database tools. `mpx-db` gives you one clean interface fo
13
13
  ✅ **Schema operations** — Dump, describe, visualize database structure
14
14
  ✅ **Data export** — Export to JSON/CSV with one command
15
15
  ✅ **Secure** — Encrypted credential storage
16
+ ✅ **AI-native** — JSON output, MCP server, schema discovery for AI agents
16
17
 
17
18
  ## Installation
18
19
 
@@ -224,6 +225,135 @@ mpx-db migrate create add_order_status_field
224
225
  mpx-db export dev orders --format csv --output orders-backup.csv
225
226
  ```
226
227
 
228
+ ## AI Agent Usage
229
+
230
+ `mpx-db` is **AI-native** — designed for both humans and AI agents. Every command supports machine-readable output and schema discovery.
231
+
232
+ ### JSON Output
233
+
234
+ Add `--json` to any command for structured output:
235
+
236
+ ```bash
237
+ # Query with JSON output
238
+ mpx-db query dev "SELECT * FROM users LIMIT 3" --json
239
+ {
240
+ "success": true,
241
+ "type": "query",
242
+ "rows": [
243
+ { "id": 1, "name": "Alice", "email": "alice@example.com" },
244
+ { "id": 2, "name": "Bob", "email": "bob@example.com" },
245
+ { "id": 3, "name": "Charlie", "email": "charlie@example.com" }
246
+ ],
247
+ "rowCount": 3,
248
+ "duration": 12
249
+ }
250
+
251
+ # List tables with JSON
252
+ mpx-db tables dev --json
253
+ {
254
+ "tables": [
255
+ { "name": "users", "type": "table", "rowCount": 150 },
256
+ { "name": "orders", "type": "table", "rowCount": 892 }
257
+ ]
258
+ }
259
+
260
+ # Migration status
261
+ mpx-db migrate status dev --json
262
+ {
263
+ "migrations": [
264
+ { "name": "20260215_100000_create_users", "status": "applied", "appliedAt": "2026-02-15T10:05:00.000Z" },
265
+ { "name": "20260216_120000_add_orders", "status": "pending", "appliedAt": null }
266
+ ]
267
+ }
268
+ ```
269
+
270
+ ### Schema Discovery
271
+
272
+ Get the full command schema for AI agent integration:
273
+
274
+ ```bash
275
+ mpx-db --schema
276
+ ```
277
+
278
+ Returns a comprehensive JSON schema describing all commands, flags, inputs, outputs, and examples.
279
+
280
+ ### MCP (Model Context Protocol) Server
281
+
282
+ Run `mpx-db` as an MCP server for seamless AI agent integration:
283
+
284
+ ```bash
285
+ mpx-db mcp
286
+ ```
287
+
288
+ #### Claude Desktop Integration
289
+
290
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
291
+
292
+ ```json
293
+ {
294
+ "mcpServers": {
295
+ "mpx-db": {
296
+ "command": "npx",
297
+ "args": ["mpx-db", "mcp"]
298
+ }
299
+ }
300
+ }
301
+ ```
302
+
303
+ Now Claude can directly query your databases, inspect schemas, and run migrations!
304
+
305
+ #### Available MCP Tools
306
+
307
+ - `query` — Execute SQL queries
308
+ - `list_tables` — Get all tables with row counts
309
+ - `describe_table` — Show table schema
310
+ - `get_info` — Database information
311
+ - `export_table` — Export table data as JSON
312
+ - `get_schema` — Get full command schema
313
+
314
+ ### Quiet Mode
315
+
316
+ Suppress non-essential output with `--quiet`:
317
+
318
+ ```bash
319
+ # Just the data, no banners or progress messages
320
+ mpx-db query dev "SELECT COUNT(*) FROM users" --quiet --json
321
+ ```
322
+
323
+ ### Exit Codes
324
+
325
+ Predictable exit codes for CI/CD and scripting:
326
+
327
+ - `0` — Success
328
+ - `1` — Error (connection failed, query failed, etc.)
329
+
330
+ ```bash
331
+ #!/bin/bash
332
+ if mpx-db query dev "SELECT 1" --quiet; then
333
+ echo "Database is up"
334
+ else
335
+ echo "Database is down"
336
+ exit 1
337
+ fi
338
+ ```
339
+
340
+ ### Example: AI Agent Workflow
341
+
342
+ ```javascript
343
+ // AI agent discovers available commands
344
+ const schema = await exec('mpx-db --schema');
345
+
346
+ // Agent queries database
347
+ const result = await exec('mpx-db query dev "SELECT * FROM orders WHERE status = \'pending\'" --json');
348
+ const orders = JSON.parse(result.stdout);
349
+
350
+ // Agent inspects schema
351
+ const tables = await exec('mpx-db tables dev --json');
352
+
353
+ // Agent runs migration
354
+ await exec('mpx-db migrate up dev --json --quiet');
355
+ ```
356
+
227
357
  ## Architecture
228
358
 
229
359
  ```
@@ -310,15 +440,18 @@ Test suite includes:
310
440
 
311
441
  ## Roadmap
312
442
 
313
- **v1.0 (Current)** ✅
443
+ **v1.1 (Current)** ✅
314
444
  - SQLite, PostgreSQL, MySQL support
315
445
  - Connection management
316
446
  - Query execution with beautiful output
317
447
  - Schema inspection (dump, describe, tables, info)
318
448
  - Migration system (create, up, down, status)
319
449
  - Data export (JSON, CSV)
450
+ - **AI-native features:** JSON output (`--json`), schema discovery (`--schema`), MCP server mode
451
+ - **Quiet mode** (`--quiet`) for scripting
452
+ - Predictable exit codes
320
453
 
321
- **v1.1 (Planned)**
454
+ **v1.2 (Planned)**
322
455
  - Interactive query REPL mode
323
456
  - Query history and favorites
324
457
  - Auto-complete for table/column names
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mpx-db",
3
- "version": "1.0.3",
3
+ "version": "1.1.2",
4
4
  "description": "Database management CLI - Connect, query, migrate, and manage databases from the terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -20,7 +20,12 @@
20
20
  "mysql",
21
21
  "sqlite",
22
22
  "schema",
23
- "sql"
23
+ "sql",
24
+ "mcp",
25
+ "ai-native",
26
+ "model-context-protocol",
27
+ "automation",
28
+ "json-output"
24
29
  ],
25
30
  "author": "Mesaplex <support@mesaplex.com>",
26
31
  "license": "MIT",
@@ -34,6 +39,7 @@
34
39
  "node": ">=18.0.0"
35
40
  },
36
41
  "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.26.0",
37
43
  "better-sqlite3": "^12.6.2",
38
44
  "chalk": "^5.3.0",
39
45
  "cli-table3": "^0.6.5",
package/src/cli.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  rollbackMigration
15
15
  } from './commands/migrate.js';
16
16
  import { exportData } from './commands/data.js';
17
+ import { getSchema } from './schema.js';
17
18
 
18
19
  const __filename = fileURLToPath(import.meta.url);
19
20
  const __dirname = dirname(__filename);
@@ -21,10 +22,27 @@ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8')
21
22
 
22
23
  const program = new Command();
23
24
 
25
+ // Global options state (passed to commands via options parameter)
26
+ let globalOptions = {};
27
+
24
28
  program
25
29
  .name('mpx-db')
26
30
  .description('Database management CLI - Connect, query, migrate, and manage databases')
27
- .version(pkg.version);
31
+ .version(pkg.version)
32
+ .option('--json', 'Output as JSON (machine-readable)')
33
+ .option('-q, --quiet', 'Suppress non-essential output')
34
+ .option('--schema', 'Output JSON schema describing all commands and flags')
35
+ .hook('preAction', (thisCommand) => {
36
+ // Merge parent options with command options
37
+ const parentOpts = thisCommand.parent?.opts() || {};
38
+ const opts = thisCommand.opts();
39
+ globalOptions = { ...parentOpts, ...opts };
40
+
41
+ // Disable chalk if JSON mode
42
+ if (globalOptions.json) {
43
+ chalk.level = 0;
44
+ }
45
+ });
28
46
 
29
47
  // Connect command
30
48
  program
@@ -32,7 +50,7 @@ program
32
50
  .description('Test and optionally save a database connection')
33
51
  .argument('<url>', 'Connection URL (sqlite://, postgres://, mysql://)')
34
52
  .option('-s, --save <name>', 'Save connection with a name')
35
- .action(handleConnect);
53
+ .action((url, options) => handleConnect(url, { ...globalOptions, ...options }));
36
54
 
37
55
  // Connections command
38
56
  const connections = program
@@ -42,13 +60,13 @@ const connections = program
42
60
  connections
43
61
  .command('list')
44
62
  .description('List all saved connections')
45
- .action(listConnections);
63
+ .action(() => listConnections(globalOptions));
46
64
 
47
65
  connections
48
66
  .command('remove')
49
67
  .description('Remove a saved connection')
50
68
  .argument('<name>', 'Connection name')
51
- .action(removeConnection);
69
+ .action((name) => removeConnection(name, globalOptions));
52
70
 
53
71
  // Query command
54
72
  program
@@ -56,21 +74,21 @@ program
56
74
  .description('Execute a SQL query')
57
75
  .argument('<target>', 'Connection name or URL')
58
76
  .argument('<sql>', 'SQL query to execute')
59
- .action(handleQuery);
77
+ .action((target, sql) => handleQuery(target, sql, globalOptions));
60
78
 
61
79
  // Info command
62
80
  program
63
81
  .command('info')
64
82
  .description('Show database information')
65
83
  .argument('<target>', 'Connection name or URL')
66
- .action(showInfo);
84
+ .action((target) => showInfo(target, globalOptions));
67
85
 
68
86
  // Tables command
69
87
  program
70
88
  .command('tables')
71
89
  .description('List all tables')
72
90
  .argument('<target>', 'Connection name or URL')
73
- .action(listTables);
91
+ .action((target) => listTables(target, globalOptions));
74
92
 
75
93
  // Describe command
76
94
  program
@@ -78,7 +96,7 @@ program
78
96
  .description('Show table schema')
79
97
  .argument('<target>', 'Connection name or URL')
80
98
  .argument('<table>', 'Table name')
81
- .action(describeTable);
99
+ .action((target, table) => describeTable(target, table, globalOptions));
82
100
 
83
101
  // Schema commands
84
102
  const schema = program
@@ -89,7 +107,7 @@ schema
89
107
  .command('dump')
90
108
  .description('Dump database schema as SQL')
91
109
  .argument('<target>', 'Connection name or URL')
92
- .action(dumpSchema);
110
+ .action((target) => dumpSchema(target, globalOptions));
93
111
 
94
112
  // Migration commands
95
113
  const migrate = program
@@ -99,31 +117,31 @@ const migrate = program
99
117
  migrate
100
118
  .command('init')
101
119
  .description('Initialize migrations directory')
102
- .action(initMigrations);
120
+ .action(() => initMigrations(globalOptions));
103
121
 
104
122
  migrate
105
123
  .command('create')
106
124
  .description('Create a new migration file')
107
125
  .argument('<description>', 'Migration description')
108
- .action(createMigration);
126
+ .action((description) => createMigration(description, globalOptions));
109
127
 
110
128
  migrate
111
129
  .command('status')
112
130
  .description('Show migration status')
113
131
  .argument('<target>', 'Connection name or URL')
114
- .action(showMigrationStatus);
132
+ .action((target) => showMigrationStatus(target, globalOptions));
115
133
 
116
134
  migrate
117
135
  .command('up')
118
136
  .description('Run pending migrations')
119
137
  .argument('<target>', 'Connection name or URL')
120
- .action(runMigrations);
138
+ .action((target) => runMigrations(target, globalOptions));
121
139
 
122
140
  migrate
123
141
  .command('down')
124
142
  .description('Rollback last migration')
125
143
  .argument('<target>', 'Connection name or URL')
126
- .action(rollbackMigration);
144
+ .action((target) => rollbackMigration(target, globalOptions));
127
145
 
128
146
  // Export command
129
147
  program
@@ -133,7 +151,99 @@ program
133
151
  .argument('<table>', 'Table name')
134
152
  .option('-f, --format <format>', 'Output format (json, csv)', 'json')
135
153
  .option('-o, --output <file>', 'Output file path')
136
- .action(exportData);
154
+ .action((target, table, options) => exportData(target, table, { ...globalOptions, ...options }));
155
+
156
+ // Update subcommand
157
+ program
158
+ .command('update')
159
+ .description('Check for updates and optionally install the latest version')
160
+ .option('--check', 'Only check for updates (do not install)')
161
+ .action(async (options) => {
162
+ const { checkForUpdate, performUpdate } = await import('./update.js');
163
+ const jsonMode = globalOptions.json;
164
+
165
+ try {
166
+ const info = checkForUpdate();
167
+
168
+ if (jsonMode) {
169
+ const output = {
170
+ current: info.current,
171
+ latest: info.latest,
172
+ updateAvailable: info.updateAvailable,
173
+ isGlobal: info.isGlobal
174
+ };
175
+
176
+ if (!options.check && info.updateAvailable) {
177
+ try {
178
+ const result = performUpdate(info.isGlobal);
179
+ output.updated = true;
180
+ output.newVersion = result.version;
181
+ } catch (err) {
182
+ output.updated = false;
183
+ output.error = err.message;
184
+ }
185
+ }
186
+
187
+ console.log(JSON.stringify(output, null, 2));
188
+ process.exit(0);
189
+ return;
190
+ }
191
+
192
+ // Human-readable output
193
+ if (!info.updateAvailable) {
194
+ console.log('');
195
+ console.log(chalk.green.bold(`✓ mpx-db v${info.current} is up to date`));
196
+ console.log('');
197
+ process.exit(0);
198
+ return;
199
+ }
200
+
201
+ console.log('');
202
+ console.log(chalk.yellow.bold(`⬆ Update available: v${info.current} → v${info.latest}`));
203
+
204
+ if (options.check) {
205
+ console.log(chalk.gray(`Run ${chalk.cyan('mpx-db update')} to install`));
206
+ console.log('');
207
+ process.exit(0);
208
+ return;
209
+ }
210
+
211
+ console.log(chalk.gray(`Installing v${info.latest}${info.isGlobal ? ' (global)' : ''}...`));
212
+
213
+ const result = performUpdate(info.isGlobal);
214
+ console.log(chalk.green.bold(`✓ Updated to v${result.version}`));
215
+ console.log('');
216
+ process.exit(0);
217
+ } catch (err) {
218
+ if (jsonMode) {
219
+ console.log(JSON.stringify({ error: err.message, code: 'ERR_UPDATE' }, null, 2));
220
+ } else {
221
+ console.error(chalk.red.bold('\n❌ Update check failed:'), err.message);
222
+ console.error('');
223
+ }
224
+ process.exit(1);
225
+ }
226
+ });
227
+
228
+ // MCP subcommand
229
+ program
230
+ .command('mcp')
231
+ .description('Start MCP (Model Context Protocol) stdio server')
232
+ .action(async () => {
233
+ try {
234
+ const { startMCPServer } = await import('./mcp.js');
235
+ await startMCPServer();
236
+ } catch (err) {
237
+ console.error(JSON.stringify({ error: err.message, code: 'ERR_MCP_START' }));
238
+ process.exit(1);
239
+ }
240
+ });
241
+
242
+ // Handle --schema before parsing (to avoid argument validation)
243
+ if (process.argv.includes('--schema')) {
244
+ console.log(JSON.stringify(getSchema(), null, 2));
245
+ process.exit(0);
246
+ }
137
247
 
138
248
  // Error handling
139
249
  program.exitOverride();
@@ -6,14 +6,40 @@ import { createConnection } from '../db/connection.js';
6
6
  /**
7
7
  * Handle connect command
8
8
  */
9
- export async function handleConnect(url, options) {
9
+ export async function handleConnect(url, options = {}) {
10
10
  try {
11
11
  // Test connection
12
- console.log(chalk.gray('Testing connection...'));
12
+ if (!options.quiet && !options.json) {
13
+ console.log(chalk.gray('Testing connection...'));
14
+ }
15
+
13
16
  const db = await createConnection(url);
14
17
  const info = await db.getInfo();
15
18
  await db.disconnect();
16
19
 
20
+ // Save if requested
21
+ let saved = false;
22
+ if (options.save) {
23
+ saveConnection(options.save, url);
24
+ saved = true;
25
+ }
26
+
27
+ // JSON output
28
+ if (options.json) {
29
+ console.log(JSON.stringify({
30
+ success: true,
31
+ connection: {
32
+ type: info.type,
33
+ database: info.database,
34
+ path: info.path
35
+ },
36
+ saved,
37
+ name: options.save || null
38
+ }, null, 2));
39
+ return;
40
+ }
41
+
42
+ // Human-readable output
17
43
  console.log(chalk.green('✓ Connection successful'));
18
44
  console.log(chalk.gray(` Type: ${info.type}`));
19
45
  if (info.database) {
@@ -23,15 +49,17 @@ export async function handleConnect(url, options) {
23
49
  console.log(chalk.gray(` Path: ${info.path}`));
24
50
  }
25
51
 
26
- // Save if requested
27
52
  if (options.save) {
28
- saveConnection(options.save, url);
29
53
  console.log(chalk.green(`✓ Saved connection as "${options.save}"`));
30
54
  }
31
55
 
32
56
  } catch (err) {
33
- console.error(chalk.red('✗ Connection failed'));
34
- console.error(chalk.red(` ${err.message}`));
57
+ if (options.json) {
58
+ console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
59
+ } else {
60
+ console.error(chalk.red('✗ Connection failed'));
61
+ console.error(chalk.red(` ${err.message}`));
62
+ }
35
63
  process.exit(1);
36
64
  }
37
65
  }
@@ -39,13 +67,27 @@ export async function handleConnect(url, options) {
39
67
  /**
40
68
  * List saved connections
41
69
  */
42
- export async function listConnections() {
70
+ export async function listConnections(options = {}) {
43
71
  const connections = loadConnections();
44
72
 
73
+ // JSON output
74
+ if (options.json) {
75
+ const connArray = Object.entries(connections).map(([name, conn]) => ({
76
+ name,
77
+ type: conn.type,
78
+ createdAt: new Date(conn.createdAt).toISOString()
79
+ }));
80
+ console.log(JSON.stringify({ connections: connArray }, null, 2));
81
+ return;
82
+ }
83
+
84
+ // Human-readable output
45
85
  if (Object.keys(connections).length === 0) {
46
86
  console.log(chalk.yellow('No saved connections'));
47
- console.log(chalk.gray('\nSave a connection with:'));
48
- console.log(chalk.gray(' mpx-db connect --save <name> <url>'));
87
+ if (!options.quiet) {
88
+ console.log(chalk.gray('\nSave a connection with:'));
89
+ console.log(chalk.gray(' mpx-db connect --save <name> <url>'));
90
+ }
49
91
  return;
50
92
  }
51
93
 
@@ -68,9 +110,20 @@ export async function listConnections() {
68
110
  /**
69
111
  * Delete a saved connection
70
112
  */
71
- export async function removeConnection(name) {
113
+ export async function removeConnection(name, options = {}) {
72
114
  const deleted = deleteConnection(name);
73
115
 
116
+ // JSON output
117
+ if (options.json) {
118
+ console.log(JSON.stringify({
119
+ success: deleted,
120
+ name,
121
+ message: deleted ? 'Connection deleted' : 'Connection not found'
122
+ }, null, 2));
123
+ return;
124
+ }
125
+
126
+ // Human-readable output
74
127
  if (deleted) {
75
128
  console.log(chalk.green(`✓ Deleted connection "${name}"`));
76
129
  } else {
@@ -6,7 +6,7 @@ import { resolveConnection } from './query.js';
6
6
  /**
7
7
  * Export table data to CSV or JSON
8
8
  */
9
- export async function exportData(target, tableName, options) {
9
+ export async function exportData(target, tableName, options = {}) {
10
10
  let db;
11
11
 
12
12
  try {
@@ -17,12 +17,14 @@ export async function exportData(target, tableName, options) {
17
17
  const rows = await db.query(`SELECT * FROM ${tableName}`);
18
18
 
19
19
  if (rows.length === 0) {
20
- console.log(chalk.yellow('No data to export'));
20
+ if (!options.quiet) {
21
+ console.log(chalk.yellow('No data to export'));
22
+ }
21
23
  return;
22
24
  }
23
25
 
24
26
  const format = options.format || 'json';
25
- const output = options.output || `${tableName}.${format}`;
27
+ const output = options.output;
26
28
 
27
29
  let content;
28
30
 
@@ -34,12 +36,23 @@ export async function exportData(target, tableName, options) {
34
36
  throw new Error(`Unsupported format: ${format}`);
35
37
  }
36
38
 
37
- fs.writeFileSync(output, content);
38
-
39
- console.log(chalk.green(`✓ Exported ${rows.length} rows to ${output}`));
39
+ // If output file specified, write to file
40
+ if (output) {
41
+ fs.writeFileSync(output, content);
42
+ if (!options.quiet) {
43
+ console.log(chalk.green(`✓ Exported ${rows.length} rows to ${output}`));
44
+ }
45
+ } else {
46
+ // Write to stdout
47
+ console.log(content);
48
+ }
40
49
 
41
50
  } catch (err) {
42
- console.error(chalk.red(`✗ Export failed: ${err.message}`));
51
+ if (options.json) {
52
+ console.log(JSON.stringify({ error: err.message }, null, 2));
53
+ } else {
54
+ console.error(chalk.red(`✗ Export failed: ${err.message}`));
55
+ }
43
56
  process.exit(1);
44
57
  } finally {
45
58
  if (db) {