truss-db-mcp 1.0.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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +79 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/ai-query.d.ts +7 -0
  8. package/dist/lib/ai-query.d.ts.map +1 -0
  9. package/dist/lib/ai-query.js +224 -0
  10. package/dist/lib/ai-query.js.map +1 -0
  11. package/dist/lib/config.d.ts +4 -0
  12. package/dist/lib/config.d.ts.map +1 -0
  13. package/dist/lib/config.js +30 -0
  14. package/dist/lib/config.js.map +1 -0
  15. package/dist/lib/connection.d.ts +14 -0
  16. package/dist/lib/connection.d.ts.map +1 -0
  17. package/dist/lib/connection.js +175 -0
  18. package/dist/lib/connection.js.map +1 -0
  19. package/dist/lib/license.d.ts +4 -0
  20. package/dist/lib/license.d.ts.map +1 -0
  21. package/dist/lib/license.js +94 -0
  22. package/dist/lib/license.js.map +1 -0
  23. package/dist/lib/query-optimizer.d.ts +3 -0
  24. package/dist/lib/query-optimizer.d.ts.map +1 -0
  25. package/dist/lib/query-optimizer.js +155 -0
  26. package/dist/lib/query-optimizer.js.map +1 -0
  27. package/dist/tools/connect.d.ts +3 -0
  28. package/dist/tools/connect.d.ts.map +1 -0
  29. package/dist/tools/connect.js +37 -0
  30. package/dist/tools/connect.js.map +1 -0
  31. package/dist/tools/describe-table.d.ts +3 -0
  32. package/dist/tools/describe-table.d.ts.map +1 -0
  33. package/dist/tools/describe-table.js +60 -0
  34. package/dist/tools/describe-table.js.map +1 -0
  35. package/dist/tools/export-data.d.ts +3 -0
  36. package/dist/tools/export-data.d.ts.map +1 -0
  37. package/dist/tools/export-data.js +68 -0
  38. package/dist/tools/export-data.js.map +1 -0
  39. package/dist/tools/generate-migration.d.ts +3 -0
  40. package/dist/tools/generate-migration.d.ts.map +1 -0
  41. package/dist/tools/generate-migration.js +36 -0
  42. package/dist/tools/generate-migration.js.map +1 -0
  43. package/dist/tools/list-tables.d.ts +3 -0
  44. package/dist/tools/list-tables.d.ts.map +1 -0
  45. package/dist/tools/list-tables.js +45 -0
  46. package/dist/tools/list-tables.js.map +1 -0
  47. package/dist/tools/natural-language-query.d.ts +3 -0
  48. package/dist/tools/natural-language-query.d.ts.map +1 -0
  49. package/dist/tools/natural-language-query.js +53 -0
  50. package/dist/tools/natural-language-query.js.map +1 -0
  51. package/dist/tools/optimize-query.d.ts +3 -0
  52. package/dist/tools/optimize-query.d.ts.map +1 -0
  53. package/dist/tools/optimize-query.js +51 -0
  54. package/dist/tools/optimize-query.js.map +1 -0
  55. package/dist/tools/query.d.ts +3 -0
  56. package/dist/tools/query.d.ts.map +1 -0
  57. package/dist/tools/query.js +79 -0
  58. package/dist/tools/query.js.map +1 -0
  59. package/dist/types.d.ts +86 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +3 -0
  62. package/dist/types.js.map +1 -0
  63. package/evals/eval-query.ts +356 -0
  64. package/evals/run-evals.ts +36 -0
  65. package/evals/test.db +0 -0
  66. package/glama.json +4 -0
  67. package/package.json +47 -0
  68. package/smithery.yaml +18 -0
  69. package/src/index.ts +99 -0
  70. package/src/lib/ai-query.ts +274 -0
  71. package/src/lib/config.ts +33 -0
  72. package/src/lib/connection.ts +263 -0
  73. package/src/lib/license.ts +118 -0
  74. package/src/lib/query-optimizer.ts +191 -0
  75. package/src/tools/connect.ts +43 -0
  76. package/src/tools/describe-table.ts +67 -0
  77. package/src/tools/export-data.ts +81 -0
  78. package/src/tools/generate-migration.ts +43 -0
  79. package/src/tools/list-tables.ts +52 -0
  80. package/src/tools/natural-language-query.ts +61 -0
  81. package/src/tools/optimize-query.ts +59 -0
  82. package/src/tools/query.ts +89 -0
  83. package/src/types.ts +115 -0
  84. package/tsconfig.json +19 -0
@@ -0,0 +1,356 @@
1
+ import Database from 'better-sqlite3';
2
+ import { resolve, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+
7
+ interface EvalResult {
8
+ name: string;
9
+ passed: boolean;
10
+ details: string;
11
+ }
12
+
13
+ interface EvalSuite {
14
+ passed: number;
15
+ failed: number;
16
+ results: EvalResult[];
17
+ }
18
+
19
+ // ── Test Database Setup ─────────────────────────────────────────────
20
+
21
+ export function createTestDb(): string {
22
+ const dbPath = resolve(__dirname, 'test.db');
23
+ const db = new Database(dbPath);
24
+
25
+ // Drop existing tables
26
+ db.exec('DROP TABLE IF EXISTS order_items');
27
+ db.exec('DROP TABLE IF EXISTS orders');
28
+ db.exec('DROP TABLE IF EXISTS products');
29
+ db.exec('DROP TABLE IF EXISTS customers');
30
+
31
+ // Create schema
32
+ db.exec(`
33
+ CREATE TABLE customers (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ name TEXT NOT NULL,
36
+ email TEXT UNIQUE NOT NULL,
37
+ city TEXT,
38
+ created_at TEXT DEFAULT (datetime('now'))
39
+ );
40
+
41
+ CREATE TABLE products (
42
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
43
+ name TEXT NOT NULL,
44
+ price REAL NOT NULL,
45
+ category TEXT NOT NULL,
46
+ stock INTEGER DEFAULT 0
47
+ );
48
+
49
+ CREATE TABLE orders (
50
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
51
+ customer_id INTEGER NOT NULL,
52
+ total REAL NOT NULL,
53
+ status TEXT DEFAULT 'pending',
54
+ created_at TEXT DEFAULT (datetime('now')),
55
+ FOREIGN KEY (customer_id) REFERENCES customers(id)
56
+ );
57
+
58
+ CREATE TABLE order_items (
59
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
60
+ order_id INTEGER NOT NULL,
61
+ product_id INTEGER NOT NULL,
62
+ quantity INTEGER NOT NULL,
63
+ price REAL NOT NULL,
64
+ FOREIGN KEY (order_id) REFERENCES orders(id),
65
+ FOREIGN KEY (product_id) REFERENCES products(id)
66
+ );
67
+
68
+ CREATE INDEX idx_orders_customer_id ON orders(customer_id);
69
+ CREATE INDEX idx_orders_status ON orders(status);
70
+ CREATE INDEX idx_order_items_order_id ON order_items(order_id);
71
+ CREATE INDEX idx_products_category ON products(category);
72
+ `);
73
+
74
+ // Insert sample data
75
+ const insertCustomer = db.prepare(
76
+ 'INSERT INTO customers (name, email, city) VALUES (?, ?, ?)'
77
+ );
78
+ const insertProduct = db.prepare(
79
+ 'INSERT INTO products (name, price, category, stock) VALUES (?, ?, ?, ?)'
80
+ );
81
+ const insertOrder = db.prepare(
82
+ 'INSERT INTO orders (customer_id, total, status, created_at) VALUES (?, ?, ?, ?)'
83
+ );
84
+ const insertItem = db.prepare(
85
+ 'INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)'
86
+ );
87
+
88
+ // Customers
89
+ const customers = [
90
+ ['Alice Johnson', 'alice@example.com', 'New York'],
91
+ ['Bob Smith', 'bob@example.com', 'San Francisco'],
92
+ ['Carol White', 'carol@example.com', 'Chicago'],
93
+ ['David Brown', 'david@example.com', 'New York'],
94
+ ['Eve Davis', 'eve@example.com', 'San Francisco'],
95
+ ];
96
+
97
+ for (const c of customers) {
98
+ insertCustomer.run(...c);
99
+ }
100
+
101
+ // Products
102
+ const products = [
103
+ ['Laptop', 999.99, 'Electronics', 50],
104
+ ['Phone', 699.99, 'Electronics', 100],
105
+ ['Headphones', 149.99, 'Electronics', 200],
106
+ ['Desk', 299.99, 'Furniture', 30],
107
+ ['Chair', 199.99, 'Furniture', 45],
108
+ ['Book: SQL Mastery', 39.99, 'Books', 500],
109
+ ['Book: Data Science', 49.99, 'Books', 300],
110
+ ['Monitor', 449.99, 'Electronics', 75],
111
+ ];
112
+
113
+ for (const p of products) {
114
+ insertProduct.run(...p);
115
+ }
116
+
117
+ // Orders
118
+ const orders = [
119
+ [1, 1149.98, 'completed', '2026-01-15 10:00:00'],
120
+ [2, 699.99, 'completed', '2026-01-20 14:30:00'],
121
+ [1, 299.99, 'shipped', '2026-02-01 09:15:00'],
122
+ [3, 189.98, 'completed', '2026-02-10 16:45:00'],
123
+ [4, 999.99, 'pending', '2026-03-01 11:00:00'],
124
+ [5, 449.99, 'shipped', '2026-03-10 08:30:00'],
125
+ [2, 39.99, 'completed', '2026-03-15 13:00:00'],
126
+ [3, 1149.98, 'pending', '2026-03-20 17:00:00'],
127
+ ];
128
+
129
+ for (const o of orders) {
130
+ insertOrder.run(...o);
131
+ }
132
+
133
+ // Order items
134
+ const items = [
135
+ [1, 1, 1, 999.99], // Alice: 1 Laptop
136
+ [1, 3, 1, 149.99], // Alice: 1 Headphones
137
+ [2, 2, 1, 699.99], // Bob: 1 Phone
138
+ [3, 4, 1, 299.99], // Alice: 1 Desk
139
+ [4, 6, 2, 39.99], // Carol: 2 SQL books
140
+ [4, 3, 1, 149.99], // Carol: 1 Headphones (wait, total is 189.98 = 2*39.99 + ... let me fix)
141
+ [5, 1, 1, 999.99], // David: 1 Laptop
142
+ [6, 8, 1, 449.99], // Eve: 1 Monitor
143
+ [7, 6, 1, 39.99], // Bob: 1 SQL book
144
+ [8, 1, 1, 999.99], // Carol: 1 Laptop
145
+ [8, 3, 1, 149.99], // Carol: 1 Headphones
146
+ ];
147
+
148
+ for (const i of items) {
149
+ insertItem.run(...i);
150
+ }
151
+
152
+ db.close();
153
+ return dbPath;
154
+ }
155
+
156
+ // ── Query Evals ─────────────────────────────────────────────────────
157
+
158
+ export function runQueryEvals(): EvalSuite {
159
+ const dbPath = createTestDb();
160
+ const db = new Database(dbPath, { readonly: true });
161
+ const results: EvalResult[] = [];
162
+
163
+ // Eval 1: Basic SELECT
164
+ try {
165
+ const rows = db.prepare('SELECT COUNT(*) as cnt FROM customers').all() as Array<{ cnt: number }>;
166
+ const count = rows[0].cnt;
167
+ results.push({
168
+ name: 'Basic SELECT COUNT(*)',
169
+ passed: count === 5,
170
+ details: `Expected 5 customers, got ${count}`,
171
+ });
172
+ } catch (err) {
173
+ results.push({ name: 'Basic SELECT COUNT(*)', passed: false, details: String(err) });
174
+ }
175
+
176
+ // Eval 2: JOIN query
177
+ try {
178
+ const rows = db.prepare(`
179
+ SELECT c.name, COUNT(o.id) as order_count
180
+ FROM customers c
181
+ LEFT JOIN orders o ON c.id = o.customer_id
182
+ GROUP BY c.id
183
+ ORDER BY order_count DESC
184
+ `).all() as Array<{ name: string; order_count: number }>;
185
+
186
+ const alice = rows.find(r => r.name === 'Alice Johnson');
187
+ results.push({
188
+ name: 'JOIN with GROUP BY',
189
+ passed: alice?.order_count === 2 && rows.length === 5,
190
+ details: `Alice has ${alice?.order_count} orders (expected 2), total customers: ${rows.length}`,
191
+ });
192
+ } catch (err) {
193
+ results.push({ name: 'JOIN with GROUP BY', passed: false, details: String(err) });
194
+ }
195
+
196
+ // Eval 3: Aggregate query
197
+ try {
198
+ const rows = db.prepare(`
199
+ SELECT category, SUM(stock) as total_stock, AVG(price) as avg_price
200
+ FROM products
201
+ GROUP BY category
202
+ ORDER BY total_stock DESC
203
+ `).all() as Array<{ category: string; total_stock: number; avg_price: number }>;
204
+
205
+ const books = rows.find(r => r.category === 'Books');
206
+ results.push({
207
+ name: 'Aggregate with GROUP BY',
208
+ passed: books?.total_stock === 800 && rows.length === 3,
209
+ details: `Books stock: ${books?.total_stock} (expected 800), categories: ${rows.length}`,
210
+ });
211
+ } catch (err) {
212
+ results.push({ name: 'Aggregate with GROUP BY', passed: false, details: String(err) });
213
+ }
214
+
215
+ // Eval 4: Subquery
216
+ try {
217
+ const rows = db.prepare(`
218
+ SELECT name FROM customers
219
+ WHERE id IN (SELECT customer_id FROM orders WHERE status = 'pending')
220
+ ORDER BY name
221
+ `).all() as Array<{ name: string }>;
222
+
223
+ const names = rows.map(r => r.name);
224
+ results.push({
225
+ name: 'Subquery with IN',
226
+ passed: names.includes('David Brown') && names.includes('Carol White') && names.length === 2,
227
+ details: `Pending customers: ${names.join(', ')} (expected David Brown, Carol White)`,
228
+ });
229
+ } catch (err) {
230
+ results.push({ name: 'Subquery with IN', passed: false, details: String(err) });
231
+ }
232
+
233
+ // Eval 5: EXPLAIN QUERY PLAN
234
+ try {
235
+ const rows = db.prepare(
236
+ 'EXPLAIN QUERY PLAN SELECT * FROM orders WHERE customer_id = 1'
237
+ ).all() as Array<{ detail: string }>;
238
+
239
+ const usesIndex = rows.some(r => r.detail.includes('idx_orders_customer_id'));
240
+ results.push({
241
+ name: 'EXPLAIN shows index usage',
242
+ passed: usesIndex,
243
+ details: `Index used: ${usesIndex}. Plan: ${rows.map(r => r.detail).join('; ')}`,
244
+ });
245
+ } catch (err) {
246
+ results.push({ name: 'EXPLAIN shows index usage', passed: false, details: String(err) });
247
+ }
248
+
249
+ // Eval 6: Foreign key info
250
+ try {
251
+ const fks = db.pragma('foreign_key_list("orders")') as Array<{
252
+ table: string;
253
+ from: string;
254
+ to: string;
255
+ }>;
256
+
257
+ results.push({
258
+ name: 'Foreign key detection',
259
+ passed: fks.length === 1 && fks[0].table === 'customers',
260
+ details: `FKs: ${fks.map(f => `${f.from} -> ${f.table}(${f.to})`).join(', ')}`,
261
+ });
262
+ } catch (err) {
263
+ results.push({ name: 'Foreign key detection', passed: false, details: String(err) });
264
+ }
265
+
266
+ // Eval 7: Write rejection (simulate free tier)
267
+ try {
268
+ const writePatterns = [
269
+ 'INSERT INTO customers (name, email) VALUES ("test", "test@test.com")',
270
+ 'UPDATE customers SET name = "test" WHERE id = 1',
271
+ 'DELETE FROM customers WHERE id = 1',
272
+ 'DROP TABLE customers',
273
+ 'ALTER TABLE customers ADD COLUMN phone TEXT',
274
+ 'CREATE TABLE test (id INTEGER)',
275
+ ];
276
+
277
+ const writePattern = /^\s*(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|REPLACE|MERGE)\b/i;
278
+ const allBlocked = writePatterns.every(sql => writePattern.test(sql));
279
+
280
+ results.push({
281
+ name: 'Write pattern detection',
282
+ passed: allBlocked,
283
+ details: `All ${writePatterns.length} write patterns correctly detected: ${allBlocked}`,
284
+ });
285
+ } catch (err) {
286
+ results.push({ name: 'Write pattern detection', passed: false, details: String(err) });
287
+ }
288
+
289
+ // Eval 8: Table listing
290
+ try {
291
+ const tables = db.prepare(
292
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
293
+ ).all() as Array<{ name: string }>;
294
+
295
+ const names = tables.map(t => t.name);
296
+ const expected = ['customers', 'order_items', 'orders', 'products'];
297
+ const allPresent = expected.every(n => names.includes(n));
298
+
299
+ results.push({
300
+ name: 'Table listing',
301
+ passed: allPresent && names.length === 4,
302
+ details: `Tables: ${names.join(', ')} (expected: ${expected.join(', ')})`,
303
+ });
304
+ } catch (err) {
305
+ results.push({ name: 'Table listing', passed: false, details: String(err) });
306
+ }
307
+
308
+ // Eval 9: Column info via pragma
309
+ try {
310
+ const cols = db.pragma('table_info("customers")') as Array<{
311
+ name: string;
312
+ type: string;
313
+ notnull: number;
314
+ pk: number;
315
+ }>;
316
+
317
+ const idCol = cols.find(c => c.name === 'id');
318
+ const emailCol = cols.find(c => c.name === 'email');
319
+
320
+ results.push({
321
+ name: 'Column info extraction',
322
+ passed: idCol?.pk === 1 && emailCol?.notnull === 1 && cols.length === 5,
323
+ details: `id is PK: ${idCol?.pk === 1}, email NOT NULL: ${emailCol?.notnull === 1}, column count: ${cols.length}`,
324
+ });
325
+ } catch (err) {
326
+ results.push({ name: 'Column info extraction', passed: false, details: String(err) });
327
+ }
328
+
329
+ // Eval 10: CSV export format
330
+ try {
331
+ const rows = db.prepare('SELECT name, email FROM customers ORDER BY name LIMIT 2').all() as Array<{
332
+ name: string;
333
+ email: string;
334
+ }>;
335
+
336
+ const header = 'name,email';
337
+ const dataRows = rows.map(r => `${r.name},${r.email}`);
338
+ const csv = [header, ...dataRows].join('\n');
339
+
340
+ const expectedCsv = 'name,email\nAlice Johnson,alice@example.com\nBob Smith,bob@example.com';
341
+ results.push({
342
+ name: 'CSV export format',
343
+ passed: csv === expectedCsv,
344
+ details: csv === expectedCsv ? 'CSV matches expected format' : `Got: ${csv}`,
345
+ });
346
+ } catch (err) {
347
+ results.push({ name: 'CSV export format', passed: false, details: String(err) });
348
+ }
349
+
350
+ db.close();
351
+
352
+ const passed = results.filter(r => r.passed).length;
353
+ const failed = results.filter(r => !r.passed).length;
354
+
355
+ return { passed, failed, results };
356
+ }
@@ -0,0 +1,36 @@
1
+ import { runQueryEvals } from './eval-query.js';
2
+
3
+ console.log('========================================');
4
+ console.log(' Database Query MCP -- Evaluation Suite');
5
+ console.log('========================================\n');
6
+
7
+ // ── Query Evals ─────────────────────────────────────────────────────
8
+
9
+ console.log('--- Query & Schema Evals ---\n');
10
+
11
+ const queryResults = runQueryEvals();
12
+
13
+ for (const r of queryResults.results) {
14
+ const icon = r.passed ? 'PASS' : 'FAIL';
15
+ console.log(` [${icon}] ${r.name}`);
16
+ console.log(` ${r.details}`);
17
+ }
18
+
19
+ console.log(`\n Query: ${queryResults.passed}/${queryResults.passed + queryResults.failed} passed\n`);
20
+
21
+ // ── Summary ─────────────────────────────────────────────────────────
22
+
23
+ const totalPassed = queryResults.passed;
24
+ const totalFailed = queryResults.failed;
25
+ const total = totalPassed + totalFailed;
26
+
27
+ console.log('========================================');
28
+ console.log(` TOTAL: ${totalPassed}/${total} passed`);
29
+
30
+ if (totalFailed > 0) {
31
+ console.log(` ${totalFailed} failure(s)`);
32
+ }
33
+
34
+ console.log('========================================');
35
+
36
+ process.exit(totalFailed > 0 ? 1 : 0);
package/evals/test.db ADDED
Binary file
package/glama.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://glama.ai/mcp/schemas/server.json",
3
+ "maintainers": ["claw-factory"]
4
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "truss-db-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Database query MCP server for Claude Code — SQLite, PostgreSQL, and MySQL with AI-powered query tools",
5
+ "type": "module",
6
+ "bin": {
7
+ "truss-db-mcp": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "tsx src/index.ts",
13
+ "eval": "tsx evals/run-evals.ts"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "database",
18
+ "sqlite",
19
+ "postgresql",
20
+ "mysql",
21
+ "sql",
22
+ "query",
23
+ "claude",
24
+ "claude-code",
25
+ "ai"
26
+ ],
27
+ "author": "TRUSS",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.12.0",
31
+ "better-sqlite3": "^11.0.0"
32
+ },
33
+ "peerDependencies": {
34
+ "pg": "^8.12.0",
35
+ "mysql2": "^3.11.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "pg": { "optional": true },
39
+ "mysql2": { "optional": true }
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.5.0",
43
+ "tsx": "^4.19.0",
44
+ "@types/better-sqlite3": "^7.6.0",
45
+ "@types/node": "^22.0.0"
46
+ }
47
+ }
package/smithery.yaml ADDED
@@ -0,0 +1,18 @@
1
+ startCommand:
2
+ type: stdio
3
+ configSchema:
4
+ type: object
5
+ properties:
6
+ TRUSS_LICENSE_KEY:
7
+ type: string
8
+ description: "License key for Pro features (optional)"
9
+ DATABASE_URL:
10
+ type: string
11
+ description: "Database connection URL (postgres:// or mysql://)"
12
+ ANTHROPIC_API_KEY:
13
+ type: string
14
+ description: "API key for AI-powered query features (Pro tier)"
15
+ OPENAI_API_KEY:
16
+ type: string
17
+ description: "Alternative AI API key (Pro tier)"
18
+ required: []
package/src/index.ts ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+
6
+ // Tool registrations
7
+ import { registerConnect } from './tools/connect.js';
8
+ import { registerListTables } from './tools/list-tables.js';
9
+ import { registerDescribeTable } from './tools/describe-table.js';
10
+ import { registerQuery } from './tools/query.js';
11
+ import { registerNaturalLanguageQuery } from './tools/natural-language-query.js';
12
+ import { registerOptimizeQuery } from './tools/optimize-query.js';
13
+ import { registerGenerateMigration } from './tools/generate-migration.js';
14
+ import { registerExportData } from './tools/export-data.js';
15
+
16
+ // Lib
17
+ import { closeConnection } from './lib/connection.js';
18
+ import { getLicenseStatus } from './lib/license.js';
19
+
20
+ const server = new McpServer({
21
+ name: 'truss-db-mcp',
22
+ version: '1.0.0',
23
+ });
24
+
25
+ // ── Status Tool ─────────────────────────────────────────────────────
26
+
27
+ server.tool(
28
+ 'db_status',
29
+ 'Check database connection status and license tier.',
30
+ {},
31
+ async () => {
32
+ const license = await getLicenseStatus();
33
+
34
+ return {
35
+ content: [{
36
+ type: 'text' as const,
37
+ text: JSON.stringify({
38
+ license: {
39
+ tier: license.tier,
40
+ valid: license.valid,
41
+ expires_at: license.expiresAt,
42
+ },
43
+ ...(license.tier === 'free' ? {
44
+ upgrade_info: {
45
+ url: 'https://truss.dev/pricing',
46
+ price: '$25/mo',
47
+ pro_features: [
48
+ 'Natural language to SQL (AI-powered)',
49
+ 'Query optimization & EXPLAIN analysis',
50
+ 'Migration generation',
51
+ 'CSV/JSON data export',
52
+ 'Write operations (INSERT/UPDATE/DELETE)',
53
+ 'PostgreSQL & MySQL support',
54
+ ],
55
+ },
56
+ } : {}),
57
+ }, null, 2),
58
+ }],
59
+ };
60
+ }
61
+ );
62
+
63
+ // ── Free Tier Tools ─────────────────────────────────────────────────
64
+
65
+ registerConnect(server);
66
+ registerListTables(server);
67
+ registerDescribeTable(server);
68
+ registerQuery(server);
69
+
70
+ // ── Pro Tier Tools ──────────────────────────────────────────────────
71
+
72
+ registerNaturalLanguageQuery(server);
73
+ registerOptimizeQuery(server);
74
+ registerGenerateMigration(server);
75
+ registerExportData(server);
76
+
77
+ // ── Start Server ────────────────────────────────────────────────────
78
+
79
+ async function main(): Promise<void> {
80
+ const transport = new StdioServerTransport();
81
+
82
+ // Clean shutdown
83
+ process.on('SIGINT', () => {
84
+ closeConnection();
85
+ process.exit(0);
86
+ });
87
+ process.on('SIGTERM', () => {
88
+ closeConnection();
89
+ process.exit(0);
90
+ });
91
+
92
+ await server.connect(transport);
93
+ }
94
+
95
+ main().catch((err) => {
96
+ console.error('Fatal error starting MCP server:', err);
97
+ closeConnection();
98
+ process.exit(1);
99
+ });