@execufunction/mcp-server 0.2.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.
package/dist/index.js ADDED
@@ -0,0 +1,1868 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * ExecuFunction MCP Server
5
+ *
6
+ * A Model Context Protocol server that exposes ExecuFunction tools to IDE clients
7
+ * like Cursor, Claude Desktop, and VS Code.
8
+ *
9
+ * Configuration via environment variables:
10
+ * - EXF_API_URL: ExecuFunction API URL (e.g., https://execufunction.com)
11
+ * - EXF_PAT: Personal Access Token for authentication
12
+ *
13
+ * Usage:
14
+ * 1. Create a PAT in ExecuFunction Settings > Developer > Access Tokens
15
+ * 2. Configure your MCP client (e.g., claude_desktop_config.json):
16
+ * {
17
+ * "mcpServers": {
18
+ * "execufunction": {
19
+ * "command": "npx",
20
+ * "args": ["@execufunction/mcp-server"],
21
+ * "env": {
22
+ * "EXF_API_URL": "https://execufunction.com",
23
+ * "EXF_PAT": "exf_pat_..."
24
+ * }
25
+ * }
26
+ * }
27
+ * }
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
31
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
32
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
33
+ const exfClient_js_1 = require("./exfClient.js");
34
+ const localIndexer_js_1 = require("./localIndexer.js");
35
+ const incrementalIndexer_js_1 = require("./incrementalIndexer.js");
36
+ const gitService_js_1 = require("./gitService.js");
37
+ // =============================================================================
38
+ // Tool Definitions
39
+ // =============================================================================
40
+ const DATASETS_ENABLED = (process.env.DATASETS_ENABLED ?? 'false').toLowerCase() === 'true';
41
+ const DATASET_ONTOLOGY_ENABLED = (process.env.DATASET_ONTOLOGY_ENABLED
42
+ ?? process.env.DATASETS_ENABLED
43
+ ?? 'false').toLowerCase() === 'true';
44
+ const DATASET_ACTIONS_ENABLED = ((process.env.DATASET_ACTIONS_ENABLED ?? 'false').toLowerCase() === 'true') && DATASET_ONTOLOGY_ENABLED;
45
+ const TOOLS = [
46
+ {
47
+ name: 'project_list',
48
+ description: 'List projects',
49
+ inputSchema: {
50
+ type: 'object',
51
+ properties: {
52
+ status: {
53
+ type: 'string',
54
+ enum: ['planning', 'active', 'on_hold', 'blocked', 'completed', 'archived'],
55
+ },
56
+ includeArchived: { type: 'boolean' },
57
+ },
58
+ },
59
+ },
60
+ {
61
+ name: 'project_create',
62
+ description: 'Create a project',
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ name: { type: 'string' },
67
+ summary: { type: 'string' },
68
+ status: {
69
+ type: 'string',
70
+ enum: ['planning', 'active', 'on_hold', 'blocked', 'completed'],
71
+ },
72
+ emoji: { type: 'string', description: 'Single emoji' },
73
+ },
74
+ required: ['name'],
75
+ },
76
+ },
77
+ {
78
+ name: 'project_update',
79
+ description: 'Update a project',
80
+ inputSchema: {
81
+ type: 'object',
82
+ properties: {
83
+ projectId: { type: 'string' },
84
+ name: { type: 'string' },
85
+ summary: { type: 'string' },
86
+ status: {
87
+ type: 'string',
88
+ enum: ['planning', 'active', 'on_hold', 'blocked', 'completed'],
89
+ },
90
+ emoji: { type: 'string', description: 'Single emoji' },
91
+ },
92
+ required: ['projectId'],
93
+ },
94
+ },
95
+ {
96
+ name: 'project_archive',
97
+ description: 'Archive a project',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ projectId: { type: 'string' },
102
+ },
103
+ required: ['projectId'],
104
+ },
105
+ },
106
+ {
107
+ name: 'project_get_context',
108
+ description: 'Get project context (tasks, notes, members, signals)',
109
+ inputSchema: {
110
+ type: 'object',
111
+ properties: {
112
+ projectId: { type: 'string', description: 'Project UUID' },
113
+ },
114
+ required: ['projectId'],
115
+ },
116
+ },
117
+ {
118
+ name: 'task_list',
119
+ description: 'List tasks',
120
+ inputSchema: {
121
+ type: 'object',
122
+ properties: {
123
+ projectId: { type: 'string' },
124
+ status: {
125
+ type: 'string',
126
+ enum: ['inbox', 'next_action', 'in_progress', 'waiting_for', 'completed', 'archived'],
127
+ },
128
+ limit: { type: 'number' },
129
+ },
130
+ },
131
+ },
132
+ {
133
+ name: 'task_create',
134
+ description: 'Create a task',
135
+ inputSchema: {
136
+ type: 'object',
137
+ properties: {
138
+ title: { type: 'string' },
139
+ description: { type: 'string' },
140
+ priority: {
141
+ type: 'string',
142
+ enum: ['do_now', 'schedule', 'delegate', 'someday'],
143
+ },
144
+ dueAt: { type: 'string', description: 'ISO 8601' },
145
+ projectId: { type: 'string' },
146
+ idempotencyKey: { type: 'string' },
147
+ },
148
+ required: ['title'],
149
+ },
150
+ },
151
+ {
152
+ name: 'task_get',
153
+ description: 'Get a single task with full details',
154
+ inputSchema: {
155
+ type: 'object',
156
+ properties: {
157
+ taskId: { type: 'string' },
158
+ },
159
+ required: ['taskId'],
160
+ },
161
+ },
162
+ {
163
+ name: 'task_update',
164
+ description: 'Update a task (title, description, status, priority, due date, project)',
165
+ inputSchema: {
166
+ type: 'object',
167
+ properties: {
168
+ taskId: { type: 'string' },
169
+ title: { type: 'string' },
170
+ description: { type: 'string' },
171
+ status: {
172
+ type: 'string',
173
+ enum: ['inbox', 'next_action', 'in_progress', 'waiting_for', 'completed', 'archived'],
174
+ },
175
+ priority: {
176
+ type: 'string',
177
+ enum: ['do_now', 'schedule', 'delegate', 'someday'],
178
+ },
179
+ dueAt: { type: 'string', description: 'ISO 8601' },
180
+ projectId: { type: 'string' },
181
+ idempotencyKey: { type: 'string' },
182
+ },
183
+ required: ['taskId'],
184
+ },
185
+ },
186
+ {
187
+ name: 'task_delete',
188
+ description: 'Delete a task',
189
+ inputSchema: {
190
+ type: 'object',
191
+ properties: {
192
+ taskId: { type: 'string' },
193
+ },
194
+ required: ['taskId'],
195
+ },
196
+ },
197
+ {
198
+ name: 'task_complete',
199
+ description: 'Mark task complete',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: {
203
+ taskId: { type: 'string' },
204
+ idempotencyKey: { type: 'string' },
205
+ },
206
+ required: ['taskId'],
207
+ },
208
+ },
209
+ {
210
+ name: 'note_search',
211
+ description: 'Search notes',
212
+ inputSchema: {
213
+ type: 'object',
214
+ properties: {
215
+ query: { type: 'string' },
216
+ projectId: { type: 'string' },
217
+ limit: { type: 'number' },
218
+ },
219
+ required: ['query'],
220
+ },
221
+ },
222
+ {
223
+ name: 'note_list',
224
+ description: 'List notes',
225
+ inputSchema: {
226
+ type: 'object',
227
+ properties: {
228
+ projectId: { type: 'string' },
229
+ noteType: {
230
+ type: 'string',
231
+ enum: ['note', 'concept', 'meeting', 'reference', 'daily', 'dataset'],
232
+ },
233
+ limit: { type: 'number' },
234
+ },
235
+ },
236
+ },
237
+ {
238
+ name: 'note_get',
239
+ description: 'Get a single note with full content',
240
+ inputSchema: {
241
+ type: 'object',
242
+ properties: {
243
+ noteId: { type: 'string' },
244
+ },
245
+ required: ['noteId'],
246
+ },
247
+ },
248
+ {
249
+ name: 'note_create',
250
+ description: 'Create a note',
251
+ inputSchema: {
252
+ type: 'object',
253
+ properties: {
254
+ title: { type: 'string' },
255
+ content: { type: 'string', description: 'Markdown' },
256
+ projectId: { type: 'string' },
257
+ noteType: {
258
+ type: 'string',
259
+ enum: ['note', 'concept', 'meeting', 'reference', 'daily', 'dataset'],
260
+ },
261
+ idempotencyKey: { type: 'string' },
262
+ },
263
+ required: ['title'],
264
+ },
265
+ },
266
+ {
267
+ name: 'note_update',
268
+ description: 'Update a note',
269
+ inputSchema: {
270
+ type: 'object',
271
+ properties: {
272
+ noteId: { type: 'string' },
273
+ title: { type: 'string' },
274
+ content: { type: 'string', description: 'Markdown' },
275
+ noteType: {
276
+ type: 'string',
277
+ enum: ['note', 'concept', 'meeting', 'reference', 'daily', 'dataset'],
278
+ },
279
+ idempotencyKey: { type: 'string' },
280
+ },
281
+ required: ['noteId'],
282
+ },
283
+ },
284
+ {
285
+ name: 'note_delete',
286
+ description: 'Delete a note',
287
+ inputSchema: {
288
+ type: 'object',
289
+ properties: {
290
+ noteId: { type: 'string' },
291
+ },
292
+ required: ['noteId'],
293
+ },
294
+ },
295
+ {
296
+ name: 'dataset_list',
297
+ description: 'List datasets',
298
+ inputSchema: {
299
+ type: 'object',
300
+ properties: {
301
+ limit: { type: 'number' },
302
+ },
303
+ },
304
+ },
305
+ {
306
+ name: 'dataset_create',
307
+ description: 'Create a dataset',
308
+ inputSchema: {
309
+ type: 'object',
310
+ properties: {
311
+ title: { type: 'string' },
312
+ description: { type: 'string' },
313
+ fields: { type: 'array', items: { type: 'object' } },
314
+ metadata: { type: 'object' },
315
+ },
316
+ required: ['title'],
317
+ },
318
+ },
319
+ {
320
+ name: 'dataset_query',
321
+ description: 'Query dataset records by filters/sorts',
322
+ inputSchema: {
323
+ type: 'object',
324
+ properties: {
325
+ datasetId: { type: 'string' },
326
+ filters: { type: 'array', items: { type: 'object' } },
327
+ sorts: { type: 'array', items: { type: 'object' } },
328
+ limit: { type: 'number' },
329
+ cursor: { type: 'string' },
330
+ },
331
+ required: ['datasetId'],
332
+ },
333
+ },
334
+ {
335
+ name: 'dataset_mutate',
336
+ description: 'Mutate dataset records (create/update/delete)',
337
+ inputSchema: {
338
+ type: 'object',
339
+ properties: {
340
+ datasetId: { type: 'string' },
341
+ operation: { type: 'string', enum: ['create', 'update', 'delete'] },
342
+ records: { type: 'array', items: { type: 'object' } },
343
+ recordId: { type: 'string' },
344
+ fields: { type: 'object' },
345
+ },
346
+ required: ['datasetId', 'operation'],
347
+ },
348
+ },
349
+ {
350
+ name: 'dataset_schema_modify',
351
+ description: 'Modify dataset schema (add/update/delete field)',
352
+ inputSchema: {
353
+ type: 'object',
354
+ properties: {
355
+ datasetId: { type: 'string' },
356
+ operation: { type: 'string', enum: ['add_field', 'update_field', 'delete_field'] },
357
+ fieldId: { type: 'string' },
358
+ field: { type: 'object' },
359
+ },
360
+ required: ['datasetId', 'operation'],
361
+ },
362
+ },
363
+ {
364
+ name: 'dataset_summarize',
365
+ description: 'Summarize dataset schema and sample rows',
366
+ inputSchema: {
367
+ type: 'object',
368
+ properties: {
369
+ datasetId: { type: 'string' },
370
+ },
371
+ required: ['datasetId'],
372
+ },
373
+ },
374
+ {
375
+ name: 'object_find',
376
+ description: 'Find ontology objects by object type and property filters',
377
+ inputSchema: {
378
+ type: 'object',
379
+ properties: {
380
+ objectType: { type: 'string' },
381
+ where: { type: 'array', items: { type: 'object' } },
382
+ limit: { type: 'number' },
383
+ },
384
+ required: ['objectType'],
385
+ },
386
+ },
387
+ {
388
+ name: 'object_links',
389
+ description: 'Traverse graph links for an object record',
390
+ inputSchema: {
391
+ type: 'object',
392
+ properties: {
393
+ objectRecordId: { type: 'string' },
394
+ depth: { type: 'number' },
395
+ limit: { type: 'number' },
396
+ },
397
+ required: ['objectRecordId'],
398
+ },
399
+ },
400
+ {
401
+ name: 'object_action_run',
402
+ description: 'Run declarative action on an ontology object',
403
+ inputSchema: {
404
+ type: 'object',
405
+ properties: {
406
+ objectTypeKey: { type: 'string' },
407
+ actionKey: { type: 'string' },
408
+ objectRecordId: { type: 'string' },
409
+ idempotencyKey: { type: 'string' },
410
+ parameters: { type: 'object' },
411
+ },
412
+ required: ['objectTypeKey', 'actionKey', 'objectRecordId'],
413
+ },
414
+ },
415
+ {
416
+ name: 'people_search',
417
+ description: 'Search contacts',
418
+ inputSchema: {
419
+ type: 'object',
420
+ properties: {
421
+ query: { type: 'string' },
422
+ limit: { type: 'number' },
423
+ },
424
+ required: ['query'],
425
+ },
426
+ },
427
+ {
428
+ name: 'calendar_list_events',
429
+ description: 'List calendar events',
430
+ inputSchema: {
431
+ type: 'object',
432
+ properties: {
433
+ startDate: { type: 'string', description: 'ISO 8601' },
434
+ endDate: { type: 'string', description: 'ISO 8601' },
435
+ limit: { type: 'number' },
436
+ },
437
+ },
438
+ },
439
+ {
440
+ name: 'calendar_create_event',
441
+ description: 'Create calendar event',
442
+ inputSchema: {
443
+ type: 'object',
444
+ properties: {
445
+ title: { type: 'string' },
446
+ startTime: { type: 'string', description: 'ISO 8601' },
447
+ endTime: { type: 'string', description: 'ISO 8601' },
448
+ description: { type: 'string' },
449
+ location: { type: 'string' },
450
+ },
451
+ required: ['title', 'startTime', 'endTime'],
452
+ },
453
+ },
454
+ {
455
+ name: 'calendar_update_event',
456
+ description: 'Update a calendar event',
457
+ inputSchema: {
458
+ type: 'object',
459
+ properties: {
460
+ eventId: { type: 'string' },
461
+ title: { type: 'string' },
462
+ startTime: { type: 'string', description: 'ISO 8601' },
463
+ endTime: { type: 'string', description: 'ISO 8601' },
464
+ description: { type: 'string' },
465
+ location: { type: 'string' },
466
+ },
467
+ required: ['eventId'],
468
+ },
469
+ },
470
+ {
471
+ name: 'calendar_delete_event',
472
+ description: 'Delete a calendar event',
473
+ inputSchema: {
474
+ type: 'object',
475
+ properties: {
476
+ eventId: { type: 'string' },
477
+ },
478
+ required: ['eventId'],
479
+ },
480
+ },
481
+ // Codebase Indexing Tools
482
+ {
483
+ name: 'codebase_list',
484
+ description: 'List indexed repositories',
485
+ inputSchema: { type: 'object', properties: {} },
486
+ },
487
+ {
488
+ name: 'codebase_register',
489
+ description: 'Register codebase for indexing',
490
+ inputSchema: {
491
+ type: 'object',
492
+ properties: {
493
+ rootPath: { type: 'string', description: 'Absolute path' },
494
+ name: { type: 'string' },
495
+ projectId: { type: 'string' },
496
+ includePatterns: { type: 'array', items: { type: 'string' }, description: 'Globs' },
497
+ excludePatterns: { type: 'array', items: { type: 'string' }, description: 'Globs' },
498
+ autoIndex: { type: 'boolean' },
499
+ },
500
+ required: ['rootPath', 'name'],
501
+ },
502
+ },
503
+ {
504
+ name: 'codebase_status',
505
+ description: 'Check indexing status',
506
+ inputSchema: {
507
+ type: 'object',
508
+ properties: {
509
+ repositoryId: { type: 'string' },
510
+ },
511
+ required: ['repositoryId'],
512
+ },
513
+ },
514
+ {
515
+ name: 'codebase_snapshot_status',
516
+ description: 'Get latest index snapshot for a repository',
517
+ inputSchema: {
518
+ type: 'object',
519
+ properties: {
520
+ repositoryId: { type: 'string' },
521
+ branch: { type: 'string' },
522
+ committedOnly: { type: 'boolean' },
523
+ },
524
+ required: ['repositoryId'],
525
+ },
526
+ },
527
+ {
528
+ name: 'codebase_materialize_snapshot',
529
+ description: 'Materialize a snapshot archive and return a short-lived download URL',
530
+ inputSchema: {
531
+ type: 'object',
532
+ properties: {
533
+ snapshotId: { type: 'string' },
534
+ expiresMinutes: { type: 'number' },
535
+ },
536
+ required: ['snapshotId'],
537
+ },
538
+ },
539
+ {
540
+ name: 'codebase_index',
541
+ description: 'Full index: scan and upload files',
542
+ inputSchema: {
543
+ type: 'object',
544
+ properties: {
545
+ repositoryId: { type: 'string' },
546
+ rootPath: { type: 'string', description: 'Absolute path' },
547
+ includePatterns: { type: 'array', items: { type: 'string' }, description: 'Globs' },
548
+ excludePatterns: { type: 'array', items: { type: 'string' }, description: 'Globs' },
549
+ },
550
+ required: ['repositoryId', 'rootPath'],
551
+ },
552
+ },
553
+ {
554
+ name: 'codebase_index_incremental',
555
+ description: 'Incremental index: git-aware, changed files only',
556
+ inputSchema: {
557
+ type: 'object',
558
+ properties: {
559
+ repositoryId: { type: 'string' },
560
+ rootPath: { type: 'string', description: 'Absolute path' },
561
+ includePatterns: { type: 'array', items: { type: 'string' }, description: 'Globs' },
562
+ excludePatterns: { type: 'array', items: { type: 'string' }, description: 'Globs' },
563
+ includeWorkingTree: {
564
+ type: 'boolean',
565
+ description: 'When true (default), also indexes local uncommitted changes',
566
+ },
567
+ },
568
+ required: ['repositoryId', 'rootPath'],
569
+ },
570
+ },
571
+ {
572
+ name: 'codebase_delete',
573
+ description: 'Delete a repository and all its indexed data (files, chunks, embeddings, snapshots)',
574
+ inputSchema: {
575
+ type: 'object',
576
+ properties: {
577
+ repositoryId: { type: 'string' },
578
+ },
579
+ required: ['repositoryId'],
580
+ },
581
+ },
582
+ {
583
+ name: 'codebase_search',
584
+ description: 'Semantic code search',
585
+ inputSchema: {
586
+ type: 'object',
587
+ properties: {
588
+ query: { type: 'string' },
589
+ repositoryId: { type: 'string' },
590
+ language: { type: 'string' },
591
+ symbolType: {
592
+ type: 'string',
593
+ enum: ['function', 'class', 'interface', 'type', 'export', 'impl'],
594
+ },
595
+ limit: { type: 'number' },
596
+ },
597
+ required: ['query'],
598
+ },
599
+ },
600
+ // Developer Expertise Tools
601
+ {
602
+ name: 'code_who_knows',
603
+ description: 'Find experts for code area',
604
+ inputSchema: {
605
+ type: 'object',
606
+ properties: {
607
+ repositoryId: { type: 'string' },
608
+ codeArea: { type: 'string', description: 'Path, glob, or symbol' },
609
+ limit: { type: 'number' },
610
+ },
611
+ required: ['repositoryId', 'codeArea'],
612
+ },
613
+ },
614
+ {
615
+ name: 'code_compute_expertise',
616
+ description: 'Refresh expertise index',
617
+ inputSchema: {
618
+ type: 'object',
619
+ properties: {
620
+ repositoryId: { type: 'string' },
621
+ },
622
+ required: ['repositoryId'],
623
+ },
624
+ },
625
+ {
626
+ name: 'code_history',
627
+ description: 'Get commit history',
628
+ inputSchema: {
629
+ type: 'object',
630
+ properties: {
631
+ repositoryId: { type: 'string' },
632
+ path: { type: 'string', description: 'Filter by file' },
633
+ limit: { type: 'number' },
634
+ },
635
+ required: ['repositoryId'],
636
+ },
637
+ },
638
+ {
639
+ name: 'git_blame_symbol',
640
+ description: 'Git blame (local)',
641
+ inputSchema: {
642
+ type: 'object',
643
+ properties: {
644
+ rootPath: { type: 'string', description: 'Repo root' },
645
+ filePath: { type: 'string', description: 'Relative path' },
646
+ startLine: { type: 'number' },
647
+ endLine: { type: 'number' },
648
+ },
649
+ required: ['rootPath', 'filePath'],
650
+ },
651
+ },
652
+ {
653
+ name: 'task_link_code',
654
+ description: 'Link task to code',
655
+ inputSchema: {
656
+ type: 'object',
657
+ properties: {
658
+ taskId: { type: 'string' },
659
+ repositoryId: { type: 'string' },
660
+ filePath: { type: 'string' },
661
+ commitSha: { type: 'string' },
662
+ notes: { type: 'string' },
663
+ },
664
+ required: ['taskId', 'repositoryId'],
665
+ },
666
+ },
667
+ // Code Memory Tools
668
+ {
669
+ name: 'code_memory_store',
670
+ description: 'Store codebase fact',
671
+ inputSchema: {
672
+ type: 'object',
673
+ properties: {
674
+ fact: { type: 'string', description: '1-2 sentences' },
675
+ category: {
676
+ type: 'string',
677
+ enum: ['architecture', 'integration', 'convention', 'entrypoint', 'gotcha', 'ownership'],
678
+ },
679
+ filePath: { type: 'string' },
680
+ repositoryId: { type: 'string' },
681
+ },
682
+ required: ['fact', 'category'],
683
+ },
684
+ },
685
+ {
686
+ name: 'code_memory_search',
687
+ description: 'Search stored codebase facts',
688
+ inputSchema: {
689
+ type: 'object',
690
+ properties: {
691
+ query: { type: 'string' },
692
+ category: {
693
+ type: 'string',
694
+ enum: ['architecture', 'integration', 'convention', 'entrypoint', 'gotcha', 'ownership'],
695
+ },
696
+ repositoryId: { type: 'string' },
697
+ limit: { type: 'number' },
698
+ },
699
+ required: ['query'],
700
+ },
701
+ },
702
+ {
703
+ name: 'code_memory_list',
704
+ description: 'List stored facts',
705
+ inputSchema: {
706
+ type: 'object',
707
+ properties: {
708
+ repositoryId: { type: 'string' },
709
+ limit: { type: 'number' },
710
+ },
711
+ },
712
+ },
713
+ {
714
+ name: 'code_memory_delete',
715
+ description: 'Delete stored fact',
716
+ inputSchema: {
717
+ type: 'object',
718
+ properties: {
719
+ memoryId: { type: 'string' },
720
+ },
721
+ required: ['memoryId'],
722
+ },
723
+ },
724
+ // Document Upload Tool
725
+ {
726
+ name: 'upload_document',
727
+ description: 'Upload a document (PDF, Markdown, or text) into Knowledge as a note',
728
+ inputSchema: {
729
+ type: 'object',
730
+ properties: {
731
+ filePath: { type: 'string', description: 'Absolute path to a local file (PDF, .md, or .txt)' },
732
+ content: { type: 'string', description: 'File content (plain text for .md/.txt, base64 for PDF). Use with filename.' },
733
+ filename: { type: 'string', description: 'Filename with extension (required when using content instead of filePath)' },
734
+ contentType: { type: 'string', description: 'MIME type override (auto-detected from extension if omitted)' },
735
+ title: { type: 'string', description: 'Note title (defaults to filename without extension)' },
736
+ noteType: {
737
+ type: 'string',
738
+ enum: ['note', 'concept', 'meeting', 'reference', 'daily', 'dataset'],
739
+ description: 'Note type (defaults to "reference" for PDFs, "note" for text)',
740
+ },
741
+ projectId: { type: 'string', description: 'Project to attach the note to' },
742
+ },
743
+ },
744
+ },
745
+ // Vault / Secrets Management Tools
746
+ {
747
+ name: 'vault_list',
748
+ description: 'List vault entries (secrets, API keys, credentials). Returns metadata only — never decrypted values.',
749
+ inputSchema: {
750
+ type: 'object',
751
+ properties: {
752
+ entryType: {
753
+ type: 'string',
754
+ enum: ['env_var', 'credential', 'oauth_token', 'ssh_key', 'certificate', 'note'],
755
+ },
756
+ category: { type: 'string' },
757
+ search: { type: 'string' },
758
+ limit: { type: 'number' },
759
+ },
760
+ },
761
+ },
762
+ {
763
+ name: 'vault_create',
764
+ description: 'Store a new encrypted secret in the vault',
765
+ inputSchema: {
766
+ type: 'object',
767
+ properties: {
768
+ name: { type: 'string' },
769
+ slug: { type: 'string', description: 'Machine-friendly identifier (auto-generated if omitted)' },
770
+ entryType: {
771
+ type: 'string',
772
+ enum: ['env_var', 'credential', 'oauth_token', 'ssh_key', 'certificate', 'note'],
773
+ },
774
+ payload: { type: 'object', description: 'Key-value pairs to encrypt' },
775
+ description: { type: 'string' },
776
+ tags: { type: 'array', items: { type: 'string' } },
777
+ category: { type: 'string' },
778
+ url: { type: 'string' },
779
+ },
780
+ required: ['name', 'payload'],
781
+ },
782
+ },
783
+ {
784
+ name: 'vault_read',
785
+ description: 'Decrypt and read a vault secret. Audit-logged. Only available to trusted MCP clients.',
786
+ inputSchema: {
787
+ type: 'object',
788
+ properties: {
789
+ entryId: { type: 'string', description: 'Vault entry UUID' },
790
+ },
791
+ required: ['entryId'],
792
+ },
793
+ },
794
+ {
795
+ name: 'vault_update',
796
+ description: 'Update vault entry metadata (name, tags, category, description)',
797
+ inputSchema: {
798
+ type: 'object',
799
+ properties: {
800
+ entryId: { type: 'string' },
801
+ name: { type: 'string' },
802
+ description: { type: 'string' },
803
+ tags: { type: 'array', items: { type: 'string' } },
804
+ category: { type: 'string' },
805
+ url: { type: 'string' },
806
+ },
807
+ required: ['entryId'],
808
+ },
809
+ },
810
+ {
811
+ name: 'vault_search',
812
+ description: 'Search vault entries by name, slug, or description. Returns metadata only.',
813
+ inputSchema: {
814
+ type: 'object',
815
+ properties: {
816
+ query: { type: 'string' },
817
+ limit: { type: 'number' },
818
+ },
819
+ required: ['query'],
820
+ },
821
+ },
822
+ ];
823
+ function isToolEnabled(name) {
824
+ if (name.startsWith('dataset_')) {
825
+ return DATASETS_ENABLED;
826
+ }
827
+ if (name === 'object_find' || name === 'object_links') {
828
+ return DATASET_ONTOLOGY_ENABLED;
829
+ }
830
+ if (name === 'object_action_run') {
831
+ return DATASET_ACTIONS_ENABLED;
832
+ }
833
+ return true;
834
+ }
835
+ // =============================================================================
836
+ // Tool Execution
837
+ // =============================================================================
838
+ async function executeTool(client, name, args) {
839
+ switch (name) {
840
+ case 'project_list': {
841
+ const result = await client.listProjects({
842
+ status: args.status,
843
+ includeArchived: args.includeArchived,
844
+ });
845
+ if (result.error)
846
+ return `Error: ${result.error}`;
847
+ const projects = result.data?.projects || [];
848
+ if (projects.length === 0)
849
+ return 'No projects found.';
850
+ return projects.map((p) => `• ${p.emoji || '📁'} ${p.name} [${p.status}] - ${p.summary || 'No description'}`).join('\n');
851
+ }
852
+ case 'project_create': {
853
+ const result = await client.createProject({
854
+ name: args.name,
855
+ summary: args.summary,
856
+ status: args.status,
857
+ emoji: args.emoji,
858
+ });
859
+ if (result.error)
860
+ return `Error: ${result.error}`;
861
+ const p = result.data?.project;
862
+ return `Project created: ${p?.emoji || '📁'} ${p?.name} [${p?.status}]\nID: ${p?.id}`;
863
+ }
864
+ case 'project_update': {
865
+ const result = await client.updateProject(args.projectId, {
866
+ name: args.name,
867
+ summary: args.summary,
868
+ status: args.status,
869
+ emoji: args.emoji,
870
+ });
871
+ if (result.error)
872
+ return `Error: ${result.error}`;
873
+ const p = result.data?.project;
874
+ return `Project updated: ${p?.emoji || '📁'} ${p?.name} [${p?.status}]`;
875
+ }
876
+ case 'project_archive': {
877
+ const result = await client.archiveProject(args.projectId);
878
+ if (result.error)
879
+ return `Error: ${result.error}`;
880
+ return `Project archived: ${result.data?.project?.name}`;
881
+ }
882
+ case 'project_get_context': {
883
+ const result = await client.getProjectContext(args.projectId);
884
+ if (result.error)
885
+ return `Error: ${result.error}`;
886
+ const ctx = result.data?.context ?? result.data;
887
+ if (!ctx)
888
+ return 'Project not found.';
889
+ let output = `# ${ctx.project?.emoji || '📁'} ${ctx.project?.name}\n`;
890
+ output += `Status: ${ctx.project?.status}\n`;
891
+ output += `Summary: ${ctx.project?.summary || 'None'}\n\n`;
892
+ if (ctx.signals) {
893
+ output += `## Signals\n`;
894
+ output += `- Open tasks: ${ctx.signals.openTaskCount || 0}\n`;
895
+ output += `- Blocked tasks: ${ctx.signals.blockedTaskCount || 0}\n`;
896
+ output += `- Overdue tasks: ${ctx.signals.overdueTaskCount || 0}\n`;
897
+ output += `- Completed tasks: ${ctx.signals.completedTaskCount || 0}\n`;
898
+ if (ctx.signals.daysUntilDeadline !== null && ctx.signals.daysUntilDeadline !== undefined) {
899
+ output += `- Days until deadline: ${ctx.signals.daysUntilDeadline}\n`;
900
+ }
901
+ output += `\n`;
902
+ }
903
+ const openTasks = ctx.tasks?.open ?? [];
904
+ const blockedTasks = ctx.tasks?.blocked ?? [];
905
+ const overdueTasks = ctx.tasks?.overdue ?? [];
906
+ if (openTasks.length > 0) {
907
+ output += `## Open Tasks (${openTasks.length})\n`;
908
+ openTasks.slice(0, 10).forEach((t) => {
909
+ output += `- [${t.status}] ${t.title}${t.dueAt ? ` (due: ${t.dueAt})` : ''}\n`;
910
+ });
911
+ if (openTasks.length > 10)
912
+ output += `... and ${openTasks.length - 10} more\n`;
913
+ output += `\n`;
914
+ }
915
+ if (blockedTasks.length > 0) {
916
+ output += `## Blocked (${blockedTasks.length})\n`;
917
+ blockedTasks.slice(0, 5).forEach((t) => {
918
+ const blockedBy = Array.isArray(t.blockedBy) && t.blockedBy.length > 0
919
+ ? ` (blocked by: ${t.blockedBy.join(', ')})`
920
+ : '';
921
+ output += `- ${t.title}${blockedBy}\n`;
922
+ });
923
+ if (blockedTasks.length > 5)
924
+ output += `... and ${blockedTasks.length - 5} more\n`;
925
+ output += `\n`;
926
+ }
927
+ if (overdueTasks.length > 0) {
928
+ output += `## Overdue (${overdueTasks.length})\n`;
929
+ overdueTasks.slice(0, 5).forEach((t) => {
930
+ output += `- ${t.title} (due: ${t.dueAt})\n`;
931
+ });
932
+ if (overdueTasks.length > 5)
933
+ output += `... and ${overdueTasks.length - 5} more\n`;
934
+ output += `\n`;
935
+ }
936
+ return output;
937
+ }
938
+ case 'task_list': {
939
+ const result = await client.listTasks({
940
+ projectId: args.projectId,
941
+ status: args.status,
942
+ limit: args.limit,
943
+ });
944
+ if (result.error)
945
+ return `Error: ${result.error}`;
946
+ const tasks = result.data?.tasks || [];
947
+ if (tasks.length === 0)
948
+ return 'No tasks found.';
949
+ return tasks.map((t) => `• [${t.status}] ${t.title}${t.dueAt ? ` (due: ${new Date(t.dueAt).toLocaleDateString()})` : ''}`).join('\n');
950
+ }
951
+ case 'task_get': {
952
+ const result = await client.getTask(args.taskId);
953
+ if (result.error)
954
+ return `Error: ${result.error}`;
955
+ const t = result.data?.task;
956
+ if (!t)
957
+ return 'Task not found.';
958
+ let output = `# ${t.title}\n`;
959
+ output += `Status: ${t.status}\n`;
960
+ output += `Priority: ${t.priority || 'none'}\n`;
961
+ if (t.dueAt)
962
+ output += `Due: ${new Date(t.dueAt).toLocaleDateString()}\n`;
963
+ if (t.projectId)
964
+ output += `Project: ${t.projectId}\n`;
965
+ if (t.description)
966
+ output += `\n${t.description}\n`;
967
+ output += `\nID: ${t.id}`;
968
+ return output;
969
+ }
970
+ case 'task_update': {
971
+ const { taskId, idempotencyKey, ...updates } = args;
972
+ const result = await client.updateTask(taskId, {
973
+ title: updates.title,
974
+ description: updates.description,
975
+ status: updates.status,
976
+ priority: updates.priority,
977
+ dueAt: updates.dueAt,
978
+ projectId: updates.projectId,
979
+ }, idempotencyKey);
980
+ if (result.error)
981
+ return `Error: ${result.error}`;
982
+ const t = result.data?.task;
983
+ return `Task updated: ${t?.title} [${t?.status}]`;
984
+ }
985
+ case 'task_delete': {
986
+ const result = await client.deleteTask(args.taskId);
987
+ if (result.error)
988
+ return `Error: ${result.error}`;
989
+ return `Task deleted.`;
990
+ }
991
+ case 'task_create': {
992
+ const result = await client.createTask({
993
+ title: args.title,
994
+ description: args.description,
995
+ priority: args.priority,
996
+ dueAt: args.dueAt,
997
+ projectId: args.projectId,
998
+ }, args.idempotencyKey);
999
+ if (result.error)
1000
+ return `Error: ${result.error}`;
1001
+ return `✅ Task created: ${result.data?.task?.title} (ID: ${result.data?.task?.id})`;
1002
+ }
1003
+ case 'task_complete': {
1004
+ const result = await client.completeTask(args.taskId, args.idempotencyKey);
1005
+ if (result.error)
1006
+ return `Error: ${result.error}`;
1007
+ return `✅ Task completed: ${result.data?.task?.title}`;
1008
+ }
1009
+ case 'note_list': {
1010
+ const result = await client.listNotes({
1011
+ projectId: args.projectId,
1012
+ noteType: args.noteType,
1013
+ limit: args.limit,
1014
+ });
1015
+ if (result.error)
1016
+ return `Error: ${result.error}`;
1017
+ const notes = result.data?.notes || [];
1018
+ if (notes.length === 0)
1019
+ return 'No notes found.';
1020
+ return notes.map((n) => `• ${n.title} [${n.noteType}]${n.projectId ? ` (project: ${n.projectId})` : ''}\n ID: ${n.id}`).join('\n');
1021
+ }
1022
+ case 'note_get': {
1023
+ const result = await client.getNote(args.noteId);
1024
+ if (result.error)
1025
+ return `Error: ${result.error}`;
1026
+ const n = result.data?.note;
1027
+ if (!n)
1028
+ return 'Note not found.';
1029
+ let output = `# ${n.title}\n`;
1030
+ output += `Type: ${n.noteType}\n`;
1031
+ if (n.projectId)
1032
+ output += `Project: ${n.projectId}\n`;
1033
+ output += `\n`;
1034
+ // Extract text from contentJson if available
1035
+ if (n.contentJson?.content) {
1036
+ for (const block of n.contentJson.content) {
1037
+ if (block.type === 'paragraph' && block.content) {
1038
+ output += block.content.map((c) => c.text || '').join('') + '\n';
1039
+ }
1040
+ else if (block.type === 'heading' && block.content) {
1041
+ const level = block.attrs?.level || 2;
1042
+ output += '#'.repeat(level) + ' ' + block.content.map((c) => c.text || '').join('') + '\n';
1043
+ }
1044
+ }
1045
+ }
1046
+ else if (n.content) {
1047
+ output += n.content;
1048
+ }
1049
+ output += `\nID: ${n.id}`;
1050
+ return output;
1051
+ }
1052
+ case 'note_search': {
1053
+ const result = await client.searchNotes(args.query, {
1054
+ projectId: args.projectId,
1055
+ limit: args.limit,
1056
+ });
1057
+ if (result.error)
1058
+ return `Error: ${result.error}`;
1059
+ const results = result.data?.results || [];
1060
+ if (results.length === 0)
1061
+ return 'No notes found.';
1062
+ return results.map((n) => `• ${n.title}${n.snippet ? `\n ${n.snippet}` : ''}`).join('\n\n');
1063
+ }
1064
+ case 'note_create': {
1065
+ const result = await client.createNote({
1066
+ title: args.title,
1067
+ content: args.content,
1068
+ projectId: args.projectId,
1069
+ noteType: args.noteType,
1070
+ }, args.idempotencyKey);
1071
+ if (result.error)
1072
+ return `Error: ${result.error}`;
1073
+ return `✅ Note created: ${result.data?.note?.title} (ID: ${result.data?.note?.id})`;
1074
+ }
1075
+ case 'note_update': {
1076
+ const result = await client.updateNote(args.noteId, {
1077
+ title: args.title,
1078
+ content: args.content,
1079
+ noteType: args.noteType,
1080
+ }, args.idempotencyKey);
1081
+ if (result.error)
1082
+ return `Error: ${result.error}`;
1083
+ return `Note updated: ${result.data?.note?.title}`;
1084
+ }
1085
+ case 'note_delete': {
1086
+ const result = await client.deleteNote(args.noteId);
1087
+ if (result.error)
1088
+ return `Error: ${result.error}`;
1089
+ return `Note deleted.`;
1090
+ }
1091
+ case 'dataset_list': {
1092
+ const result = await client.listDatasets(args.limit);
1093
+ if (result.error)
1094
+ return `Error: ${result.error}`;
1095
+ const datasets = result.data?.datasets || [];
1096
+ if (datasets.length === 0)
1097
+ return 'No datasets found.';
1098
+ return datasets.map((d) => `• ${d.title} (${d.rowCount} rows, ${d.physicalTableName})\n ID: ${d.id}`).join('\n\n');
1099
+ }
1100
+ case 'dataset_create': {
1101
+ const result = await client.createDataset({
1102
+ title: args.title,
1103
+ description: args.description,
1104
+ fields: args.fields,
1105
+ metadata: args.metadata,
1106
+ });
1107
+ if (result.error)
1108
+ return `Error: ${result.error}`;
1109
+ const dataset = result.data?.dataset;
1110
+ const fields = result.data?.fields || [];
1111
+ return `Dataset created: ${dataset?.title || args.title}\nID: ${dataset?.id}\nFields: ${fields.length}`;
1112
+ }
1113
+ case 'dataset_query': {
1114
+ const result = await client.queryDataset(args.datasetId, {
1115
+ filters: args.filters,
1116
+ sorts: args.sorts,
1117
+ limit: args.limit,
1118
+ cursor: args.cursor,
1119
+ });
1120
+ if (result.error)
1121
+ return `Error: ${result.error}`;
1122
+ const records = result.data?.records || [];
1123
+ if (records.length === 0)
1124
+ return 'No records found.';
1125
+ const lines = records.slice(0, 20).map((record, idx) => {
1126
+ return `${idx + 1}. ${record.id} ${JSON.stringify(record.fields)}`;
1127
+ });
1128
+ const nextCursor = result.data?.nextCursor;
1129
+ return `${lines.join('\n')}${nextCursor ? `\n\nnextCursor: ${nextCursor}` : ''}`;
1130
+ }
1131
+ case 'dataset_mutate': {
1132
+ const result = await client.mutateDataset(args.datasetId, {
1133
+ operation: args.operation,
1134
+ records: args.records,
1135
+ recordId: args.recordId,
1136
+ fields: args.fields,
1137
+ });
1138
+ if (result.error)
1139
+ return `Error: ${result.error}`;
1140
+ return `Dataset mutation successful: ${String(args.operation)}`;
1141
+ }
1142
+ case 'dataset_schema_modify': {
1143
+ const result = await client.modifyDatasetSchema(args.datasetId, {
1144
+ operation: args.operation,
1145
+ fieldId: args.fieldId,
1146
+ field: args.field,
1147
+ });
1148
+ if (result.error)
1149
+ return `Error: ${result.error}`;
1150
+ return `Dataset schema updated: ${String(args.operation)}`;
1151
+ }
1152
+ case 'dataset_summarize': {
1153
+ const result = await client.summarizeDataset(args.datasetId);
1154
+ if (result.error)
1155
+ return `Error: ${result.error}`;
1156
+ const summary = result.data?.summary;
1157
+ if (!summary)
1158
+ return 'No summary available.';
1159
+ return [
1160
+ `Dataset: ${summary.title}`,
1161
+ `Rows: ${summary.rowCount}`,
1162
+ `Fields: ${summary.fieldCount}`,
1163
+ `Formula fields: ${summary.formulaFieldCount}`,
1164
+ `Relation fields: ${summary.relationFieldCount}`,
1165
+ `Object bound: ${summary.objectBound ? 'yes' : 'no'}`,
1166
+ ].join('\n');
1167
+ }
1168
+ case 'object_find': {
1169
+ const result = await client.findObjects({
1170
+ objectType: args.objectType,
1171
+ where: args.where,
1172
+ limit: args.limit,
1173
+ });
1174
+ if (result.error)
1175
+ return `Error: ${result.error}`;
1176
+ const objects = result.data?.objects || [];
1177
+ if (objects.length === 0)
1178
+ return 'No objects found.';
1179
+ return objects.map((obj) => `• ${obj.display_name} [${obj.status || 'no-status'}]\n id: ${obj.id}\n datasetRow: ${obj.dataset_row_id || '-'}`).join('\n\n');
1180
+ }
1181
+ case 'object_links': {
1182
+ const result = await client.getObjectLinks(args.objectRecordId, {
1183
+ depth: args.depth,
1184
+ limit: args.limit,
1185
+ });
1186
+ if (result.error)
1187
+ return `Error: ${result.error}`;
1188
+ const graph = result.data?.graph;
1189
+ if (!graph)
1190
+ return 'No graph returned.';
1191
+ return `Graph nodes: ${graph.nodes?.length || 0}\nGraph edges: ${graph.edges?.length || 0}`;
1192
+ }
1193
+ case 'object_action_run': {
1194
+ const result = await client.runObjectAction({
1195
+ objectTypeKey: args.objectTypeKey,
1196
+ actionKey: args.actionKey,
1197
+ objectRecordId: args.objectRecordId,
1198
+ idempotencyKey: args.idempotencyKey,
1199
+ parameters: args.parameters,
1200
+ });
1201
+ if (result.error)
1202
+ return `Error: ${result.error}`;
1203
+ const actionResult = result.data?.result ?? result.data;
1204
+ return `Action executed: ${actionResult?.actionKey || args.actionKey}\nSide effects: ${(actionResult?.sideEffects || []).length}`;
1205
+ }
1206
+ case 'people_search': {
1207
+ const result = await client.searchPeople(args.query, args.limit);
1208
+ if (result.error)
1209
+ return `Error: ${result.error}`;
1210
+ const people = result.data?.people || [];
1211
+ if (people.length === 0)
1212
+ return 'No people found.';
1213
+ return people.map((p) => `• ${p.name}${p.email ? ` <${p.email}>` : ''}${p.company ? ` @ ${p.company}` : ''}`).join('\n');
1214
+ }
1215
+ case 'calendar_list_events': {
1216
+ const result = await client.listCalendarEvents({
1217
+ startDate: args.startDate,
1218
+ endDate: args.endDate,
1219
+ limit: args.limit,
1220
+ });
1221
+ if (result.error)
1222
+ return `Error: ${result.error}`;
1223
+ const events = result.data?.events || [];
1224
+ if (events.length === 0)
1225
+ return 'No calendar events found in the specified range.';
1226
+ return events.map((e) => {
1227
+ const start = new Date(e.startTime);
1228
+ const end = new Date(e.endTime);
1229
+ const dateStr = start.toLocaleDateString();
1230
+ const timeStr = e.isAllDay ? 'All day' : `${start.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${end.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
1231
+ return `• ${dateStr} ${timeStr}: ${e.title}${e.location ? ` @ ${e.location}` : ''}`;
1232
+ }).join('\n');
1233
+ }
1234
+ case 'calendar_create_event': {
1235
+ const result = await client.createCalendarEvent({
1236
+ title: args.title,
1237
+ startTime: args.startTime,
1238
+ endTime: args.endTime,
1239
+ description: args.description,
1240
+ location: args.location,
1241
+ });
1242
+ if (result.error)
1243
+ return `Error: ${result.error}`;
1244
+ const event = result.data?.event;
1245
+ if (!event)
1246
+ return 'Event created but no details returned.';
1247
+ const start = new Date(event.startTime);
1248
+ const end = new Date(event.endTime);
1249
+ return `✅ Calendar event created: "${event.title}"\n ${start.toLocaleDateString()} ${start.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${end.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}${event.location ? `\n Location: ${event.location}` : ''}\n ID: ${event.id}`;
1250
+ }
1251
+ case 'calendar_update_event': {
1252
+ const result = await client.updateCalendarEvent(args.eventId, {
1253
+ title: args.title,
1254
+ startTime: args.startTime,
1255
+ endTime: args.endTime,
1256
+ description: args.description,
1257
+ location: args.location,
1258
+ });
1259
+ if (result.error)
1260
+ return `Error: ${result.error}`;
1261
+ const e = result.data?.event;
1262
+ return `Calendar event updated: ${e?.title || 'done'}`;
1263
+ }
1264
+ case 'calendar_delete_event': {
1265
+ const result = await client.deleteCalendarEvent(args.eventId);
1266
+ if (result.error)
1267
+ return `Error: ${result.error}`;
1268
+ return `Calendar event deleted.`;
1269
+ }
1270
+ // =========================================================================
1271
+ // Codebase Indexing Tools
1272
+ // =========================================================================
1273
+ case 'codebase_list': {
1274
+ const result = await client.listCodeRepositories();
1275
+ if (result.error)
1276
+ return `Error: ${result.error}`;
1277
+ const repos = result.data?.repositories || [];
1278
+ if (repos.length === 0)
1279
+ return 'No code repositories indexed yet. Use codebase_register to add one.';
1280
+ return repos.map((r) => `• ${r.name} [${r.status}] - ${r.rootPath}\n ID: ${r.id}\n Files: ${r.fileCount}, Chunks: ${r.chunkCount}${r.lastIndexedAt ? `, Last indexed: ${new Date(r.lastIndexedAt).toLocaleString()}` : ''}`).join('\n\n');
1281
+ }
1282
+ case 'codebase_register': {
1283
+ const rootPath = args.rootPath;
1284
+ const includePatterns = args.includePatterns;
1285
+ const excludePatterns = args.excludePatterns;
1286
+ const autoIndex = args.autoIndex;
1287
+ const result = await client.createCodeRepository({
1288
+ name: args.name,
1289
+ rootPath,
1290
+ projectId: args.projectId,
1291
+ includePatterns,
1292
+ excludePatterns,
1293
+ });
1294
+ if (result.error)
1295
+ return `Error: ${result.error}`;
1296
+ const repo = result.data?.repository;
1297
+ if (!autoIndex) {
1298
+ return `✅ Repository registered: ${repo?.name}\nID: ${repo?.id}\nPath: ${repo?.rootPath}\nStatus: ${repo?.status}\n\nTo index files, use codebase_index with repositoryId: ${repo?.id}`;
1299
+ }
1300
+ // Auto-index: scan and upload files
1301
+ console.error(`Auto-indexing ${repo?.name}...`);
1302
+ const progress = await (0, localIndexer_js_1.indexLocalCodebase)(client, repo?.id, rootPath, {
1303
+ includePatterns,
1304
+ excludePatterns,
1305
+ onProgress: (p) => {
1306
+ if (p.phase === 'uploading' && p.currentFile) {
1307
+ console.error(`[${p.filesUploaded}/${p.filesFound}] ${p.currentFile}`);
1308
+ }
1309
+ },
1310
+ });
1311
+ if (progress.phase === 'error') {
1312
+ return `✅ Repository registered: ${repo?.name}\nID: ${repo?.id}\n\n❌ Auto-indexing failed: ${progress.error}`;
1313
+ }
1314
+ return `✅ Repository registered and indexed: ${repo?.name}\n` +
1315
+ `ID: ${repo?.id}\n` +
1316
+ `Path: ${repo?.rootPath}\n` +
1317
+ `Files uploaded: ${progress.filesUploaded}\n` +
1318
+ `Chunks created: ${progress.chunksCreated}\n\n` +
1319
+ `Embeddings will be generated in the background. Use codebase_search to query once ready.`;
1320
+ }
1321
+ case 'codebase_status': {
1322
+ const result = await client.getCodeRepository(args.repositoryId);
1323
+ if (result.error)
1324
+ return `Error: ${result.error}`;
1325
+ const repo = result.data?.repository;
1326
+ if (!repo)
1327
+ return 'Repository not found.';
1328
+ return `# ${repo.name}\n` +
1329
+ `Status: ${repo.status}\n` +
1330
+ `Path: ${repo.rootPath}\n` +
1331
+ `Files: ${repo.fileCount}\n` +
1332
+ `Chunks: ${repo.chunkCount}\n` +
1333
+ (repo.rawBlobCoverage ? `Raw blob coverage: ${repo.rawBlobCoverage}\n` : '') +
1334
+ (repo.lastIndexedAt ? `Last indexed: ${new Date(repo.lastIndexedAt).toLocaleString()}\n` : '') +
1335
+ (repo.errorMessage ? `Error: ${repo.errorMessage}\n` : '');
1336
+ }
1337
+ case 'codebase_snapshot_status': {
1338
+ const result = await client.getLatestCodeSnapshot(args.repositoryId, {
1339
+ branch: args.branch,
1340
+ committedOnly: args.committedOnly,
1341
+ });
1342
+ if (result.error)
1343
+ return `Error: ${result.error}`;
1344
+ const snapshot = result.data?.snapshot;
1345
+ if (!snapshot)
1346
+ return 'No snapshot found.';
1347
+ return [
1348
+ `Snapshot: ${snapshot.id}`,
1349
+ `Repository: ${snapshot.repositoryId}`,
1350
+ `Commit: ${snapshot.commitSha}`,
1351
+ `Branch: ${snapshot.branch || '(none)'}`,
1352
+ `Mode: ${snapshot.mode}`,
1353
+ `Files: ${snapshot.fileCount}`,
1354
+ `Bytes: ${snapshot.totalBytes}`,
1355
+ `Created: ${new Date(snapshot.createdAt).toLocaleString()}`,
1356
+ ].join('\n');
1357
+ }
1358
+ case 'codebase_materialize_snapshot': {
1359
+ const result = await client.createSnapshotArchiveUrl(args.snapshotId, {
1360
+ expiresMinutes: args.expiresMinutes,
1361
+ });
1362
+ if (result.error)
1363
+ return `Error: ${result.error}`;
1364
+ const payload = result.data;
1365
+ if (!payload)
1366
+ return 'No archive response.';
1367
+ return [
1368
+ `Snapshot: ${payload.snapshot?.id}`,
1369
+ `Commit: ${payload.snapshot?.commitSha}`,
1370
+ `Archive URL: ${payload.archiveUrl}`,
1371
+ `Expires At: ${payload.expiresAt}`,
1372
+ ].join('\n');
1373
+ }
1374
+ case 'codebase_index': {
1375
+ const repositoryId = args.repositoryId;
1376
+ const rootPath = args.rootPath;
1377
+ const includePatterns = args.includePatterns;
1378
+ const excludePatterns = args.excludePatterns;
1379
+ // First verify the repository exists
1380
+ const repoResult = await client.getCodeRepository(repositoryId);
1381
+ if (repoResult.error)
1382
+ return `Error: ${repoResult.error}`;
1383
+ if (!repoResult.data?.repository)
1384
+ return 'Repository not found. Register it first with codebase_register.';
1385
+ // Scan to show what will be indexed
1386
+ const files = (0, localIndexer_js_1.scanDirectory)(rootPath, { includePatterns, excludePatterns });
1387
+ if (files.length === 0) {
1388
+ return `No indexable files found in ${rootPath}. Check your include/exclude patterns.`;
1389
+ }
1390
+ // Index the codebase
1391
+ const progress = await (0, localIndexer_js_1.indexLocalCodebase)(client, repositoryId, rootPath, {
1392
+ includePatterns,
1393
+ excludePatterns,
1394
+ onProgress: (p) => {
1395
+ // Log progress to stderr (stdout is MCP protocol)
1396
+ if (p.phase === 'uploading' && p.currentFile) {
1397
+ console.error(`[${p.filesUploaded}/${p.filesFound}] ${p.currentFile}`);
1398
+ }
1399
+ },
1400
+ });
1401
+ if (progress.phase === 'error') {
1402
+ return `❌ Indexing failed: ${progress.error}`;
1403
+ }
1404
+ return `✅ Indexing complete!\n` +
1405
+ `Files scanned: ${progress.filesFound}\n` +
1406
+ `Files uploaded: ${progress.filesUploaded}\n` +
1407
+ `Chunks created: ${progress.chunksCreated}\n\n` +
1408
+ `Note: Embeddings will be generated in the background. Use codebase_status to check progress.`;
1409
+ }
1410
+ case 'codebase_index_incremental': {
1411
+ const repositoryId = args.repositoryId;
1412
+ const rootPath = args.rootPath;
1413
+ const includePatterns = args.includePatterns;
1414
+ const excludePatterns = args.excludePatterns;
1415
+ const includeWorkingTree = args.includeWorkingTree;
1416
+ // First verify the repository exists
1417
+ const repoResult = await client.getCodeRepository(repositoryId);
1418
+ if (repoResult.error)
1419
+ return `Error: ${repoResult.error}`;
1420
+ if (!repoResult.data?.repository)
1421
+ return 'Repository not found. Register it first with codebase_register.';
1422
+ // Use incremental indexer (git-aware)
1423
+ const progress = await (0, incrementalIndexer_js_1.indexIncrementally)(client, repositoryId, rootPath, {
1424
+ includePatterns,
1425
+ excludePatterns,
1426
+ includeWorkingTree,
1427
+ onProgress: (p) => {
1428
+ if (p.phase === 'uploading' && p.currentFile) {
1429
+ console.error(`[${p.filesUploaded}/${p.filesFound}] ${p.currentFile}`);
1430
+ }
1431
+ },
1432
+ });
1433
+ return (0, incrementalIndexer_js_1.formatIncrementalResult)(progress);
1434
+ }
1435
+ case 'codebase_delete': {
1436
+ const repositoryId = args.repositoryId;
1437
+ // Verify the repository exists first
1438
+ const repoCheck = await client.getCodeRepository(repositoryId);
1439
+ if (repoCheck.error)
1440
+ return `Error: ${repoCheck.error}`;
1441
+ if (!repoCheck.data?.repository)
1442
+ return 'Repository not found.';
1443
+ const repoName = repoCheck.data.repository.name;
1444
+ const result = await client.deleteCodeRepository(repositoryId);
1445
+ if (result.error)
1446
+ return `Error: ${result.error}`;
1447
+ return `✅ Deleted repository "${repoName}" (${repositoryId}) and all its indexed data (files, chunks, embeddings, snapshots).`;
1448
+ }
1449
+ case 'codebase_search': {
1450
+ const result = await client.searchCode({
1451
+ query: args.query,
1452
+ repositoryId: args.repositoryId,
1453
+ language: args.language,
1454
+ symbolType: args.symbolType,
1455
+ limit: args.limit,
1456
+ });
1457
+ if (result.error)
1458
+ return `Error: ${result.error}`;
1459
+ const results = result.data?.results || [];
1460
+ if (results.length === 0)
1461
+ return 'No matching code found.';
1462
+ return results.map((r, i) => {
1463
+ const header = `## ${i + 1}. ${r.filePath}:${r.startLine}-${r.endLine}`;
1464
+ const meta = r.symbolName ? `${r.symbolType}: ${r.symbolName}` : r.symbolType || '';
1465
+ const score = `(similarity: ${(r.similarity * 100).toFixed(1)}%)`;
1466
+ const codeBlock = '```\n' + r.body.slice(0, 500) + (r.body.length > 500 ? '\n...' : '') + '\n```';
1467
+ return `${header}\n${meta} ${score}\n${codeBlock}`;
1468
+ }).join('\n\n');
1469
+ }
1470
+ // =========================================================================
1471
+ // Developer Expertise Tools (Phase 4)
1472
+ // =========================================================================
1473
+ case 'code_who_knows': {
1474
+ const repositoryId = args.repositoryId;
1475
+ const codeArea = args.codeArea;
1476
+ const limit = args.limit;
1477
+ const result = await client.whoKnows(repositoryId, codeArea, limit);
1478
+ if (result.error)
1479
+ return `Error: ${result.error}`;
1480
+ const experts = result.data?.experts || [];
1481
+ if (experts.length === 0) {
1482
+ return `No experts found for "${codeArea}". Try:\n` +
1483
+ `1. Running \`code_compute_expertise\` to build the expertise index\n` +
1484
+ `2. Using a broader code area pattern (e.g., "src/*" instead of "src/services/auth.ts")`;
1485
+ }
1486
+ const header = `# Experts for "${codeArea}"\n`;
1487
+ const rows = experts.map((e, i) => {
1488
+ const score = e.totalScore.toFixed(1);
1489
+ const lastActive = e.lastActive ? new Date(e.lastActive).toLocaleDateString() : 'unknown';
1490
+ return `${i + 1}. **${e.authorName}** (${e.authorEmail})\n` +
1491
+ ` Score: ${score} | Commits: ${e.commitCount} | Lines: ${e.linesChanged}\n` +
1492
+ ` Last active: ${lastActive}\n` +
1493
+ ` Areas: ${e.areas.slice(0, 3).join(', ')}${e.areas.length > 3 ? '...' : ''}`;
1494
+ });
1495
+ return header + rows.join('\n\n');
1496
+ }
1497
+ case 'code_compute_expertise': {
1498
+ const repositoryId = args.repositoryId;
1499
+ const result = await client.computeExpertise(repositoryId);
1500
+ if (result.error)
1501
+ return `Error: ${result.error}`;
1502
+ return `✅ Developer expertise computed!\n` +
1503
+ `Entries updated: ${result.data?.updated || 0}\n\n` +
1504
+ `You can now use \`code_who_knows\` to find experts for any code area.`;
1505
+ }
1506
+ case 'code_history': {
1507
+ const repositoryId = args.repositoryId;
1508
+ const path = args.path;
1509
+ const limit = args.limit || 20;
1510
+ const result = await client.getCommits(repositoryId, { path, limit });
1511
+ if (result.error)
1512
+ return `Error: ${result.error}`;
1513
+ const commits = result.data?.commits || [];
1514
+ if (commits.length === 0) {
1515
+ const pathNote = path ? ` for "${path}"` : '';
1516
+ return `No commit history found${pathNote}. Try:\n` +
1517
+ `1. Running \`codebase_index_incremental\` to index the repository\n` +
1518
+ `2. Checking if the file path is correct`;
1519
+ }
1520
+ const header = path
1521
+ ? `# Commit History for "${path}"\n`
1522
+ : `# Recent Commits\n`;
1523
+ const rows = commits.map((c, i) => {
1524
+ const date = new Date(c.authorDate).toLocaleDateString();
1525
+ const shortSha = c.sha.substring(0, 7);
1526
+ const msgFirstLine = c.message.split('\n')[0].substring(0, 60);
1527
+ const stats = `+${c.linesAdded}/-${c.linesDeleted}`;
1528
+ const tasks = c.taskReferences?.length > 0
1529
+ ? ` (refs: ${c.taskReferences.slice(0, 3).join(', ')})`
1530
+ : '';
1531
+ return `${i + 1}. \`${shortSha}\` ${date} - ${msgFirstLine}${msgFirstLine.length >= 60 ? '...' : ''}\n` +
1532
+ ` Author: ${c.authorName} | ${stats}${tasks}`;
1533
+ });
1534
+ return header + rows.join('\n\n');
1535
+ }
1536
+ case 'git_blame_symbol': {
1537
+ const rootPath = args.rootPath;
1538
+ const filePath = args.filePath;
1539
+ const startLine = args.startLine;
1540
+ const endLine = args.endLine;
1541
+ const gitService = new gitService_js_1.GitService(rootPath);
1542
+ // Check if path is a git repo
1543
+ const isRepo = await gitService.isGitRepository();
1544
+ if (!isRepo) {
1545
+ return `Error: "${rootPath}" is not a git repository`;
1546
+ }
1547
+ const blameEntries = await gitService.getFileBlame(filePath);
1548
+ if (blameEntries.length === 0) {
1549
+ return `No blame data found for "${filePath}". The file may not exist or not be tracked by git.`;
1550
+ }
1551
+ // Filter by line range if specified
1552
+ let filtered = blameEntries;
1553
+ if (startLine || endLine) {
1554
+ const start = startLine || 1;
1555
+ const end = endLine || Infinity;
1556
+ filtered = blameEntries.filter((e) => e.lineStart >= start && e.lineStart <= end);
1557
+ }
1558
+ if (filtered.length === 0) {
1559
+ return `No blame data found for lines ${startLine}-${endLine} in "${filePath}"`;
1560
+ }
1561
+ // Aggregate by author for summary
1562
+ const authorStats = new Map();
1563
+ for (const entry of filtered) {
1564
+ const key = entry.author;
1565
+ if (!authorStats.has(key)) {
1566
+ authorStats.set(key, { lines: 0, commits: new Set() });
1567
+ }
1568
+ const stat = authorStats.get(key);
1569
+ stat.lines += entry.lineCount;
1570
+ stat.commits.add(entry.sha);
1571
+ }
1572
+ const header = `# Git Blame: ${filePath}\n`;
1573
+ const lineRange = startLine || endLine
1574
+ ? `Lines ${startLine || 1}-${endLine || 'end'}\n\n`
1575
+ : '\n';
1576
+ // Sort by lines descending
1577
+ const sorted = Array.from(authorStats.entries())
1578
+ .sort((a, b) => b[1].lines - a[1].lines);
1579
+ const totalLines = filtered.reduce((s, e) => s + e.lineCount, 0);
1580
+ const summary = sorted.map(([author, stat], i) => {
1581
+ const pct = ((stat.lines / totalLines) * 100).toFixed(0);
1582
+ return `${i + 1}. **${author}**: ${stat.lines} lines (${pct}%) across ${stat.commits.size} commits`;
1583
+ }).join('\n');
1584
+ return header + lineRange + summary;
1585
+ }
1586
+ case 'task_link_code': {
1587
+ const taskId = args.taskId;
1588
+ const repositoryId = args.repositoryId;
1589
+ const filePath = args.filePath;
1590
+ const commitSha = args.commitSha;
1591
+ const notes = args.notes;
1592
+ if (!filePath && !commitSha) {
1593
+ return 'Error: At least one of filePath or commitSha must be provided';
1594
+ }
1595
+ const result = await client.linkTaskToCode(taskId, {
1596
+ repositoryId,
1597
+ filePath,
1598
+ commitSha,
1599
+ notes,
1600
+ });
1601
+ if (result.error)
1602
+ return `Error: ${result.error}`;
1603
+ const linkTarget = filePath
1604
+ ? `file "${filePath}"`
1605
+ : `commit ${commitSha?.substring(0, 7)}`;
1606
+ return `✅ Task linked to ${linkTarget}\n` +
1607
+ `Link ID: ${result.data?.id || 'created'}\n` +
1608
+ `Source: ${result.data?.linkSource || 'manual'}`;
1609
+ }
1610
+ // =========================================================================
1611
+ // Code Memory Tools
1612
+ // =========================================================================
1613
+ case 'code_memory_store': {
1614
+ const result = await client.storeCodeMemory({
1615
+ fact: args.fact,
1616
+ category: args.category,
1617
+ filePath: args.filePath,
1618
+ repositoryId: args.repositoryId,
1619
+ });
1620
+ if (result.error)
1621
+ return `Error: ${result.error}`;
1622
+ const memory = result.data;
1623
+ const merged = memory?.createdAt !== memory?.updatedAt;
1624
+ return `✅ Code memory ${merged ? 'updated' : 'stored'}:\n` +
1625
+ `ID: ${memory?.id}\n` +
1626
+ `Category: ${memory?.factType}\n` +
1627
+ `Fact: ${memory?.content}`;
1628
+ }
1629
+ case 'code_memory_search': {
1630
+ const result = await client.searchCodeMemories({
1631
+ query: args.query,
1632
+ category: args.category,
1633
+ repositoryId: args.repositoryId,
1634
+ limit: args.limit,
1635
+ });
1636
+ if (result.error)
1637
+ return `Error: ${result.error}`;
1638
+ const memories = result.data?.memories || [];
1639
+ if (memories.length === 0)
1640
+ return 'No code memories found matching your query.';
1641
+ return memories.map((m, i) => {
1642
+ const category = m.factType.replace('code.', '');
1643
+ const filePath = m.filePath ? ` (${m.filePath})` : '';
1644
+ return `${i + 1}. [${category}] ${m.content}${filePath}`;
1645
+ }).join('\n\n');
1646
+ }
1647
+ case 'code_memory_list': {
1648
+ const result = await client.listCodeMemories({
1649
+ repositoryId: args.repositoryId,
1650
+ limit: args.limit,
1651
+ });
1652
+ if (result.error)
1653
+ return `Error: ${result.error}`;
1654
+ const memories = result.data?.memories || [];
1655
+ if (memories.length === 0)
1656
+ return 'No code memories stored yet.';
1657
+ return `# Code Memories (${memories.length})\n\n` +
1658
+ memories.map((m) => {
1659
+ const category = m.factType.replace('code.', '');
1660
+ const filePath = m.filePath ? `\n File: ${m.filePath}` : '';
1661
+ return `• [${category}] ${m.content}${filePath}\n ID: ${m.id}`;
1662
+ }).join('\n\n');
1663
+ }
1664
+ case 'code_memory_delete': {
1665
+ const result = await client.deleteCodeMemory(args.memoryId);
1666
+ if (result.error)
1667
+ return `Error: ${result.error}`;
1668
+ return result.data?.deleted
1669
+ ? `✅ Code memory deleted: ${args.memoryId}`
1670
+ : `Memory not found: ${args.memoryId}`;
1671
+ }
1672
+ // =========================================================================
1673
+ // Document Upload
1674
+ // =========================================================================
1675
+ case 'upload_document': {
1676
+ const filePath = args.filePath;
1677
+ const content = args.content;
1678
+ const filename = args.filename;
1679
+ if (!filePath && !(content && filename)) {
1680
+ return 'Error: Either filePath or both content and filename are required';
1681
+ }
1682
+ const result = await client.uploadDocument({
1683
+ filePath,
1684
+ content,
1685
+ filename,
1686
+ contentType: args.contentType,
1687
+ title: args.title,
1688
+ noteType: args.noteType,
1689
+ projectId: args.projectId,
1690
+ });
1691
+ if (result.error)
1692
+ return `Error: ${result.error}`;
1693
+ const note = result.data?.note;
1694
+ return `✅ Document uploaded as note: "${note?.title}"\nType: ${note?.noteType || 'note'}\nID: ${note?.id}`;
1695
+ }
1696
+ // Vault / Secrets Management
1697
+ case 'vault_list': {
1698
+ const result = await client.listVaultEntries({
1699
+ entryType: args.entryType,
1700
+ category: args.category,
1701
+ search: args.search,
1702
+ limit: args.limit,
1703
+ });
1704
+ if (result.error)
1705
+ return `Error: ${result.error}`;
1706
+ const entries = result.data?.entries || [];
1707
+ if (entries.length === 0)
1708
+ return 'No vault entries found.';
1709
+ return entries.map((e) => `• [${e.entryType}] ${e.name} (slug: ${e.slug})${e.category ? ` [${e.category}]` : ''}${e.tags?.length ? ` tags: ${e.tags.join(', ')}` : ''}`).join('\n');
1710
+ }
1711
+ case 'vault_create': {
1712
+ const result = await client.createVaultEntry({
1713
+ name: args.name,
1714
+ slug: args.slug,
1715
+ entryType: args.entryType,
1716
+ payload: args.payload,
1717
+ description: args.description,
1718
+ tags: args.tags,
1719
+ category: args.category,
1720
+ url: args.url,
1721
+ });
1722
+ if (result.error)
1723
+ return `Error: ${result.error}`;
1724
+ const entry = result.data?.entry;
1725
+ return `Secret stored: "${entry?.name}" (slug: ${entry?.slug}, type: ${entry?.entryType})`;
1726
+ }
1727
+ case 'vault_read': {
1728
+ const result = await client.readVaultSecret(args.entryId);
1729
+ if (result.error)
1730
+ return `Error: ${result.error}`;
1731
+ const payload = result.data?.payload;
1732
+ if (!payload)
1733
+ return 'No payload returned.';
1734
+ return Object.entries(payload)
1735
+ .map(([k, v]) => `${k}: ${v}`)
1736
+ .join('\n');
1737
+ }
1738
+ case 'vault_update': {
1739
+ const { entryId, ...updates } = args;
1740
+ const result = await client.updateVaultEntry(entryId, {
1741
+ name: updates.name,
1742
+ description: updates.description,
1743
+ tags: updates.tags,
1744
+ category: updates.category,
1745
+ url: updates.url,
1746
+ });
1747
+ if (result.error)
1748
+ return `Error: ${result.error}`;
1749
+ const entry = result.data?.entry;
1750
+ return `Vault entry updated: "${entry?.name}" (slug: ${entry?.slug})`;
1751
+ }
1752
+ case 'vault_search': {
1753
+ const result = await client.searchVaultEntries(args.query, args.limit);
1754
+ if (result.error)
1755
+ return `Error: ${result.error}`;
1756
+ const entries = result.data?.entries || [];
1757
+ if (entries.length === 0)
1758
+ return `No vault entries found matching "${args.query}".`;
1759
+ return entries.map((e) => `• [${e.entryType}] ${e.name} (slug: ${e.slug})${e.category ? ` [${e.category}]` : ''}`).join('\n');
1760
+ }
1761
+ default:
1762
+ return `Unknown tool: ${name}`;
1763
+ }
1764
+ }
1765
+ // =============================================================================
1766
+ // MCP Server Setup
1767
+ // =============================================================================
1768
+ async function main() {
1769
+ // Validate environment
1770
+ let client;
1771
+ try {
1772
+ client = (0, exfClient_js_1.createClientFromEnv)();
1773
+ }
1774
+ catch (err) {
1775
+ console.error(`Configuration error: ${err instanceof Error ? err.message : err}`);
1776
+ console.error('\nRequired environment variables:');
1777
+ console.error(' EXF_API_URL - ExecuFunction API URL');
1778
+ console.error(' EXF_PAT - Personal Access Token');
1779
+ process.exit(1);
1780
+ }
1781
+ const server = new index_js_1.Server({
1782
+ name: 'execufunction',
1783
+ version: '0.1.0',
1784
+ }, {
1785
+ capabilities: {
1786
+ tools: {},
1787
+ resources: {},
1788
+ },
1789
+ });
1790
+ // Handle tool listing
1791
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
1792
+ return { tools: TOOLS.filter((tool) => isToolEnabled(tool.name)) };
1793
+ });
1794
+ // Handle tool execution
1795
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1796
+ const { name, arguments: args } = request.params;
1797
+ if (!isToolEnabled(name)) {
1798
+ return {
1799
+ content: [{ type: 'text', text: `Error: Tool is disabled by feature flag: ${name}` }],
1800
+ isError: true,
1801
+ };
1802
+ }
1803
+ try {
1804
+ const result = await executeTool(client, name, args || {});
1805
+ return {
1806
+ content: [{ type: 'text', text: result }],
1807
+ };
1808
+ }
1809
+ catch (err) {
1810
+ const message = err instanceof Error ? err.message : 'Unknown error';
1811
+ return {
1812
+ content: [{ type: 'text', text: `Error: ${message}` }],
1813
+ isError: true,
1814
+ };
1815
+ }
1816
+ });
1817
+ // Handle resource listing (expose project context as resources)
1818
+ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
1819
+ try {
1820
+ const result = await client.listProjects({ includeArchived: false });
1821
+ if (result.error || !result.data?.projects) {
1822
+ return { resources: [] };
1823
+ }
1824
+ return {
1825
+ resources: result.data.projects.map((p) => ({
1826
+ uri: `exf://projects/${p.id}/context`,
1827
+ name: `${p.emoji || '📁'} ${p.name} Context`,
1828
+ description: `Full context bundle for project: ${p.name}`,
1829
+ mimeType: 'application/json',
1830
+ })),
1831
+ };
1832
+ }
1833
+ catch {
1834
+ return { resources: [] };
1835
+ }
1836
+ });
1837
+ // Handle resource reading
1838
+ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
1839
+ const { uri } = request.params;
1840
+ // Parse exf://projects/{id}/context
1841
+ const match = uri.match(/^exf:\/\/projects\/([^/]+)\/context$/);
1842
+ if (!match) {
1843
+ throw new Error(`Unknown resource URI: ${uri}`);
1844
+ }
1845
+ const projectId = match[1];
1846
+ const result = await client.getProjectContext(projectId);
1847
+ if (result.error) {
1848
+ throw new Error(result.error);
1849
+ }
1850
+ return {
1851
+ contents: [{
1852
+ uri,
1853
+ mimeType: 'application/json',
1854
+ text: JSON.stringify(result.data, null, 2),
1855
+ }],
1856
+ };
1857
+ });
1858
+ // Start server with stdio transport
1859
+ const transport = new stdio_js_1.StdioServerTransport();
1860
+ await server.connect(transport);
1861
+ // Log to stderr (stdout is reserved for MCP protocol)
1862
+ console.error('ExecuFunction MCP server started');
1863
+ }
1864
+ main().catch((err) => {
1865
+ console.error('Fatal error:', err);
1866
+ process.exit(1);
1867
+ });
1868
+ //# sourceMappingURL=index.js.map