lokicms-plugin-sql 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 (93) hide show
  1. package/dist/federation/cache-layer.d.ts +58 -0
  2. package/dist/federation/cache-layer.d.ts.map +1 -0
  3. package/dist/federation/cache-layer.js +210 -0
  4. package/dist/federation/cache-layer.js.map +1 -0
  5. package/dist/federation/index.d.ts +12 -0
  6. package/dist/federation/index.d.ts.map +1 -0
  7. package/dist/federation/index.js +9 -0
  8. package/dist/federation/index.js.map +1 -0
  9. package/dist/federation/source-manager.d.ts +57 -0
  10. package/dist/federation/source-manager.d.ts.map +1 -0
  11. package/dist/federation/source-manager.js +238 -0
  12. package/dist/federation/source-manager.js.map +1 -0
  13. package/dist/federation/sync-engine.d.ts +68 -0
  14. package/dist/federation/sync-engine.d.ts.map +1 -0
  15. package/dist/federation/sync-engine.js +288 -0
  16. package/dist/federation/sync-engine.js.map +1 -0
  17. package/dist/index.d.ts +80 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +79 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/plugin.d.ts +28 -0
  22. package/dist/plugin.d.ts.map +1 -0
  23. package/dist/plugin.js +798 -0
  24. package/dist/plugin.js.map +1 -0
  25. package/dist/providers/base.d.ts +142 -0
  26. package/dist/providers/base.d.ts.map +1 -0
  27. package/dist/providers/base.js +161 -0
  28. package/dist/providers/base.js.map +1 -0
  29. package/dist/providers/index.d.ts +22 -0
  30. package/dist/providers/index.d.ts.map +1 -0
  31. package/dist/providers/index.js +74 -0
  32. package/dist/providers/index.js.map +1 -0
  33. package/dist/providers/mariadb.d.ts +83 -0
  34. package/dist/providers/mariadb.d.ts.map +1 -0
  35. package/dist/providers/mariadb.js +293 -0
  36. package/dist/providers/mariadb.js.map +1 -0
  37. package/dist/providers/mysql.d.ts +78 -0
  38. package/dist/providers/mysql.d.ts.map +1 -0
  39. package/dist/providers/mysql.js +284 -0
  40. package/dist/providers/mysql.js.map +1 -0
  41. package/dist/providers/postgresql.d.ts +77 -0
  42. package/dist/providers/postgresql.d.ts.map +1 -0
  43. package/dist/providers/postgresql.js +296 -0
  44. package/dist/providers/postgresql.js.map +1 -0
  45. package/dist/providers/sqlite.d.ts +80 -0
  46. package/dist/providers/sqlite.d.ts.map +1 -0
  47. package/dist/providers/sqlite.js +283 -0
  48. package/dist/providers/sqlite.js.map +1 -0
  49. package/dist/query/builder.d.ts +74 -0
  50. package/dist/query/builder.d.ts.map +1 -0
  51. package/dist/query/builder.js +279 -0
  52. package/dist/query/builder.js.map +1 -0
  53. package/dist/query/index.d.ts +10 -0
  54. package/dist/query/index.d.ts.map +1 -0
  55. package/dist/query/index.js +8 -0
  56. package/dist/query/index.js.map +1 -0
  57. package/dist/query/transformer.d.ts +74 -0
  58. package/dist/query/transformer.d.ts.map +1 -0
  59. package/dist/query/transformer.js +236 -0
  60. package/dist/query/transformer.js.map +1 -0
  61. package/dist/types.d.ts +350 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +38 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/vectors/adapter.d.ts +128 -0
  66. package/dist/vectors/adapter.d.ts.map +1 -0
  67. package/dist/vectors/adapter.js +79 -0
  68. package/dist/vectors/adapter.js.map +1 -0
  69. package/dist/vectors/index.d.ts +41 -0
  70. package/dist/vectors/index.d.ts.map +1 -0
  71. package/dist/vectors/index.js +87 -0
  72. package/dist/vectors/index.js.map +1 -0
  73. package/dist/vectors/lokijs-vector.d.ts +112 -0
  74. package/dist/vectors/lokijs-vector.d.ts.map +1 -0
  75. package/dist/vectors/lokijs-vector.js +217 -0
  76. package/dist/vectors/lokijs-vector.js.map +1 -0
  77. package/dist/vectors/mariadb-vector.d.ts +56 -0
  78. package/dist/vectors/mariadb-vector.d.ts.map +1 -0
  79. package/dist/vectors/mariadb-vector.js +263 -0
  80. package/dist/vectors/mariadb-vector.js.map +1 -0
  81. package/dist/vectors/mysql-vector.d.ts +56 -0
  82. package/dist/vectors/mysql-vector.d.ts.map +1 -0
  83. package/dist/vectors/mysql-vector.js +235 -0
  84. package/dist/vectors/mysql-vector.js.map +1 -0
  85. package/dist/vectors/pgvector.d.ts +52 -0
  86. package/dist/vectors/pgvector.d.ts.map +1 -0
  87. package/dist/vectors/pgvector.js +190 -0
  88. package/dist/vectors/pgvector.js.map +1 -0
  89. package/dist/vectors/sqlite-vec.d.ts +80 -0
  90. package/dist/vectors/sqlite-vec.d.ts.map +1 -0
  91. package/dist/vectors/sqlite-vec.js +362 -0
  92. package/dist/vectors/sqlite-vec.js.map +1 -0
  93. package/package.json +64 -0
package/dist/plugin.js ADDED
@@ -0,0 +1,798 @@
1
+ /**
2
+ * LokiCMS SQL Plugin
3
+ *
4
+ * Universal SQL adapter with multi-database support, data federation,
5
+ * split storage, and vector capabilities.
6
+ */
7
+ import { z } from 'zod';
8
+ import { DEFAULT_TABLE_PREFIX, DEFAULT_VECTOR_DIMENSIONS, DEFAULT_CACHE_TTL } from './types.js';
9
+ import { createSourceManager } from './federation/source-manager.js';
10
+ import { createSyncEngine } from './federation/sync-engine.js';
11
+ import { createCacheLayer } from './federation/cache-layer.js';
12
+ import { createVectorAdapter } from './vectors/index.js';
13
+ // Plugin state
14
+ let sourceManager = null;
15
+ let syncEngine = null;
16
+ let cacheLayer = null;
17
+ let vectorAdapter = null;
18
+ let pluginConfig = null;
19
+ /**
20
+ * Get source manager for code usage
21
+ */
22
+ export function getSourceManager() {
23
+ if (!sourceManager) {
24
+ throw new Error('SQL plugin not initialized');
25
+ }
26
+ return sourceManager;
27
+ }
28
+ /**
29
+ * Get sync engine for code usage
30
+ */
31
+ export function getSyncEngine() {
32
+ if (!syncEngine) {
33
+ throw new Error('SQL plugin not initialized');
34
+ }
35
+ return syncEngine;
36
+ }
37
+ /**
38
+ * Get vector adapter for code usage
39
+ */
40
+ export function getVectorAdapter() {
41
+ return vectorAdapter;
42
+ }
43
+ /**
44
+ * LokiCMS SQL Plugin Definition
45
+ */
46
+ const plugin = {
47
+ name: 'lokicms-sql',
48
+ version: '1.0.0',
49
+ description: 'Universal SQL adapter with multi-database support and data federation',
50
+ async register(api) {
51
+ pluginConfig = api.config.sql || {};
52
+ const tablePrefix = pluginConfig.tablePrefix ?? DEFAULT_TABLE_PREFIX;
53
+ api.logger.info('Initializing SQL plugin');
54
+ // Create source manager
55
+ sourceManager = createSourceManager(api.logger, tablePrefix);
56
+ // Set up primary if configured
57
+ if (pluginConfig.primary) {
58
+ try {
59
+ await sourceManager.setPrimary(pluginConfig.primary.provider, pluginConfig.primary.mode, pluginConfig.primary.connection);
60
+ // Create vector adapter if configured
61
+ const primary = sourceManager.getPrimary();
62
+ if (primary && pluginConfig.vectors) {
63
+ vectorAdapter = createVectorAdapter(primary, api.logger, {
64
+ tableName: `${tablePrefix}vectors`,
65
+ dimensions: pluginConfig.vectors.dimensions ?? DEFAULT_VECTOR_DIMENSIONS,
66
+ });
67
+ await vectorAdapter.initialize(pluginConfig.vectors.dimensions ?? DEFAULT_VECTOR_DIMENSIONS);
68
+ }
69
+ }
70
+ catch (error) {
71
+ api.logger.error(`Failed to connect to primary: ${error}`);
72
+ }
73
+ }
74
+ // Add configured sources
75
+ if (pluginConfig.sources) {
76
+ for (const sourceConfig of pluginConfig.sources) {
77
+ try {
78
+ await sourceManager.addSource(sourceConfig);
79
+ if (sourceConfig.autoSync) {
80
+ await sourceManager.connect(sourceConfig.name);
81
+ }
82
+ }
83
+ catch (error) {
84
+ api.logger.error(`Failed to add source ${sourceConfig.name}: ${error}`);
85
+ }
86
+ }
87
+ }
88
+ // Create sync engine (requires LokiJS db from services)
89
+ const lokiDb = api.services.db;
90
+ if (lokiDb) {
91
+ syncEngine = createSyncEngine(sourceManager, lokiDb, api.logger);
92
+ // Create cache layer
93
+ const cacheConfig = pluginConfig.cache ?? {
94
+ enabled: true,
95
+ ttlMinutes: DEFAULT_CACHE_TTL,
96
+ };
97
+ cacheLayer = createCacheLayer(lokiDb, cacheConfig, api.logger);
98
+ // Start auto-sync for enabled sources
99
+ if (pluginConfig.sources) {
100
+ for (const sourceConfig of pluginConfig.sources) {
101
+ if (sourceConfig.autoSync) {
102
+ syncEngine.startAutoSync(sourceConfig.name);
103
+ }
104
+ }
105
+ }
106
+ }
107
+ // Register MCP tools
108
+ registerMcpTools(api);
109
+ api.logger.info('SQL plugin registered');
110
+ },
111
+ };
112
+ /**
113
+ * Register all 15 MCP tools
114
+ */
115
+ function registerMcpTools(api) {
116
+ // ========================================================================
117
+ // Tool 1: sql_status
118
+ // ========================================================================
119
+ api.mcp.registerTool('sql_status', {
120
+ description: 'Get status of all SQL connections including primary, sources, vectors, and cache',
121
+ inputSchema: z.object({}),
122
+ handler: async () => {
123
+ if (!sourceManager) {
124
+ return { error: 'SQL plugin not initialized' };
125
+ }
126
+ const primary = sourceManager.getPrimary();
127
+ let primaryStatus = null;
128
+ if (primary) {
129
+ primaryStatus = await primary.getStatus();
130
+ }
131
+ const sourceStatuses = await sourceManager.getAllStatuses();
132
+ const sources = {};
133
+ for (const [name, status] of sourceStatuses) {
134
+ sources[name] = status;
135
+ }
136
+ return {
137
+ primary: primaryStatus,
138
+ sources,
139
+ vectors: vectorAdapter ? {
140
+ supported: vectorAdapter.isSupported(),
141
+ type: vectorAdapter.type,
142
+ } : null,
143
+ cache: cacheLayer ? cacheLayer.getStats() : null,
144
+ syncJobs: syncEngine ? syncEngine.getJobs().length : 0,
145
+ };
146
+ },
147
+ });
148
+ // ========================================================================
149
+ // Tool 2: sql_connect
150
+ // ========================================================================
151
+ api.mcp.registerTool('sql_connect', {
152
+ description: 'Connect to a configured data source',
153
+ inputSchema: z.object({
154
+ source: z.string().describe('Source name to connect to'),
155
+ }),
156
+ handler: async (input) => {
157
+ if (!sourceManager) {
158
+ return { error: 'SQL plugin not initialized' };
159
+ }
160
+ try {
161
+ await sourceManager.connect(input.source);
162
+ const status = await sourceManager.getStatus(input.source);
163
+ return {
164
+ success: true,
165
+ source: input.source,
166
+ status,
167
+ };
168
+ }
169
+ catch (error) {
170
+ return {
171
+ success: false,
172
+ error: error instanceof Error ? error.message : String(error),
173
+ };
174
+ }
175
+ },
176
+ });
177
+ // ========================================================================
178
+ // Tool 3: sql_disconnect
179
+ // ========================================================================
180
+ api.mcp.registerTool('sql_disconnect', {
181
+ description: 'Disconnect from a data source',
182
+ inputSchema: z.object({
183
+ source: z.string().describe('Source name to disconnect from'),
184
+ }),
185
+ handler: async (input) => {
186
+ if (!sourceManager) {
187
+ return { error: 'SQL plugin not initialized' };
188
+ }
189
+ try {
190
+ // Stop auto-sync if running
191
+ if (syncEngine) {
192
+ syncEngine.stopAutoSync(input.source);
193
+ }
194
+ await sourceManager.disconnect(input.source);
195
+ return {
196
+ success: true,
197
+ source: input.source,
198
+ message: `Disconnected from ${input.source}`,
199
+ };
200
+ }
201
+ catch (error) {
202
+ return {
203
+ success: false,
204
+ error: error instanceof Error ? error.message : String(error),
205
+ };
206
+ }
207
+ },
208
+ });
209
+ // ========================================================================
210
+ // Tool 4: sql_sources
211
+ // ========================================================================
212
+ api.mcp.registerTool('sql_sources', {
213
+ description: 'List all configured data sources',
214
+ inputSchema: z.object({}),
215
+ handler: async () => {
216
+ if (!sourceManager) {
217
+ return { error: 'SQL plugin not initialized' };
218
+ }
219
+ const sources = sourceManager.getAllSources();
220
+ return {
221
+ count: sources.length,
222
+ sources: sources.map((s) => ({
223
+ name: s.name,
224
+ provider: s.config.provider,
225
+ mode: s.config.mode,
226
+ status: s.status,
227
+ tables: Object.keys(s.config.tables),
228
+ autoSync: s.config.autoSync ?? false,
229
+ lastConnected: s.lastConnected,
230
+ error: s.error,
231
+ })),
232
+ };
233
+ },
234
+ });
235
+ // ========================================================================
236
+ // Tool 5: sql_import
237
+ // ========================================================================
238
+ api.mcp.registerTool('sql_import', {
239
+ description: 'Import data from a readonly source into LokiJS collections',
240
+ inputSchema: z.object({
241
+ source: z.string().describe('Source name to import from'),
242
+ table: z.string().optional().describe('Specific table to import (omit for all)'),
243
+ fullRefresh: z.boolean().optional().describe('Clear existing data before import'),
244
+ }),
245
+ handler: async (input) => {
246
+ if (!syncEngine) {
247
+ return { error: 'Sync engine not initialized' };
248
+ }
249
+ try {
250
+ const result = await syncEngine.import({
251
+ source: input.source,
252
+ table: input.table,
253
+ fullRefresh: input.fullRefresh ?? false,
254
+ });
255
+ return {
256
+ success: result.success,
257
+ source: result.source,
258
+ table: result.table,
259
+ collection: result.collection,
260
+ inserted: result.inserted,
261
+ updated: result.updated,
262
+ deleted: result.deleted,
263
+ errors: result.errors,
264
+ duration: `${result.duration}ms`,
265
+ };
266
+ }
267
+ catch (error) {
268
+ return {
269
+ success: false,
270
+ error: error instanceof Error ? error.message : String(error),
271
+ };
272
+ }
273
+ },
274
+ });
275
+ // ========================================================================
276
+ // Tool 6: sql_sync
277
+ // ========================================================================
278
+ api.mcp.registerTool('sql_sync', {
279
+ description: 'Sync data from all or specific source',
280
+ inputSchema: z.object({
281
+ source: z.string().optional().describe('Source to sync (omit for all)'),
282
+ }),
283
+ handler: async (input) => {
284
+ if (!syncEngine) {
285
+ return { error: 'Sync engine not initialized' };
286
+ }
287
+ try {
288
+ const results = input.source
289
+ ? await syncEngine.sync(input.source)
290
+ : await syncEngine.syncAll();
291
+ return {
292
+ success: results.every((r) => r.success),
293
+ synced: results.length,
294
+ results: results.map((r) => ({
295
+ source: r.source,
296
+ table: r.table,
297
+ collection: r.collection,
298
+ inserted: r.inserted,
299
+ updated: r.updated,
300
+ errors: r.errors.length,
301
+ })),
302
+ };
303
+ }
304
+ catch (error) {
305
+ return {
306
+ success: false,
307
+ error: error instanceof Error ? error.message : String(error),
308
+ };
309
+ }
310
+ },
311
+ });
312
+ // ========================================================================
313
+ // Tool 7: sql_query
314
+ // ========================================================================
315
+ api.mcp.registerTool('sql_query', {
316
+ description: 'Execute a SQL query on primary or specified source',
317
+ inputSchema: z.object({
318
+ sql: z.string().describe('SQL query to execute'),
319
+ source: z.string().optional().describe('Source name (omit for primary)'),
320
+ params: z.array(z.unknown()).optional().describe('Query parameters'),
321
+ }),
322
+ handler: async (input) => {
323
+ if (!sourceManager) {
324
+ return { error: 'SQL plugin not initialized' };
325
+ }
326
+ const provider = input.source
327
+ ? sourceManager.getProvider(input.source)
328
+ : sourceManager.getPrimary();
329
+ if (!provider) {
330
+ return { error: input.source ? `Source '${input.source}' not found` : 'No primary configured' };
331
+ }
332
+ if (!provider.isConnected()) {
333
+ return { error: 'Provider not connected' };
334
+ }
335
+ try {
336
+ const result = await provider.query(input.sql, input.params);
337
+ return {
338
+ success: true,
339
+ rowCount: result.rowCount,
340
+ rows: result.rows.slice(0, 100), // Limit response size
341
+ truncated: result.rowCount > 100,
342
+ fields: result.fields,
343
+ };
344
+ }
345
+ catch (error) {
346
+ return {
347
+ success: false,
348
+ error: error instanceof Error ? error.message : String(error),
349
+ };
350
+ }
351
+ },
352
+ });
353
+ // ========================================================================
354
+ // Tool 8: sql_search
355
+ // ========================================================================
356
+ api.mcp.registerTool('sql_search', {
357
+ description: 'Perform full-text search on a table',
358
+ inputSchema: z.object({
359
+ table: z.string().describe('Table to search'),
360
+ query: z.string().describe('Search query text'),
361
+ columns: z.array(z.string()).optional().describe('Columns to search'),
362
+ limit: z.number().optional().describe('Maximum results'),
363
+ source: z.string().optional().describe('Source name (omit for primary)'),
364
+ }),
365
+ handler: async (input) => {
366
+ if (!sourceManager) {
367
+ return { error: 'SQL plugin not initialized' };
368
+ }
369
+ const provider = input.source
370
+ ? sourceManager.getProvider(input.source)
371
+ : sourceManager.getPrimary();
372
+ if (!provider) {
373
+ return { error: 'Provider not found' };
374
+ }
375
+ const caps = await provider.getCapabilities();
376
+ if (!caps.fullText) {
377
+ return { error: 'Full-text search not supported by this provider' };
378
+ }
379
+ const limit = input.limit ?? 20;
380
+ const columns = input.columns ?? ['*'];
381
+ const escapedTable = provider.escapeIdentifier(input.table);
382
+ let sql;
383
+ const params = [];
384
+ // Build provider-specific full-text query
385
+ if (provider.type === 'postgresql') {
386
+ // PostgreSQL tsvector search
387
+ const searchColumns = columns.join(' || \' \' || ');
388
+ sql = `SELECT * FROM ${escapedTable}
389
+ WHERE to_tsvector('english', ${searchColumns}) @@ plainto_tsquery('english', $1)
390
+ LIMIT $2`;
391
+ params.push(input.query, limit);
392
+ }
393
+ else if (provider.type === 'mysql' || provider.type === 'mariadb') {
394
+ // MySQL/MariaDB MATCH AGAINST
395
+ const searchColumns = columns.join(', ');
396
+ sql = `SELECT * FROM ${escapedTable}
397
+ WHERE MATCH(${searchColumns}) AGAINST(? IN NATURAL LANGUAGE MODE)
398
+ LIMIT ?`;
399
+ params.push(input.query, limit);
400
+ }
401
+ else {
402
+ // SQLite LIKE fallback
403
+ const likeConditions = columns.map((col) => `${provider.escapeIdentifier(col)} LIKE ?`);
404
+ sql = `SELECT * FROM ${escapedTable}
405
+ WHERE ${likeConditions.join(' OR ')}
406
+ LIMIT ?`;
407
+ for (const _ of columns) {
408
+ params.push(`%${input.query}%`);
409
+ }
410
+ params.push(limit);
411
+ }
412
+ try {
413
+ const result = await provider.query(sql, params);
414
+ return {
415
+ success: true,
416
+ query: input.query,
417
+ results: result.rows,
418
+ count: result.rowCount,
419
+ };
420
+ }
421
+ catch (error) {
422
+ return {
423
+ success: false,
424
+ error: error instanceof Error ? error.message : String(error),
425
+ };
426
+ }
427
+ },
428
+ });
429
+ // ========================================================================
430
+ // Tool 9: sql_vector_search
431
+ // ========================================================================
432
+ api.mcp.registerTool('sql_vector_search', {
433
+ description: 'Perform semantic vector search',
434
+ inputSchema: z.object({
435
+ vector: z.array(z.number()).describe('Query vector'),
436
+ collection: z.string().optional().describe('Collection to search'),
437
+ limit: z.number().optional().describe('Maximum results'),
438
+ minSimilarity: z.number().optional().describe('Minimum similarity threshold (0-1)'),
439
+ }),
440
+ handler: async (input) => {
441
+ if (!vectorAdapter) {
442
+ return { error: 'Vector adapter not initialized' };
443
+ }
444
+ if (!vectorAdapter.isSupported()) {
445
+ return { error: 'Vector search not supported by current provider' };
446
+ }
447
+ try {
448
+ const results = await vectorAdapter.search(input.vector, {
449
+ collection: input.collection,
450
+ limit: input.limit ?? 10,
451
+ minSimilarity: input.minSimilarity ?? 0,
452
+ });
453
+ return {
454
+ success: true,
455
+ count: results.length,
456
+ results: results.map((r) => ({
457
+ id: r.id,
458
+ entryId: r.entryId,
459
+ collection: r.collection,
460
+ similarity: r.similarity,
461
+ })),
462
+ };
463
+ }
464
+ catch (error) {
465
+ return {
466
+ success: false,
467
+ error: error instanceof Error ? error.message : String(error),
468
+ };
469
+ }
470
+ },
471
+ });
472
+ // ========================================================================
473
+ // Tool 10: sql_vector_index
474
+ // ========================================================================
475
+ api.mcp.registerTool('sql_vector_index', {
476
+ description: 'Store or manage vector indexes',
477
+ inputSchema: z.object({
478
+ action: z.enum(['store', 'delete', 'create_index', 'status']).describe('Action to perform'),
479
+ id: z.string().optional().describe('Vector ID (for store/delete)'),
480
+ entryId: z.string().optional().describe('Entry ID (for store)'),
481
+ collection: z.string().optional().describe('Collection name'),
482
+ vector: z.array(z.number()).optional().describe('Vector data (for store)'),
483
+ indexType: z.string().optional().describe('Index type (hnsw, ivfflat)'),
484
+ }),
485
+ handler: async (input) => {
486
+ if (!vectorAdapter) {
487
+ return { error: 'Vector adapter not initialized' };
488
+ }
489
+ try {
490
+ switch (input.action) {
491
+ case 'store':
492
+ if (!input.id || !input.entryId || !input.collection || !input.vector) {
493
+ return { error: 'Missing required fields for store: id, entryId, collection, vector' };
494
+ }
495
+ await vectorAdapter.store({
496
+ id: input.id,
497
+ entryId: input.entryId,
498
+ collection: input.collection,
499
+ vector: input.vector,
500
+ textHash: '',
501
+ });
502
+ return { success: true, action: 'stored', id: input.id };
503
+ case 'delete':
504
+ if (!input.id) {
505
+ return { error: 'Missing id for delete' };
506
+ }
507
+ const deleted = await vectorAdapter.delete(input.id);
508
+ return { success: deleted, action: 'deleted', id: input.id };
509
+ case 'create_index':
510
+ const indexed = await vectorAdapter.createIndex(input.indexType);
511
+ return { success: indexed, action: 'create_index', indexType: input.indexType ?? 'default' };
512
+ case 'status':
513
+ const hasIndex = await vectorAdapter.hasIndex();
514
+ const count = await vectorAdapter.count(input.collection);
515
+ return {
516
+ success: true,
517
+ supported: vectorAdapter.isSupported(),
518
+ type: vectorAdapter.type,
519
+ hasIndex,
520
+ vectorCount: count,
521
+ collection: input.collection,
522
+ };
523
+ }
524
+ }
525
+ catch (error) {
526
+ return {
527
+ success: false,
528
+ error: error instanceof Error ? error.message : String(error),
529
+ };
530
+ }
531
+ },
532
+ });
533
+ // ========================================================================
534
+ // Tool 11: sql_cache_status
535
+ // ========================================================================
536
+ api.mcp.registerTool('sql_cache_status', {
537
+ description: 'Get cache statistics',
538
+ inputSchema: z.object({}),
539
+ handler: async () => {
540
+ if (!cacheLayer) {
541
+ return { error: 'Cache not initialized' };
542
+ }
543
+ const stats = cacheLayer.getStats();
544
+ return {
545
+ enabled: stats.enabled,
546
+ entries: stats.entries,
547
+ size: `${Math.round(stats.size / 1024)}KB`,
548
+ hits: stats.hits,
549
+ misses: stats.misses,
550
+ hitRate: stats.hits + stats.misses > 0
551
+ ? `${Math.round((stats.hits / (stats.hits + stats.misses)) * 100)}%`
552
+ : 'N/A',
553
+ ttlMinutes: stats.ttlMinutes,
554
+ };
555
+ },
556
+ });
557
+ // ========================================================================
558
+ // Tool 12: sql_cache_clear
559
+ // ========================================================================
560
+ api.mcp.registerTool('sql_cache_clear', {
561
+ description: 'Clear cache entries',
562
+ inputSchema: z.object({
563
+ collection: z.string().optional().describe('Clear only specific collection'),
564
+ }),
565
+ handler: async (input) => {
566
+ if (!cacheLayer) {
567
+ return { error: 'Cache not initialized' };
568
+ }
569
+ if (input.collection) {
570
+ const cleared = cacheLayer.clearCollection(input.collection);
571
+ return { success: true, cleared, collection: input.collection };
572
+ }
573
+ else {
574
+ cacheLayer.clear();
575
+ return { success: true, message: 'All cache cleared' };
576
+ }
577
+ },
578
+ });
579
+ // ========================================================================
580
+ // Tool 13: sql_schema
581
+ // ========================================================================
582
+ api.mcp.registerTool('sql_schema', {
583
+ description: 'View database schema',
584
+ inputSchema: z.object({
585
+ source: z.string().optional().describe('Source name (omit for primary)'),
586
+ table: z.string().optional().describe('Specific table for details'),
587
+ }),
588
+ handler: async (input) => {
589
+ if (!sourceManager) {
590
+ return { error: 'SQL plugin not initialized' };
591
+ }
592
+ const provider = input.source
593
+ ? sourceManager.getProvider(input.source)
594
+ : sourceManager.getPrimary();
595
+ if (!provider || !provider.isConnected()) {
596
+ return { error: 'Provider not connected' };
597
+ }
598
+ try {
599
+ if (input.table) {
600
+ const tableInfo = await provider.getTableInfo(input.table);
601
+ if (!tableInfo) {
602
+ return { error: `Table '${input.table}' not found` };
603
+ }
604
+ return {
605
+ success: true,
606
+ table: tableInfo,
607
+ };
608
+ }
609
+ else {
610
+ const schema = await provider.getSchema();
611
+ return {
612
+ success: true,
613
+ tables: schema.tables.map((t) => ({
614
+ name: t.name,
615
+ schema: t.schema,
616
+ columns: t.columns.length,
617
+ rowCount: t.rowCount,
618
+ })),
619
+ views: schema.views,
620
+ };
621
+ }
622
+ }
623
+ catch (error) {
624
+ return {
625
+ success: false,
626
+ error: error instanceof Error ? error.message : String(error),
627
+ };
628
+ }
629
+ },
630
+ });
631
+ // ========================================================================
632
+ // Tool 14: sql_map_table
633
+ // ========================================================================
634
+ api.mcp.registerTool('sql_map_table', {
635
+ description: 'Map a database table to a LokiJS collection',
636
+ inputSchema: z.object({
637
+ source: z.string().describe('Source name'),
638
+ table: z.string().describe('Table name'),
639
+ collection: z.string().describe('Target LokiJS collection name'),
640
+ primaryKey: z.string().optional().describe('Primary key column'),
641
+ columns: z.array(z.string()).optional().describe('Columns to include'),
642
+ where: z.string().optional().describe('WHERE clause for filtering'),
643
+ syncInterval: z.number().optional().describe('Auto-sync interval in minutes'),
644
+ }),
645
+ handler: async (input) => {
646
+ if (!sourceManager) {
647
+ return { error: 'SQL plugin not initialized' };
648
+ }
649
+ const source = sourceManager.getSource(input.source);
650
+ if (!source) {
651
+ return { error: `Source '${input.source}' not found` };
652
+ }
653
+ // Add table mapping to source config
654
+ source.config.tables[input.table] = {
655
+ collection: input.collection,
656
+ primaryKey: input.primaryKey,
657
+ columns: input.columns,
658
+ where: input.where,
659
+ syncInterval: input.syncInterval,
660
+ };
661
+ return {
662
+ success: true,
663
+ source: input.source,
664
+ table: input.table,
665
+ collection: input.collection,
666
+ mapping: source.config.tables[input.table],
667
+ message: `Mapped ${input.source}.${input.table} → ${input.collection}`,
668
+ };
669
+ },
670
+ });
671
+ // ========================================================================
672
+ // Tool 15: sql_configure
673
+ // ========================================================================
674
+ api.mcp.registerTool('sql_configure', {
675
+ description: 'Configure SQL plugin sources and settings',
676
+ inputSchema: z.object({
677
+ action: z.enum(['add_source', 'remove_source', 'set_primary', 'configure_cache', 'configure_vectors'])
678
+ .describe('Configuration action'),
679
+ // Source options
680
+ name: z.string().optional().describe('Source name'),
681
+ provider: z.enum(['postgresql', 'mysql', 'mariadb', 'sqlite']).optional().describe('Provider type'),
682
+ mode: z.enum(['readwrite', 'readonly']).optional().describe('Connection mode'),
683
+ connectionString: z.string().optional().describe('Connection string'),
684
+ host: z.string().optional().describe('Database host'),
685
+ port: z.number().optional().describe('Database port'),
686
+ database: z.string().optional().describe('Database name'),
687
+ user: z.string().optional().describe('Database user'),
688
+ password: z.string().optional().describe('Database password'),
689
+ // Cache options
690
+ cacheEnabled: z.boolean().optional().describe('Enable cache'),
691
+ cacheTtl: z.number().optional().describe('Cache TTL in minutes'),
692
+ // Vector options
693
+ vectorDimensions: z.number().optional().describe('Vector dimensions'),
694
+ vectorIndex: z.boolean().optional().describe('Enable vector indexing'),
695
+ }),
696
+ handler: async (input) => {
697
+ if (!sourceManager) {
698
+ return { error: 'SQL plugin not initialized' };
699
+ }
700
+ try {
701
+ switch (input.action) {
702
+ case 'add_source':
703
+ if (!input.name || !input.provider) {
704
+ return { error: 'Missing required fields: name, provider' };
705
+ }
706
+ const sourceConfig = {
707
+ name: input.name,
708
+ provider: input.provider,
709
+ mode: input.mode ?? 'readonly',
710
+ connection: input.connectionString
711
+ ? { connectionString: input.connectionString }
712
+ : {
713
+ host: input.host ?? 'localhost',
714
+ port: input.port,
715
+ database: input.database ?? '',
716
+ user: input.user ?? '',
717
+ password: input.password ?? '',
718
+ },
719
+ tables: {},
720
+ autoSync: false,
721
+ };
722
+ await sourceManager.addSource(sourceConfig);
723
+ await sourceManager.connect(input.name);
724
+ return {
725
+ success: true,
726
+ action: 'add_source',
727
+ name: input.name,
728
+ status: await sourceManager.getStatus(input.name),
729
+ };
730
+ case 'remove_source':
731
+ if (!input.name) {
732
+ return { error: 'Missing source name' };
733
+ }
734
+ await sourceManager.removeSource(input.name);
735
+ return { success: true, action: 'remove_source', name: input.name };
736
+ case 'set_primary':
737
+ if (!input.provider) {
738
+ return { error: 'Missing provider' };
739
+ }
740
+ await sourceManager.setPrimary(input.provider, input.mode ?? 'readwrite', input.connectionString
741
+ ? { connectionString: input.connectionString }
742
+ : {
743
+ host: input.host ?? 'localhost',
744
+ port: input.port,
745
+ database: input.database ?? '',
746
+ user: input.user ?? '',
747
+ password: input.password ?? '',
748
+ });
749
+ const primary = sourceManager.getPrimary();
750
+ return {
751
+ success: true,
752
+ action: 'set_primary',
753
+ provider: input.provider,
754
+ status: primary ? await primary.getStatus() : null,
755
+ };
756
+ case 'configure_cache':
757
+ if (!cacheLayer) {
758
+ return { error: 'Cache not initialized' };
759
+ }
760
+ if (input.cacheEnabled !== undefined) {
761
+ cacheLayer.setEnabled(input.cacheEnabled);
762
+ }
763
+ return {
764
+ success: true,
765
+ action: 'configure_cache',
766
+ stats: cacheLayer.getStats(),
767
+ };
768
+ case 'configure_vectors':
769
+ if (!vectorAdapter) {
770
+ return { error: 'Vector adapter not initialized' };
771
+ }
772
+ if (input.vectorIndex) {
773
+ await vectorAdapter.createIndex();
774
+ }
775
+ return {
776
+ success: true,
777
+ action: 'configure_vectors',
778
+ supported: vectorAdapter.isSupported(),
779
+ hasIndex: await vectorAdapter.hasIndex(),
780
+ };
781
+ default:
782
+ return { error: `Unknown action: ${input.action}` };
783
+ }
784
+ }
785
+ catch (error) {
786
+ return {
787
+ success: false,
788
+ error: error instanceof Error ? error.message : String(error),
789
+ };
790
+ }
791
+ },
792
+ });
793
+ api.logger.info('SQL plugin registered 15 MCP tools: sql_status, sql_connect, sql_disconnect, sql_sources, ' +
794
+ 'sql_import, sql_sync, sql_query, sql_search, sql_vector_search, sql_vector_index, ' +
795
+ 'sql_cache_status, sql_cache_clear, sql_schema, sql_map_table, sql_configure');
796
+ }
797
+ export default plugin;
798
+ //# sourceMappingURL=plugin.js.map