aidex-mcp 1.4.1

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 (76) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/LICENSE +21 -0
  3. package/MCP-API-REFERENCE.md +690 -0
  4. package/README.md +314 -0
  5. package/build/commands/files.d.ts +28 -0
  6. package/build/commands/files.js +124 -0
  7. package/build/commands/index.d.ts +14 -0
  8. package/build/commands/index.js +14 -0
  9. package/build/commands/init.d.ts +24 -0
  10. package/build/commands/init.js +396 -0
  11. package/build/commands/link.d.ts +45 -0
  12. package/build/commands/link.js +167 -0
  13. package/build/commands/note.d.ts +29 -0
  14. package/build/commands/note.js +105 -0
  15. package/build/commands/query.d.ts +36 -0
  16. package/build/commands/query.js +176 -0
  17. package/build/commands/scan.d.ts +25 -0
  18. package/build/commands/scan.js +104 -0
  19. package/build/commands/session.d.ts +52 -0
  20. package/build/commands/session.js +216 -0
  21. package/build/commands/signature.d.ts +52 -0
  22. package/build/commands/signature.js +171 -0
  23. package/build/commands/summary.d.ts +56 -0
  24. package/build/commands/summary.js +324 -0
  25. package/build/commands/update.d.ts +36 -0
  26. package/build/commands/update.js +273 -0
  27. package/build/constants.d.ts +10 -0
  28. package/build/constants.js +10 -0
  29. package/build/db/database.d.ts +69 -0
  30. package/build/db/database.js +126 -0
  31. package/build/db/index.d.ts +7 -0
  32. package/build/db/index.js +6 -0
  33. package/build/db/queries.d.ts +163 -0
  34. package/build/db/queries.js +273 -0
  35. package/build/db/schema.sql +136 -0
  36. package/build/index.d.ts +13 -0
  37. package/build/index.js +74 -0
  38. package/build/parser/extractor.d.ts +41 -0
  39. package/build/parser/extractor.js +249 -0
  40. package/build/parser/index.d.ts +7 -0
  41. package/build/parser/index.js +7 -0
  42. package/build/parser/languages/c.d.ts +28 -0
  43. package/build/parser/languages/c.js +70 -0
  44. package/build/parser/languages/cpp.d.ts +28 -0
  45. package/build/parser/languages/cpp.js +91 -0
  46. package/build/parser/languages/csharp.d.ts +32 -0
  47. package/build/parser/languages/csharp.js +97 -0
  48. package/build/parser/languages/go.d.ts +28 -0
  49. package/build/parser/languages/go.js +83 -0
  50. package/build/parser/languages/index.d.ts +21 -0
  51. package/build/parser/languages/index.js +107 -0
  52. package/build/parser/languages/java.d.ts +28 -0
  53. package/build/parser/languages/java.js +58 -0
  54. package/build/parser/languages/php.d.ts +28 -0
  55. package/build/parser/languages/php.js +75 -0
  56. package/build/parser/languages/python.d.ts +28 -0
  57. package/build/parser/languages/python.js +67 -0
  58. package/build/parser/languages/ruby.d.ts +28 -0
  59. package/build/parser/languages/ruby.js +68 -0
  60. package/build/parser/languages/rust.d.ts +28 -0
  61. package/build/parser/languages/rust.js +73 -0
  62. package/build/parser/languages/typescript.d.ts +28 -0
  63. package/build/parser/languages/typescript.js +82 -0
  64. package/build/parser/tree-sitter.d.ts +30 -0
  65. package/build/parser/tree-sitter.js +132 -0
  66. package/build/server/mcp-server.d.ts +7 -0
  67. package/build/server/mcp-server.js +36 -0
  68. package/build/server/tools.d.ts +18 -0
  69. package/build/server/tools.js +1245 -0
  70. package/build/viewer/git-status.d.ts +25 -0
  71. package/build/viewer/git-status.js +163 -0
  72. package/build/viewer/index.d.ts +5 -0
  73. package/build/viewer/index.js +5 -0
  74. package/build/viewer/server.d.ts +12 -0
  75. package/build/viewer/server.js +1122 -0
  76. package/package.json +66 -0
@@ -0,0 +1,1245 @@
1
+ /**
2
+ * MCP Tool definitions and handlers for AiDex
3
+ */
4
+ import { existsSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { init, query, signature, signatures, update, remove, summary, tree, describe, link, unlink, listLinks, scan, files, note, session, formatSessionTime, formatDuration } from '../commands/index.js';
7
+ import { openDatabase } from '../db/index.js';
8
+ import { startViewer, stopViewer } from '../viewer/index.js';
9
+ import { PRODUCT_NAME, INDEX_DIR, TOOL_PREFIX } from '../constants.js';
10
+ /**
11
+ * Register all available tools
12
+ */
13
+ export function registerTools() {
14
+ return [
15
+ {
16
+ name: `${TOOL_PREFIX}init`,
17
+ description: `Initialize ${PRODUCT_NAME} indexing for a project. Scans all source files and builds a searchable index of identifiers, methods, types, and signatures.`,
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ path: {
22
+ type: 'string',
23
+ description: 'Absolute path to the project directory to index',
24
+ },
25
+ name: {
26
+ type: 'string',
27
+ description: 'Optional project name (defaults to directory name)',
28
+ },
29
+ exclude: {
30
+ type: 'array',
31
+ items: { type: 'string' },
32
+ description: 'Additional glob patterns to exclude (e.g., ["**/test/**"])',
33
+ },
34
+ },
35
+ required: ['path'],
36
+ },
37
+ },
38
+ {
39
+ name: `${TOOL_PREFIX}query`,
40
+ description: `Search for terms/identifiers in the ${PRODUCT_NAME} index. Returns file locations where the term appears. PREFERRED over Grep/Glob for code searches when ${INDEX_DIR}/ exists - faster and more precise. Use this instead of grep for finding functions, classes, variables by name.`,
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ path: {
45
+ type: 'string',
46
+ description: `Path to project with ${INDEX_DIR} directory`,
47
+ },
48
+ term: {
49
+ type: 'string',
50
+ description: 'The term to search for',
51
+ },
52
+ mode: {
53
+ type: 'string',
54
+ enum: ['exact', 'contains', 'starts_with'],
55
+ description: 'Search mode: exact match, contains, or starts_with (default: exact)',
56
+ },
57
+ file_filter: {
58
+ type: 'string',
59
+ description: 'Glob pattern to filter files (e.g., "src/commands/**")',
60
+ },
61
+ type_filter: {
62
+ type: 'array',
63
+ items: { type: 'string' },
64
+ description: 'Filter by line type: code, comment, method, struct, property',
65
+ },
66
+ modified_since: {
67
+ type: 'string',
68
+ description: 'Only include lines modified after this time. Supports: "2h" (hours), "30m" (minutes), "1d" (days), "1w" (weeks), or ISO date string',
69
+ },
70
+ modified_before: {
71
+ type: 'string',
72
+ description: 'Only include lines modified before this time. Same format as modified_since',
73
+ },
74
+ limit: {
75
+ type: 'number',
76
+ description: 'Maximum number of results (default: 100)',
77
+ },
78
+ },
79
+ required: ['path', 'term'],
80
+ },
81
+ },
82
+ {
83
+ name: `${TOOL_PREFIX}status`,
84
+ description: `Get ${PRODUCT_NAME} server status and statistics for an indexed project`,
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ path: {
89
+ type: 'string',
90
+ description: `Path to project with ${INDEX_DIR} directory (optional, shows server status if not provided)`,
91
+ },
92
+ },
93
+ required: [],
94
+ },
95
+ },
96
+ {
97
+ name: `${TOOL_PREFIX}signature`,
98
+ description: 'Get the signature of a single file: header comments, types (classes/structs/interfaces), and method prototypes. Use this INSTEAD of reading entire files when you only need to know what methods/classes exist. Much faster than Read tool for understanding file structure.',
99
+ inputSchema: {
100
+ type: 'object',
101
+ properties: {
102
+ path: {
103
+ type: 'string',
104
+ description: `Path to project with ${INDEX_DIR} directory`,
105
+ },
106
+ file: {
107
+ type: 'string',
108
+ description: 'Relative path to the file within the project (e.g., "src/Core/Engine.cs")',
109
+ },
110
+ },
111
+ required: ['path', 'file'],
112
+ },
113
+ },
114
+ {
115
+ name: `${TOOL_PREFIX}signatures`,
116
+ description: 'Get signatures for multiple files at once using glob pattern or file list. Returns types and method prototypes. Use INSTEAD of reading multiple files when exploring codebase structure. Much more efficient than multiple Read calls.',
117
+ inputSchema: {
118
+ type: 'object',
119
+ properties: {
120
+ path: {
121
+ type: 'string',
122
+ description: `Path to project with ${INDEX_DIR} directory`,
123
+ },
124
+ pattern: {
125
+ type: 'string',
126
+ description: 'Glob pattern to match files (e.g., "src/Core/**/*.cs", "**/*.ts")',
127
+ },
128
+ files: {
129
+ type: 'array',
130
+ items: { type: 'string' },
131
+ description: 'Explicit list of relative file paths (alternative to pattern)',
132
+ },
133
+ },
134
+ required: ['path'],
135
+ },
136
+ },
137
+ {
138
+ name: `${TOOL_PREFIX}update`,
139
+ description: `Re-index a single file. Use after editing a file to update the ${PRODUCT_NAME} index. If the file is new, it will be added to the index. If unchanged (same hash), no update is performed.`,
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ path: {
144
+ type: 'string',
145
+ description: `Path to project with ${INDEX_DIR} directory`,
146
+ },
147
+ file: {
148
+ type: 'string',
149
+ description: 'Relative path to the file to update (e.g., "src/Core/Engine.cs")',
150
+ },
151
+ },
152
+ required: ['path', 'file'],
153
+ },
154
+ },
155
+ {
156
+ name: `${TOOL_PREFIX}remove`,
157
+ description: `Remove a file from the ${PRODUCT_NAME} index. Use when a file has been deleted from the project.`,
158
+ inputSchema: {
159
+ type: 'object',
160
+ properties: {
161
+ path: {
162
+ type: 'string',
163
+ description: `Path to project with ${INDEX_DIR} directory`,
164
+ },
165
+ file: {
166
+ type: 'string',
167
+ description: 'Relative path to the file to remove (e.g., "src/OldFile.cs")',
168
+ },
169
+ },
170
+ required: ['path', 'file'],
171
+ },
172
+ },
173
+ {
174
+ name: `${TOOL_PREFIX}summary`,
175
+ description: 'Get project summary including auto-detected entry points, main types, and languages. Also returns content from summary.md if it exists.',
176
+ inputSchema: {
177
+ type: 'object',
178
+ properties: {
179
+ path: {
180
+ type: 'string',
181
+ description: `Path to project with ${INDEX_DIR} directory`,
182
+ },
183
+ },
184
+ required: ['path'],
185
+ },
186
+ },
187
+ {
188
+ name: `${TOOL_PREFIX}tree`,
189
+ description: 'Get the indexed file tree. Optionally filter by subdirectory, limit depth, or include statistics per file.',
190
+ inputSchema: {
191
+ type: 'object',
192
+ properties: {
193
+ path: {
194
+ type: 'string',
195
+ description: `Path to project with ${INDEX_DIR} directory`,
196
+ },
197
+ subpath: {
198
+ type: 'string',
199
+ description: 'Subdirectory to list (default: project root)',
200
+ },
201
+ depth: {
202
+ type: 'number',
203
+ description: 'Maximum depth to traverse (default: unlimited)',
204
+ },
205
+ include_stats: {
206
+ type: 'boolean',
207
+ description: 'Include item/method/type counts per file',
208
+ },
209
+ },
210
+ required: ['path'],
211
+ },
212
+ },
213
+ {
214
+ name: `${TOOL_PREFIX}describe`,
215
+ description: 'Add or update a section in the project summary (summary.md). Use to document project purpose, architecture, key concepts, or patterns.',
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: {
219
+ path: {
220
+ type: 'string',
221
+ description: `Path to project with ${INDEX_DIR} directory`,
222
+ },
223
+ section: {
224
+ type: 'string',
225
+ enum: ['purpose', 'architecture', 'concepts', 'patterns', 'notes'],
226
+ description: 'Section to update',
227
+ },
228
+ content: {
229
+ type: 'string',
230
+ description: 'Content to add to the section',
231
+ },
232
+ replace: {
233
+ type: 'boolean',
234
+ description: 'Replace existing section content (default: append)',
235
+ },
236
+ },
237
+ required: ['path', 'section', 'content'],
238
+ },
239
+ },
240
+ {
241
+ name: `${TOOL_PREFIX}link`,
242
+ description: `Link a dependency project to enable cross-project queries. The dependency must have its own ${INDEX_DIR} index.`,
243
+ inputSchema: {
244
+ type: 'object',
245
+ properties: {
246
+ path: {
247
+ type: 'string',
248
+ description: `Path to current project with ${INDEX_DIR} directory`,
249
+ },
250
+ dependency: {
251
+ type: 'string',
252
+ description: 'Path to dependency project to link',
253
+ },
254
+ name: {
255
+ type: 'string',
256
+ description: 'Optional display name for the dependency',
257
+ },
258
+ },
259
+ required: ['path', 'dependency'],
260
+ },
261
+ },
262
+ {
263
+ name: `${TOOL_PREFIX}unlink`,
264
+ description: 'Remove a linked dependency project.',
265
+ inputSchema: {
266
+ type: 'object',
267
+ properties: {
268
+ path: {
269
+ type: 'string',
270
+ description: `Path to current project with ${INDEX_DIR} directory`,
271
+ },
272
+ dependency: {
273
+ type: 'string',
274
+ description: 'Path to dependency project to unlink',
275
+ },
276
+ },
277
+ required: ['path', 'dependency'],
278
+ },
279
+ },
280
+ {
281
+ name: `${TOOL_PREFIX}links`,
282
+ description: 'List all linked dependency projects.',
283
+ inputSchema: {
284
+ type: 'object',
285
+ properties: {
286
+ path: {
287
+ type: 'string',
288
+ description: `Path to project with ${INDEX_DIR} directory`,
289
+ },
290
+ },
291
+ required: ['path'],
292
+ },
293
+ },
294
+ {
295
+ name: `${TOOL_PREFIX}scan`,
296
+ description: `Scan a directory tree to find all projects with ${PRODUCT_NAME} indexes (${INDEX_DIR} directories). Use this to discover which projects are already indexed before using Grep/Glob - indexed projects should use ${TOOL_PREFIX}query instead.`,
297
+ inputSchema: {
298
+ type: 'object',
299
+ properties: {
300
+ path: {
301
+ type: 'string',
302
+ description: `Root path to scan for ${INDEX_DIR} directories`,
303
+ },
304
+ max_depth: {
305
+ type: 'number',
306
+ description: 'Maximum directory depth to scan (default: 10)',
307
+ },
308
+ },
309
+ required: ['path'],
310
+ },
311
+ },
312
+ {
313
+ name: `${TOOL_PREFIX}files`,
314
+ description: 'List all files and directories in the indexed project. Returns the complete project structure with file types (code, config, doc, asset, test, other) and whether each file is indexed for code search. Use modified_since to find files changed in this session.',
315
+ inputSchema: {
316
+ type: 'object',
317
+ properties: {
318
+ path: {
319
+ type: 'string',
320
+ description: `Path to project with ${INDEX_DIR} directory`,
321
+ },
322
+ type: {
323
+ type: 'string',
324
+ enum: ['dir', 'code', 'config', 'doc', 'asset', 'test', 'other'],
325
+ description: 'Filter by file type',
326
+ },
327
+ pattern: {
328
+ type: 'string',
329
+ description: 'Glob pattern to filter files (e.g., "src/**/*.ts")',
330
+ },
331
+ modified_since: {
332
+ type: 'string',
333
+ description: 'Only files indexed after this time. Supports: "2h", "30m", "1d", "1w", or ISO date. Use to find files changed this session.',
334
+ },
335
+ },
336
+ required: ['path'],
337
+ },
338
+ },
339
+ {
340
+ name: `${TOOL_PREFIX}note`,
341
+ description: `Read or write a session note for the project. Use this to leave reminders for the next session (e.g., "Test the glob fix", "Refactor X"). Notes persist in the ${PRODUCT_NAME} database and are shown when querying the project.`,
342
+ inputSchema: {
343
+ type: 'object',
344
+ properties: {
345
+ path: {
346
+ type: 'string',
347
+ description: `Path to project with ${INDEX_DIR} directory`,
348
+ },
349
+ note: {
350
+ type: 'string',
351
+ description: 'Note to save. If omitted, reads the current note.',
352
+ },
353
+ append: {
354
+ type: 'boolean',
355
+ description: 'If true, appends to existing note instead of replacing (default: false)',
356
+ },
357
+ clear: {
358
+ type: 'boolean',
359
+ description: 'If true, clears the note (default: false)',
360
+ },
361
+ },
362
+ required: ['path'],
363
+ },
364
+ },
365
+ {
366
+ name: `${TOOL_PREFIX}session`,
367
+ description: `Start or check an ${PRODUCT_NAME} session. Call this at the beginning of a new chat session to: (1) detect files changed externally since last session, (2) auto-reindex modified files, (3) get session note and last session times. Returns info for "What did we do last session?" queries.`,
368
+ inputSchema: {
369
+ type: 'object',
370
+ properties: {
371
+ path: {
372
+ type: 'string',
373
+ description: `Path to project with ${INDEX_DIR} directory`,
374
+ },
375
+ },
376
+ required: ['path'],
377
+ },
378
+ },
379
+ {
380
+ name: `${TOOL_PREFIX}viewer`,
381
+ description: 'Open an interactive project tree viewer in the browser. Shows the indexed file structure with clickable nodes - click on a file to see its signature (header comments, types, methods). Uses a local HTTP server with WebSocket for live updates.',
382
+ inputSchema: {
383
+ type: 'object',
384
+ properties: {
385
+ path: {
386
+ type: 'string',
387
+ description: `Path to project with ${INDEX_DIR} directory`,
388
+ },
389
+ action: {
390
+ type: 'string',
391
+ enum: ['open', 'close'],
392
+ description: 'Action to perform: open (default) or close the viewer',
393
+ },
394
+ },
395
+ required: ['path'],
396
+ },
397
+ },
398
+ ];
399
+ }
400
+ /**
401
+ * Handle tool calls
402
+ */
403
+ export async function handleToolCall(name, args) {
404
+ try {
405
+ switch (name) {
406
+ case `${TOOL_PREFIX}init`:
407
+ return await handleInit(args);
408
+ case `${TOOL_PREFIX}query`:
409
+ return handleQuery(args);
410
+ case `${TOOL_PREFIX}status`:
411
+ return handleStatus(args);
412
+ case `${TOOL_PREFIX}signature`:
413
+ return handleSignature(args);
414
+ case `${TOOL_PREFIX}signatures`:
415
+ return handleSignatures(args);
416
+ case `${TOOL_PREFIX}update`:
417
+ return handleUpdate(args);
418
+ case `${TOOL_PREFIX}remove`:
419
+ return handleRemove(args);
420
+ case `${TOOL_PREFIX}summary`:
421
+ return handleSummary(args);
422
+ case `${TOOL_PREFIX}tree`:
423
+ return handleTree(args);
424
+ case `${TOOL_PREFIX}describe`:
425
+ return handleDescribe(args);
426
+ case `${TOOL_PREFIX}link`:
427
+ return handleLink(args);
428
+ case `${TOOL_PREFIX}unlink`:
429
+ return handleUnlink(args);
430
+ case `${TOOL_PREFIX}links`:
431
+ return handleLinks(args);
432
+ case `${TOOL_PREFIX}scan`:
433
+ return handleScan(args);
434
+ case `${TOOL_PREFIX}files`:
435
+ return handleFiles(args);
436
+ case `${TOOL_PREFIX}note`:
437
+ return handleNote(args);
438
+ case `${TOOL_PREFIX}session`:
439
+ return handleSession(args);
440
+ case `${TOOL_PREFIX}viewer`:
441
+ return handleViewer(args);
442
+ default:
443
+ return {
444
+ content: [
445
+ {
446
+ type: 'text',
447
+ text: `Unknown tool: ${name}`,
448
+ },
449
+ ],
450
+ };
451
+ }
452
+ }
453
+ catch (error) {
454
+ return {
455
+ content: [
456
+ {
457
+ type: 'text',
458
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
459
+ },
460
+ ],
461
+ };
462
+ }
463
+ }
464
+ /**
465
+ * Handle init
466
+ */
467
+ async function handleInit(args) {
468
+ const path = args.path;
469
+ if (!path) {
470
+ return {
471
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
472
+ };
473
+ }
474
+ const result = await init({
475
+ path,
476
+ name: args.name,
477
+ exclude: args.exclude,
478
+ });
479
+ if (result.success) {
480
+ let message = `āœ“ ${PRODUCT_NAME} initialized for project\n\n`;
481
+ message += `Database: ${result.indexPath}/index.db\n`;
482
+ message += `Files indexed: ${result.filesIndexed}`;
483
+ if (result.filesSkipped > 0) {
484
+ message += ` (${result.filesSkipped} unchanged, skipped)`;
485
+ }
486
+ message += `\n`;
487
+ if (result.filesRemoved > 0) {
488
+ message += `Files removed: ${result.filesRemoved} (now excluded)\n`;
489
+ }
490
+ message += `Items found: ${result.itemsFound}\n`;
491
+ message += `Methods found: ${result.methodsFound}\n`;
492
+ message += `Types found: ${result.typesFound}\n`;
493
+ message += `Duration: ${result.durationMs}ms`;
494
+ if (result.errors.length > 0) {
495
+ message += `\n\nWarnings (${result.errors.length}):\n`;
496
+ message += result.errors.slice(0, 10).map(e => ` - ${e}`).join('\n');
497
+ if (result.errors.length > 10) {
498
+ message += `\n ... and ${result.errors.length - 10} more`;
499
+ }
500
+ }
501
+ return {
502
+ content: [{ type: 'text', text: message }],
503
+ };
504
+ }
505
+ else {
506
+ return {
507
+ content: [{ type: 'text', text: `Error: ${result.errors.join(', ')}` }],
508
+ };
509
+ }
510
+ }
511
+ /**
512
+ * Handle query
513
+ */
514
+ function handleQuery(args) {
515
+ const path = args.path;
516
+ const term = args.term;
517
+ if (!path || !term) {
518
+ return {
519
+ content: [{ type: 'text', text: 'Error: path and term parameters are required' }],
520
+ };
521
+ }
522
+ const result = query({
523
+ path,
524
+ term,
525
+ mode: args.mode ?? 'exact',
526
+ fileFilter: args.file_filter,
527
+ typeFilter: args.type_filter,
528
+ modifiedSince: args.modified_since,
529
+ modifiedBefore: args.modified_before,
530
+ limit: args.limit,
531
+ });
532
+ if (!result.success) {
533
+ return {
534
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
535
+ };
536
+ }
537
+ if (result.matches.length === 0) {
538
+ return {
539
+ content: [{ type: 'text', text: `No matches found for "${term}" (mode: ${result.mode})` }],
540
+ };
541
+ }
542
+ // Format results
543
+ let message = `Found ${result.totalMatches} match(es) for "${term}" (mode: ${result.mode})`;
544
+ if (result.truncated) {
545
+ message += ` [showing first ${result.matches.length}]`;
546
+ }
547
+ message += '\n\n';
548
+ // Group by file
549
+ const byFile = new Map();
550
+ for (const match of result.matches) {
551
+ if (!byFile.has(match.file)) {
552
+ byFile.set(match.file, []);
553
+ }
554
+ byFile.get(match.file).push({ lineNumber: match.lineNumber, lineType: match.lineType });
555
+ }
556
+ for (const [file, lines] of byFile) {
557
+ message += `${file}\n`;
558
+ for (const line of lines) {
559
+ message += ` :${line.lineNumber} (${line.lineType})\n`;
560
+ }
561
+ }
562
+ return {
563
+ content: [{ type: 'text', text: message.trimEnd() }],
564
+ };
565
+ }
566
+ /**
567
+ * Handle status
568
+ */
569
+ function handleStatus(args) {
570
+ const path = args.path;
571
+ if (!path) {
572
+ return {
573
+ content: [
574
+ {
575
+ type: 'text',
576
+ text: JSON.stringify({
577
+ status: 'running',
578
+ version: '0.1.0',
579
+ message: `${PRODUCT_NAME} MCP server is running. Use ${TOOL_PREFIX}init to index a project.`,
580
+ }, null, 2),
581
+ },
582
+ ],
583
+ };
584
+ }
585
+ // Check if project has index
586
+ const indexDir = join(path, INDEX_DIR);
587
+ const dbPath = join(indexDir, 'index.db');
588
+ if (!existsSync(dbPath)) {
589
+ return {
590
+ content: [
591
+ {
592
+ type: 'text',
593
+ text: `No ${PRODUCT_NAME} index found at ${path}. Run ${TOOL_PREFIX}init first.`,
594
+ },
595
+ ],
596
+ };
597
+ }
598
+ // Open database and get stats
599
+ const db = openDatabase(dbPath, true);
600
+ const stats = db.getStats();
601
+ const projectName = db.getMetadata('project_name') ?? 'Unknown';
602
+ const schemaVersion = db.getMetadata('schema_version') ?? 'Unknown';
603
+ db.close();
604
+ return {
605
+ content: [
606
+ {
607
+ type: 'text',
608
+ text: JSON.stringify({
609
+ project: projectName,
610
+ schemaVersion,
611
+ statistics: stats,
612
+ databasePath: dbPath,
613
+ }, null, 2),
614
+ },
615
+ ],
616
+ };
617
+ }
618
+ /**
619
+ * Handle signature
620
+ */
621
+ function handleSignature(args) {
622
+ const path = args.path;
623
+ const file = args.file;
624
+ if (!path || !file) {
625
+ return {
626
+ content: [{ type: 'text', text: 'Error: path and file parameters are required' }],
627
+ };
628
+ }
629
+ const result = signature({ path, file });
630
+ if (!result.success) {
631
+ return {
632
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
633
+ };
634
+ }
635
+ // Format output
636
+ let message = `# Signature: ${result.file}\n\n`;
637
+ // Header comments
638
+ if (result.headerComments) {
639
+ message += `## Header Comments\n\`\`\`\n${result.headerComments}\n\`\`\`\n\n`;
640
+ }
641
+ // Types
642
+ if (result.types.length > 0) {
643
+ message += `## Types (${result.types.length})\n`;
644
+ for (const t of result.types) {
645
+ message += `- **${t.kind}** \`${t.name}\` (line ${t.lineNumber})\n`;
646
+ }
647
+ message += '\n';
648
+ }
649
+ // Methods
650
+ if (result.methods.length > 0) {
651
+ message += `## Methods (${result.methods.length})\n`;
652
+ for (const m of result.methods) {
653
+ const modifiers = [];
654
+ if (m.visibility)
655
+ modifiers.push(m.visibility);
656
+ if (m.isStatic)
657
+ modifiers.push('static');
658
+ if (m.isAsync)
659
+ modifiers.push('async');
660
+ const prefix = modifiers.length > 0 ? `[${modifiers.join(' ')}] ` : '';
661
+ message += `- ${prefix}\`${m.prototype}\` (line ${m.lineNumber})\n`;
662
+ }
663
+ }
664
+ if (result.types.length === 0 && result.methods.length === 0 && !result.headerComments) {
665
+ message += '_No signature data found for this file._\n';
666
+ }
667
+ return {
668
+ content: [{ type: 'text', text: message.trimEnd() }],
669
+ };
670
+ }
671
+ /**
672
+ * Handle signatures
673
+ */
674
+ function handleSignatures(args) {
675
+ const path = args.path;
676
+ const pattern = args.pattern;
677
+ const files = args.files;
678
+ if (!path) {
679
+ return {
680
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
681
+ };
682
+ }
683
+ if (!pattern && (!files || files.length === 0)) {
684
+ return {
685
+ content: [{ type: 'text', text: 'Error: either pattern or files parameter is required' }],
686
+ };
687
+ }
688
+ const result = signatures({ path, pattern, files });
689
+ if (!result.success) {
690
+ return {
691
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
692
+ };
693
+ }
694
+ if (result.signatures.length === 0) {
695
+ const searchDesc = pattern ? `pattern "${pattern}"` : `files list`;
696
+ return {
697
+ content: [{ type: 'text', text: `No files found matching ${searchDesc}` }],
698
+ };
699
+ }
700
+ // Format output - summary view
701
+ let message = `# Signatures (${result.totalFiles} files)\n\n`;
702
+ for (const sig of result.signatures) {
703
+ if (!sig.success) {
704
+ message += `## ${sig.file}\n_Error: ${sig.error}_\n\n`;
705
+ continue;
706
+ }
707
+ message += `## ${sig.file}\n`;
708
+ // Compact summary
709
+ const parts = [];
710
+ if (sig.types.length > 0) {
711
+ const typesSummary = sig.types.map(t => `${t.kind} ${t.name}`).join(', ');
712
+ parts.push(`Types: ${typesSummary}`);
713
+ }
714
+ if (sig.methods.length > 0) {
715
+ parts.push(`Methods: ${sig.methods.length}`);
716
+ }
717
+ if (parts.length > 0) {
718
+ message += parts.join(' | ') + '\n';
719
+ }
720
+ // List methods compactly
721
+ if (sig.methods.length > 0) {
722
+ for (const m of sig.methods) {
723
+ const modifiers = [];
724
+ if (m.visibility)
725
+ modifiers.push(m.visibility);
726
+ if (m.isStatic)
727
+ modifiers.push('static');
728
+ if (m.isAsync)
729
+ modifiers.push('async');
730
+ const prefix = modifiers.length > 0 ? `[${modifiers.join(' ')}] ` : '';
731
+ message += ` - ${prefix}${m.prototype} :${m.lineNumber}\n`;
732
+ }
733
+ }
734
+ message += '\n';
735
+ }
736
+ return {
737
+ content: [{ type: 'text', text: message.trimEnd() }],
738
+ };
739
+ }
740
+ /**
741
+ * Handle update
742
+ */
743
+ function handleUpdate(args) {
744
+ const path = args.path;
745
+ const file = args.file;
746
+ if (!path || !file) {
747
+ return {
748
+ content: [{ type: 'text', text: 'Error: path and file parameters are required' }],
749
+ };
750
+ }
751
+ const result = update({ path, file });
752
+ if (!result.success) {
753
+ return {
754
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
755
+ };
756
+ }
757
+ // Check if file was unchanged
758
+ if (result.error === 'File unchanged (hash match)') {
759
+ return {
760
+ content: [{ type: 'text', text: `File unchanged: ${result.file} (hash match, no update needed)` }],
761
+ };
762
+ }
763
+ let message = `āœ“ Updated: ${result.file}\n`;
764
+ message += ` Items: +${result.itemsAdded} / -${result.itemsRemoved}\n`;
765
+ message += ` Methods: ${result.methodsUpdated}\n`;
766
+ message += ` Types: ${result.typesUpdated}\n`;
767
+ message += ` Duration: ${result.durationMs}ms`;
768
+ return {
769
+ content: [{ type: 'text', text: message }],
770
+ };
771
+ }
772
+ /**
773
+ * Handle remove
774
+ */
775
+ function handleRemove(args) {
776
+ const path = args.path;
777
+ const file = args.file;
778
+ if (!path || !file) {
779
+ return {
780
+ content: [{ type: 'text', text: 'Error: path and file parameters are required' }],
781
+ };
782
+ }
783
+ const result = remove({ path, file });
784
+ if (!result.success) {
785
+ return {
786
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
787
+ };
788
+ }
789
+ if (!result.removed) {
790
+ return {
791
+ content: [{ type: 'text', text: `File not in index: ${result.file}` }],
792
+ };
793
+ }
794
+ return {
795
+ content: [{ type: 'text', text: `āœ“ Removed from index: ${result.file}` }],
796
+ };
797
+ }
798
+ /**
799
+ * Handle summary
800
+ */
801
+ function handleSummary(args) {
802
+ const path = args.path;
803
+ if (!path) {
804
+ return {
805
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
806
+ };
807
+ }
808
+ const result = summary({ path });
809
+ if (!result.success) {
810
+ return {
811
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
812
+ };
813
+ }
814
+ let message = `# Project: ${result.name}\n\n`;
815
+ // Auto-generated info
816
+ message += `## Overview\n`;
817
+ message += `- **Files indexed:** ${result.autoGenerated.fileCount}\n`;
818
+ message += `- **Languages:** ${result.autoGenerated.languages.join(', ') || 'None detected'}\n`;
819
+ if (result.autoGenerated.entryPoints.length > 0) {
820
+ message += `- **Entry points:** ${result.autoGenerated.entryPoints.join(', ')}\n`;
821
+ }
822
+ if (result.autoGenerated.mainTypes.length > 0) {
823
+ message += `\n## Main Types\n`;
824
+ for (const t of result.autoGenerated.mainTypes) {
825
+ message += `- ${t}\n`;
826
+ }
827
+ }
828
+ // User-provided summary content
829
+ if (result.content) {
830
+ message += `\n---\n\n${result.content}`;
831
+ }
832
+ return {
833
+ content: [{ type: 'text', text: message.trimEnd() }],
834
+ };
835
+ }
836
+ /**
837
+ * Handle tree
838
+ */
839
+ function handleTree(args) {
840
+ const path = args.path;
841
+ if (!path) {
842
+ return {
843
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
844
+ };
845
+ }
846
+ const result = tree({
847
+ path,
848
+ subpath: args.subpath,
849
+ depth: args.depth,
850
+ includeStats: args.include_stats,
851
+ });
852
+ if (!result.success) {
853
+ return {
854
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
855
+ };
856
+ }
857
+ if (result.entries.length === 0) {
858
+ return {
859
+ content: [{ type: 'text', text: `No files found in ${result.root}` }],
860
+ };
861
+ }
862
+ let message = `# File Tree: ${result.root} (${result.totalFiles} files)\n\n`;
863
+ for (const entry of result.entries) {
864
+ if (entry.type === 'directory') {
865
+ message += `šŸ“ ${entry.path}/\n`;
866
+ }
867
+ else {
868
+ let stats = '';
869
+ if (entry.itemCount !== undefined) {
870
+ stats = ` [${entry.itemCount} items, ${entry.methodCount} methods, ${entry.typeCount} types]`;
871
+ }
872
+ message += ` šŸ“„ ${entry.path}${stats}\n`;
873
+ }
874
+ }
875
+ return {
876
+ content: [{ type: 'text', text: message.trimEnd() }],
877
+ };
878
+ }
879
+ /**
880
+ * Handle describe
881
+ */
882
+ function handleDescribe(args) {
883
+ const path = args.path;
884
+ const section = args.section;
885
+ const content = args.content;
886
+ if (!path || !section || !content) {
887
+ return {
888
+ content: [{ type: 'text', text: 'Error: path, section, and content parameters are required' }],
889
+ };
890
+ }
891
+ const validSections = ['purpose', 'architecture', 'concepts', 'patterns', 'notes'];
892
+ if (!validSections.includes(section)) {
893
+ return {
894
+ content: [{ type: 'text', text: `Error: section must be one of: ${validSections.join(', ')}` }],
895
+ };
896
+ }
897
+ const result = describe({
898
+ path,
899
+ section: section,
900
+ content,
901
+ replace: args.replace,
902
+ });
903
+ if (!result.success) {
904
+ return {
905
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
906
+ };
907
+ }
908
+ return {
909
+ content: [{ type: 'text', text: `āœ“ Updated section: ${result.section}` }],
910
+ };
911
+ }
912
+ /**
913
+ * Handle link
914
+ */
915
+ function handleLink(args) {
916
+ const path = args.path;
917
+ const dependency = args.dependency;
918
+ if (!path || !dependency) {
919
+ return {
920
+ content: [{ type: 'text', text: 'Error: path and dependency parameters are required' }],
921
+ };
922
+ }
923
+ const result = link({
924
+ path,
925
+ dependency,
926
+ name: args.name,
927
+ });
928
+ if (!result.success) {
929
+ return {
930
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
931
+ };
932
+ }
933
+ return {
934
+ content: [{ type: 'text', text: `āœ“ Linked: ${result.name} (${result.filesAvailable} files)` }],
935
+ };
936
+ }
937
+ /**
938
+ * Handle unlink
939
+ */
940
+ function handleUnlink(args) {
941
+ const path = args.path;
942
+ const dependency = args.dependency;
943
+ if (!path || !dependency) {
944
+ return {
945
+ content: [{ type: 'text', text: 'Error: path and dependency parameters are required' }],
946
+ };
947
+ }
948
+ const result = unlink({ path, dependency });
949
+ if (!result.success) {
950
+ return {
951
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
952
+ };
953
+ }
954
+ if (!result.removed) {
955
+ return {
956
+ content: [{ type: 'text', text: `Dependency not found: ${dependency}` }],
957
+ };
958
+ }
959
+ return {
960
+ content: [{ type: 'text', text: `āœ“ Unlinked: ${dependency}` }],
961
+ };
962
+ }
963
+ /**
964
+ * Handle links
965
+ */
966
+ function handleLinks(args) {
967
+ const path = args.path;
968
+ if (!path) {
969
+ return {
970
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
971
+ };
972
+ }
973
+ const result = listLinks({ path });
974
+ if (!result.success) {
975
+ return {
976
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
977
+ };
978
+ }
979
+ if (result.dependencies.length === 0) {
980
+ return {
981
+ content: [{ type: 'text', text: 'No linked dependencies.' }],
982
+ };
983
+ }
984
+ let message = `# Linked Dependencies (${result.dependencies.length})\n\n`;
985
+ for (const dep of result.dependencies) {
986
+ const status = dep.available ? 'āœ“' : 'āœ—';
987
+ const name = dep.name ?? 'unnamed';
988
+ message += `${status} **${name}**\n`;
989
+ message += ` Path: ${dep.path}\n`;
990
+ message += ` Files: ${dep.filesAvailable}\n`;
991
+ if (!dep.available) {
992
+ message += ` āš ļø Not available (index missing)\n`;
993
+ }
994
+ message += '\n';
995
+ }
996
+ return {
997
+ content: [{ type: 'text', text: message.trimEnd() }],
998
+ };
999
+ }
1000
+ /**
1001
+ * Handle scan
1002
+ */
1003
+ function handleScan(args) {
1004
+ const path = args.path;
1005
+ if (!path) {
1006
+ return {
1007
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
1008
+ };
1009
+ }
1010
+ const result = scan({
1011
+ path,
1012
+ maxDepth: args.max_depth,
1013
+ });
1014
+ if (!result.success) {
1015
+ return {
1016
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
1017
+ };
1018
+ }
1019
+ if (result.projects.length === 0) {
1020
+ return {
1021
+ content: [{ type: 'text', text: `No ${PRODUCT_NAME} indexes found in ${result.searchPath}\n(scanned ${result.scannedDirs} directories)` }],
1022
+ };
1023
+ }
1024
+ let message = `# ${PRODUCT_NAME} Indexes Found (${result.projects.length})\n\n`;
1025
+ message += `Scanned: ${result.searchPath} (${result.scannedDirs} directories)\n\n`;
1026
+ for (const proj of result.projects) {
1027
+ message += `## ${proj.name}\n`;
1028
+ message += `- **Path:** ${proj.path}\n`;
1029
+ message += `- **Files:** ${proj.files} | **Items:** ${proj.items} | **Methods:** ${proj.methods} | **Types:** ${proj.types}\n`;
1030
+ message += `- **Last indexed:** ${proj.lastIndexed}\n`;
1031
+ message += '\n';
1032
+ }
1033
+ return {
1034
+ content: [{ type: 'text', text: message.trimEnd() }],
1035
+ };
1036
+ }
1037
+ /**
1038
+ * Handle files
1039
+ */
1040
+ function handleFiles(args) {
1041
+ const path = args.path;
1042
+ if (!path) {
1043
+ return {
1044
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
1045
+ };
1046
+ }
1047
+ const result = files({
1048
+ path,
1049
+ type: args.type,
1050
+ pattern: args.pattern,
1051
+ modifiedSince: args.modified_since,
1052
+ });
1053
+ if (!result.success) {
1054
+ return {
1055
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
1056
+ };
1057
+ }
1058
+ if (result.files.length === 0) {
1059
+ return {
1060
+ content: [{ type: 'text', text: 'No files found in project.' }],
1061
+ };
1062
+ }
1063
+ // Build summary
1064
+ let message = `# Project Files (${result.totalFiles})\n\n`;
1065
+ // Type statistics
1066
+ message += `## By Type\n`;
1067
+ for (const [type, count] of Object.entries(result.byType).sort()) {
1068
+ message += `- **${type}:** ${count}\n`;
1069
+ }
1070
+ message += '\n';
1071
+ // Group files by directory
1072
+ const byDir = new Map();
1073
+ for (const file of result.files) {
1074
+ const dir = file.path.includes('/') ? file.path.substring(0, file.path.lastIndexOf('/')) : '.';
1075
+ if (!byDir.has(dir)) {
1076
+ byDir.set(dir, []);
1077
+ }
1078
+ byDir.get(dir).push(file);
1079
+ }
1080
+ // List files (limit output for large projects)
1081
+ const MAX_ENTRIES = 200;
1082
+ let entriesShown = 0;
1083
+ message += `## Files\n`;
1084
+ for (const [dir, dirFiles] of [...byDir.entries()].sort()) {
1085
+ if (entriesShown >= MAX_ENTRIES) {
1086
+ message += `\n... and ${result.totalFiles - entriesShown} more files\n`;
1087
+ break;
1088
+ }
1089
+ // Show directory
1090
+ if (dir !== '.') {
1091
+ message += `\nšŸ“ ${dir}/\n`;
1092
+ entriesShown++;
1093
+ }
1094
+ // Show files in directory
1095
+ for (const file of dirFiles) {
1096
+ if (entriesShown >= MAX_ENTRIES)
1097
+ break;
1098
+ const fileName = file.path.includes('/') ? file.path.substring(file.path.lastIndexOf('/') + 1) : file.path;
1099
+ const icon = file.type === 'dir' ? 'šŸ“' : 'šŸ“„';
1100
+ const indexed = file.indexed ? ' āœ“' : '';
1101
+ message += ` ${icon} ${fileName} (${file.type})${indexed}\n`;
1102
+ entriesShown++;
1103
+ }
1104
+ }
1105
+ return {
1106
+ content: [{ type: 'text', text: message.trimEnd() }],
1107
+ };
1108
+ }
1109
+ /**
1110
+ * Handle note
1111
+ */
1112
+ function handleNote(args) {
1113
+ const path = args.path;
1114
+ if (!path) {
1115
+ return {
1116
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
1117
+ };
1118
+ }
1119
+ const result = note({
1120
+ path,
1121
+ note: args.note,
1122
+ append: args.append,
1123
+ clear: args.clear,
1124
+ });
1125
+ if (!result.success) {
1126
+ return {
1127
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
1128
+ };
1129
+ }
1130
+ switch (result.action) {
1131
+ case 'clear':
1132
+ return {
1133
+ content: [{ type: 'text', text: 'āœ“ Session note cleared.' }],
1134
+ };
1135
+ case 'write':
1136
+ return {
1137
+ content: [{ type: 'text', text: `āœ“ Session note saved:\n\n${result.note}` }],
1138
+ };
1139
+ case 'append':
1140
+ return {
1141
+ content: [{ type: 'text', text: `āœ“ Appended to session note:\n\n${result.note}` }],
1142
+ };
1143
+ case 'read':
1144
+ default:
1145
+ if (!result.note) {
1146
+ return {
1147
+ content: [{ type: 'text', text: 'No session note set for this project.' }],
1148
+ };
1149
+ }
1150
+ return {
1151
+ content: [{ type: 'text', text: `šŸ“ Session Note:\n\n${result.note}` }],
1152
+ };
1153
+ }
1154
+ }
1155
+ /**
1156
+ * Handle session
1157
+ */
1158
+ function handleSession(args) {
1159
+ const path = args.path;
1160
+ if (!path) {
1161
+ return {
1162
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
1163
+ };
1164
+ }
1165
+ const result = session({ path });
1166
+ if (!result.success) {
1167
+ return {
1168
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
1169
+ };
1170
+ }
1171
+ let message = '';
1172
+ // Session status
1173
+ if (result.isNewSession) {
1174
+ message += 'šŸ†• **New Session Started**\n\n';
1175
+ }
1176
+ else {
1177
+ message += 'ā–¶ļø **Session Continued**\n\n';
1178
+ }
1179
+ // Last session info
1180
+ if (result.sessionInfo.lastSessionStart && result.sessionInfo.lastSessionEnd) {
1181
+ message += '## Last Session\n';
1182
+ message += `- **Start:** ${formatSessionTime(result.sessionInfo.lastSessionStart)}\n`;
1183
+ message += `- **End:** ${formatSessionTime(result.sessionInfo.lastSessionEnd)}\n`;
1184
+ message += `- **Duration:** ${formatDuration(result.sessionInfo.lastSessionStart, result.sessionInfo.lastSessionEnd)}\n`;
1185
+ message += `\nšŸ’” Query last session changes with:\n\`${TOOL_PREFIX}query({ term: "...", modified_since: "${result.sessionInfo.lastSessionStart}", modified_before: "${result.sessionInfo.lastSessionEnd}" })\`\n\n`;
1186
+ }
1187
+ // External changes
1188
+ if (result.externalChanges.length > 0) {
1189
+ message += '## External Changes Detected\n';
1190
+ message += `Found ${result.externalChanges.length} file(s) changed outside of session:\n\n`;
1191
+ for (const change of result.externalChanges) {
1192
+ const icon = change.reason === 'deleted' ? 'šŸ—‘ļø' : 'āœļø';
1193
+ message += `- ${icon} ${change.path} (${change.reason})\n`;
1194
+ }
1195
+ if (result.reindexed.length > 0) {
1196
+ message += `\nāœ… Auto-reindexed ${result.reindexed.length} file(s)\n`;
1197
+ }
1198
+ message += '\n';
1199
+ }
1200
+ // Session note
1201
+ if (result.note) {
1202
+ message += '## šŸ“ Session Note\n';
1203
+ message += result.note + '\n';
1204
+ }
1205
+ return {
1206
+ content: [{ type: 'text', text: message.trimEnd() }],
1207
+ };
1208
+ }
1209
+ /**
1210
+ * Handle viewer
1211
+ */
1212
+ async function handleViewer(args) {
1213
+ const path = args.path;
1214
+ const action = args.action || 'open';
1215
+ if (!path) {
1216
+ return {
1217
+ content: [{ type: 'text', text: 'Error: path parameter is required' }],
1218
+ };
1219
+ }
1220
+ // Check if index directory exists
1221
+ const indexPath = join(path, INDEX_DIR);
1222
+ if (!existsSync(indexPath)) {
1223
+ return {
1224
+ content: [{ type: 'text', text: `Error: No ${INDEX_DIR} directory found at ${path}. Run ${TOOL_PREFIX}init first.` }],
1225
+ };
1226
+ }
1227
+ if (action === 'close') {
1228
+ const message = stopViewer();
1229
+ return {
1230
+ content: [{ type: 'text', text: message }],
1231
+ };
1232
+ }
1233
+ try {
1234
+ const message = await startViewer(path);
1235
+ return {
1236
+ content: [{ type: 'text', text: `šŸ–„ļø ${message}` }],
1237
+ };
1238
+ }
1239
+ catch (error) {
1240
+ return {
1241
+ content: [{ type: 'text', text: `Error starting viewer: ${error instanceof Error ? error.message : String(error)}` }],
1242
+ };
1243
+ }
1244
+ }
1245
+ //# sourceMappingURL=tools.js.map