contextforge-mcp 0.1.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,1483 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { ApiClient, ApiClientError } from './api-client.js';
6
+ import { ConfigSchema, IngestInputSchema, QueryInputSchema, CreateSpaceInputSchema, RelateInputSchema, DeleteInputSchema, GitConnectInputSchema, GitActivateInputSchema, GitDisconnectInputSchema, GitSyncInputSchema, GitHistoryInputSchema, SnapshotCreateInputSchema, SnapshotRestoreInputSchema, SnapshotDeleteInputSchema, ExportInputSchema, ImportInputSchema, IngestBatchInputSchema, DeleteBatchInputSchema, } from './types.js';
7
+ import { appendFileSync } from 'fs';
8
+ // ============ Logger with Colors ============
9
+ const LOG_FILE = process.env.CONTEXTFORGE_LOG_FILE || '/tmp/contextforge-mcp.log';
10
+ const colors = {
11
+ reset: '\x1b[0m',
12
+ bright: '\x1b[1m',
13
+ dim: '\x1b[2m',
14
+ red: '\x1b[31m',
15
+ green: '\x1b[32m',
16
+ yellow: '\x1b[33m',
17
+ blue: '\x1b[34m',
18
+ magenta: '\x1b[35m',
19
+ cyan: '\x1b[36m',
20
+ white: '\x1b[37m',
21
+ bgBlue: '\x1b[44m',
22
+ bgGreen: '\x1b[42m',
23
+ bgYellow: '\x1b[43m',
24
+ bgRed: '\x1b[41m',
25
+ };
26
+ function log(icon, message, color = colors.white) {
27
+ const timestamp = new Date().toLocaleTimeString();
28
+ const logLine = `${colors.dim}[${timestamp}]${colors.reset} ${icon} ${color}${message}${colors.reset}`;
29
+ console.error(logLine);
30
+ // Also write to log file (without colors)
31
+ const plainLine = `[${timestamp}] ${icon} ${message}\n`;
32
+ try {
33
+ appendFileSync(LOG_FILE, plainLine);
34
+ }
35
+ catch {
36
+ // Ignore file write errors
37
+ }
38
+ }
39
+ function logTool(toolName, details) {
40
+ const icons = {
41
+ memory_ingest: 'đŸ“Ĩ',
42
+ memory_query: '🔍',
43
+ memory_list_spaces: '📂',
44
+ memory_list_items: '📋',
45
+ memory_create_space: '✨',
46
+ memory_relate: '🔗',
47
+ memory_delete: 'đŸ—‘ī¸',
48
+ memory_stats: '📊',
49
+ memory_help: '❓',
50
+ memory_git_connect: '🔌',
51
+ memory_git_list: '📡',
52
+ memory_git_activate: '✅',
53
+ memory_git_disconnect: '🔌',
54
+ memory_git_sync: '🔄',
55
+ memory_git_commits: '📝',
56
+ memory_git_prs: '🔀',
57
+ memory_snapshot_create: '📸',
58
+ memory_snapshot_list: 'đŸ—‚ī¸',
59
+ memory_snapshot_restore: 'âĒ',
60
+ memory_snapshot_delete: 'đŸ—‘ī¸',
61
+ memory_export: '📤',
62
+ memory_import: 'đŸ“Ĩ',
63
+ memory_ingest_batch: 'đŸ“Ļ',
64
+ memory_delete_batch: 'đŸ—‘ī¸',
65
+ };
66
+ const icon = icons[toolName] || '🔧';
67
+ log(icon, `${colors.bright}${toolName}${colors.reset}${details ? ` ${colors.dim}${details}${colors.reset}` : ''}`, colors.cyan);
68
+ }
69
+ function logSuccess(message) {
70
+ log('✅', message, colors.green);
71
+ }
72
+ function logError(message) {
73
+ log('❌', message, colors.red);
74
+ }
75
+ function logInfo(message) {
76
+ log('â„šī¸', message, colors.blue);
77
+ }
78
+ // ============ Configuration ============
79
+ function loadConfig() {
80
+ const apiKey = process.env.CONTEXTFORGE_API_KEY;
81
+ const apiUrl = process.env.CONTEXTFORGE_API_URL ?? 'https://api.contextforge.io';
82
+ const defaultSpace = process.env.CONTEXTFORGE_DEFAULT_SPACE;
83
+ const result = ConfigSchema.safeParse({
84
+ apiKey,
85
+ apiUrl,
86
+ defaultSpace,
87
+ });
88
+ if (!result.success) {
89
+ console.error('Configuration error:', result.error.format());
90
+ console.error('\nRequired environment variables:');
91
+ console.error(' CONTEXTFORGE_API_KEY - Your ContextForge API key');
92
+ console.error('\nOptional environment variables:');
93
+ console.error(' CONTEXTFORGE_API_URL - API URL (default: https://api.contextforge.io)');
94
+ console.error(' CONTEXTFORGE_DEFAULT_SPACE - Default space UUID');
95
+ process.exit(1);
96
+ }
97
+ return result.data;
98
+ }
99
+ // ============ Tool Definitions ============
100
+ const TOOLS = [
101
+ {
102
+ name: 'memory_ingest',
103
+ description: 'Add content to the contextual memory. Use this to store code snippets, documentation, decisions, or any knowledge you want to remember.',
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: {
107
+ content: {
108
+ type: 'string',
109
+ description: 'The content to store in memory',
110
+ },
111
+ title: {
112
+ type: 'string',
113
+ description: 'Optional title for the content',
114
+ },
115
+ source_type: {
116
+ type: 'string',
117
+ enum: ['manual', 'url', 'file_upload', 'api_ingestion'],
118
+ description: 'Type of source (default: manual)',
119
+ },
120
+ source_uri: {
121
+ type: 'string',
122
+ description: 'Optional source URI (file path, URL, etc.)',
123
+ },
124
+ tags: {
125
+ type: 'array',
126
+ items: { type: 'string' },
127
+ description: 'Optional tags for categorization',
128
+ },
129
+ category: {
130
+ type: 'string',
131
+ description: 'Optional category',
132
+ },
133
+ space_id: {
134
+ type: 'string',
135
+ description: 'Space UUID (uses default if not specified)',
136
+ },
137
+ },
138
+ required: ['content'],
139
+ },
140
+ },
141
+ {
142
+ name: 'memory_query',
143
+ description: 'Search the contextual memory using semantic search. Returns the most relevant stored content based on your query.',
144
+ inputSchema: {
145
+ type: 'object',
146
+ properties: {
147
+ query: {
148
+ type: 'string',
149
+ description: 'The search query',
150
+ },
151
+ space_id: {
152
+ type: 'string',
153
+ description: 'Space UUID (uses default if not specified)',
154
+ },
155
+ limit: {
156
+ type: 'number',
157
+ description: 'Maximum number of results (1-50, default: 10)',
158
+ },
159
+ min_score: {
160
+ type: 'number',
161
+ description: 'Minimum similarity score (0-1, default: 0.5)',
162
+ },
163
+ filters: {
164
+ type: 'object',
165
+ properties: {
166
+ tags: {
167
+ type: 'array',
168
+ items: { type: 'string' },
169
+ description: 'Filter by tags',
170
+ },
171
+ source_types: {
172
+ type: 'array',
173
+ items: { type: 'string' },
174
+ description: 'Filter by source types',
175
+ },
176
+ category: {
177
+ type: 'string',
178
+ description: 'Filter by category',
179
+ },
180
+ },
181
+ description: 'Optional filters',
182
+ },
183
+ include_relationships: {
184
+ type: 'boolean',
185
+ description: 'Include related items (default: false)',
186
+ },
187
+ },
188
+ required: ['query'],
189
+ },
190
+ },
191
+ {
192
+ name: 'memory_list_spaces',
193
+ description: 'List all available memory spaces (workspaces)',
194
+ inputSchema: {
195
+ type: 'object',
196
+ properties: {},
197
+ required: [],
198
+ },
199
+ },
200
+ {
201
+ name: 'memory_create_space',
202
+ description: 'Create a new memory space (workspace) for organizing knowledge',
203
+ inputSchema: {
204
+ type: 'object',
205
+ properties: {
206
+ name: {
207
+ type: 'string',
208
+ description: 'Name of the space',
209
+ },
210
+ description: {
211
+ type: 'string',
212
+ description: 'Optional description',
213
+ },
214
+ },
215
+ required: ['name'],
216
+ },
217
+ },
218
+ {
219
+ name: 'memory_relate',
220
+ description: 'Create a relationship between two knowledge items',
221
+ inputSchema: {
222
+ type: 'object',
223
+ properties: {
224
+ source_id: {
225
+ type: 'string',
226
+ description: 'Source item UUID',
227
+ },
228
+ target_id: {
229
+ type: 'string',
230
+ description: 'Target item UUID',
231
+ },
232
+ relationship_type: {
233
+ type: 'string',
234
+ enum: [
235
+ 'references', 'implements', 'extends', 'depends_on',
236
+ 'related_to', 'contradicts', 'supersedes', 'part_of',
237
+ 'similar_to', 'derived_from',
238
+ ],
239
+ description: 'Type of relationship',
240
+ },
241
+ weight: {
242
+ type: 'number',
243
+ description: 'Relationship weight (0-1, default: 0.5)',
244
+ },
245
+ bidirectional: {
246
+ type: 'boolean',
247
+ description: 'Create relationship in both directions (default: false)',
248
+ },
249
+ },
250
+ required: ['source_id', 'target_id', 'relationship_type'],
251
+ },
252
+ },
253
+ {
254
+ name: 'memory_delete',
255
+ description: 'Delete a knowledge item from memory by ID or title',
256
+ inputSchema: {
257
+ type: 'object',
258
+ properties: {
259
+ id: {
260
+ type: 'string',
261
+ description: 'Item UUID to delete (optional if title is provided)',
262
+ },
263
+ title: {
264
+ type: 'string',
265
+ description: 'Item title to search and delete (optional if id is provided)',
266
+ },
267
+ space_id: {
268
+ type: 'string',
269
+ description: 'Space UUID to narrow down title search (optional)',
270
+ },
271
+ cascade: {
272
+ type: 'boolean',
273
+ description: 'Also delete related relationships (default: false)',
274
+ },
275
+ },
276
+ required: [],
277
+ },
278
+ },
279
+ {
280
+ name: 'memory_stats',
281
+ description: 'Get statistics about memory usage',
282
+ inputSchema: {
283
+ type: 'object',
284
+ properties: {
285
+ space_id: {
286
+ type: 'string',
287
+ description: 'Space UUID (optional, shows all if not specified)',
288
+ },
289
+ },
290
+ required: [],
291
+ },
292
+ },
293
+ {
294
+ name: 'memory_list_items',
295
+ description: 'List all items stored in memory. Shows titles, previews, tags, and creation dates.',
296
+ inputSchema: {
297
+ type: 'object',
298
+ properties: {
299
+ space_id: {
300
+ type: 'string',
301
+ description: 'Space UUID (optional, lists all spaces if not specified)',
302
+ },
303
+ limit: {
304
+ type: 'number',
305
+ description: 'Maximum number of items to return (1-100, default: 50)',
306
+ },
307
+ offset: {
308
+ type: 'number',
309
+ description: 'Number of items to skip (for pagination, default: 0)',
310
+ },
311
+ },
312
+ required: [],
313
+ },
314
+ },
315
+ {
316
+ name: 'memory_help',
317
+ description: 'Show help and usage instructions for ContextForge memory commands',
318
+ inputSchema: {
319
+ type: 'object',
320
+ properties: {},
321
+ required: [],
322
+ },
323
+ },
324
+ // ============ Git Integration Tools ============
325
+ {
326
+ name: 'memory_git_connect',
327
+ description: 'Connect a GitHub repository to automatically sync commits and PRs to memory',
328
+ inputSchema: {
329
+ type: 'object',
330
+ properties: {
331
+ repo_url: {
332
+ type: 'string',
333
+ description: 'GitHub repository URL or owner/repo format (e.g., "owner/repo" or "https://github.com/owner/repo")',
334
+ },
335
+ space_id: {
336
+ type: 'string',
337
+ description: 'Space UUID where git knowledge will be stored',
338
+ },
339
+ },
340
+ required: ['repo_url', 'space_id'],
341
+ },
342
+ },
343
+ {
344
+ name: 'memory_git_list',
345
+ description: 'List all connected GitHub repositories',
346
+ inputSchema: {
347
+ type: 'object',
348
+ properties: {
349
+ space_id: {
350
+ type: 'string',
351
+ description: 'Filter by space UUID (optional)',
352
+ },
353
+ },
354
+ required: [],
355
+ },
356
+ },
357
+ {
358
+ name: 'memory_git_activate',
359
+ description: 'Activate or deactivate a connected repository webhook after setup',
360
+ inputSchema: {
361
+ type: 'object',
362
+ properties: {
363
+ repository_id: {
364
+ type: 'string',
365
+ description: 'Repository UUID (optional if repo is provided)',
366
+ },
367
+ repo: {
368
+ type: 'string',
369
+ description: 'Repository name in owner/repo format (optional if repository_id is provided)',
370
+ },
371
+ active: {
372
+ type: 'boolean',
373
+ description: 'Set to true to activate, false to deactivate (default: true)',
374
+ },
375
+ },
376
+ required: [],
377
+ },
378
+ },
379
+ {
380
+ name: 'memory_git_disconnect',
381
+ description: 'Disconnect a GitHub repository and stop syncing',
382
+ inputSchema: {
383
+ type: 'object',
384
+ properties: {
385
+ repository_id: {
386
+ type: 'string',
387
+ description: 'Repository UUID (optional if repo is provided)',
388
+ },
389
+ repo: {
390
+ type: 'string',
391
+ description: 'Repository name in owner/repo format (optional if repository_id is provided)',
392
+ },
393
+ },
394
+ required: [],
395
+ },
396
+ },
397
+ {
398
+ name: 'memory_git_sync',
399
+ description: 'Sync existing commits and PRs from a connected GitHub repository into memory',
400
+ inputSchema: {
401
+ type: 'object',
402
+ properties: {
403
+ repository_id: {
404
+ type: 'string',
405
+ description: 'Repository UUID (optional if repo is provided)',
406
+ },
407
+ repo: {
408
+ type: 'string',
409
+ description: 'Repository name in owner/repo format (optional if repository_id is provided)',
410
+ },
411
+ sync_type: {
412
+ type: 'string',
413
+ enum: ['commits', 'prs', 'all'],
414
+ description: 'What to sync: commits, prs, or all (default: all)',
415
+ },
416
+ limit: {
417
+ type: 'number',
418
+ description: 'Maximum number of items to sync (1-100, default: 30)',
419
+ },
420
+ },
421
+ required: [],
422
+ },
423
+ },
424
+ {
425
+ name: 'memory_git_commits',
426
+ description: 'List commits stored in memory from connected repositories',
427
+ inputSchema: {
428
+ type: 'object',
429
+ properties: {
430
+ repository_id: {
431
+ type: 'string',
432
+ description: 'Filter by repository UUID (optional)',
433
+ },
434
+ repo: {
435
+ type: 'string',
436
+ description: 'Filter by repository name in owner/repo format (optional)',
437
+ },
438
+ space_id: {
439
+ type: 'string',
440
+ description: 'Filter by space UUID (optional)',
441
+ },
442
+ limit: {
443
+ type: 'number',
444
+ description: 'Maximum number of commits to return (1-100, default: 50)',
445
+ },
446
+ offset: {
447
+ type: 'number',
448
+ description: 'Number of items to skip for pagination (default: 0)',
449
+ },
450
+ },
451
+ required: [],
452
+ },
453
+ },
454
+ {
455
+ name: 'memory_git_prs',
456
+ description: 'List pull requests stored in memory from connected repositories',
457
+ inputSchema: {
458
+ type: 'object',
459
+ properties: {
460
+ repository_id: {
461
+ type: 'string',
462
+ description: 'Filter by repository UUID (optional)',
463
+ },
464
+ repo: {
465
+ type: 'string',
466
+ description: 'Filter by repository name in owner/repo format (optional)',
467
+ },
468
+ space_id: {
469
+ type: 'string',
470
+ description: 'Filter by space UUID (optional)',
471
+ },
472
+ limit: {
473
+ type: 'number',
474
+ description: 'Maximum number of PRs to return (1-100, default: 50)',
475
+ },
476
+ offset: {
477
+ type: 'number',
478
+ description: 'Number of items to skip for pagination (default: 0)',
479
+ },
480
+ },
481
+ required: [],
482
+ },
483
+ },
484
+ // ============ Snapshot Tools ============
485
+ {
486
+ name: 'memory_snapshot_create',
487
+ description: 'Create a snapshot (backup) of the current memory state',
488
+ inputSchema: {
489
+ type: 'object',
490
+ properties: {
491
+ space_id: {
492
+ type: 'string',
493
+ description: 'Space UUID to snapshot',
494
+ },
495
+ name: {
496
+ type: 'string',
497
+ description: 'Name for the snapshot (e.g., "Before refactoring")',
498
+ },
499
+ description: {
500
+ type: 'string',
501
+ description: 'Optional description of why this snapshot was created',
502
+ },
503
+ },
504
+ required: ['space_id', 'name'],
505
+ },
506
+ },
507
+ {
508
+ name: 'memory_snapshot_list',
509
+ description: 'List all available snapshots',
510
+ inputSchema: {
511
+ type: 'object',
512
+ properties: {
513
+ space_id: {
514
+ type: 'string',
515
+ description: 'Filter by space UUID (optional)',
516
+ },
517
+ },
518
+ required: [],
519
+ },
520
+ },
521
+ {
522
+ name: 'memory_snapshot_restore',
523
+ description: 'Restore memory to a previous snapshot state',
524
+ inputSchema: {
525
+ type: 'object',
526
+ properties: {
527
+ snapshot_id: {
528
+ type: 'string',
529
+ description: 'Snapshot UUID to restore',
530
+ },
531
+ mode: {
532
+ type: 'string',
533
+ enum: ['merge', 'replace'],
534
+ description: 'merge: add missing items, replace: delete current and restore all (default: merge)',
535
+ },
536
+ },
537
+ required: ['snapshot_id'],
538
+ },
539
+ },
540
+ {
541
+ name: 'memory_snapshot_delete',
542
+ description: 'Delete a snapshot',
543
+ inputSchema: {
544
+ type: 'object',
545
+ properties: {
546
+ snapshot_id: {
547
+ type: 'string',
548
+ description: 'Snapshot UUID to delete',
549
+ },
550
+ },
551
+ required: ['snapshot_id'],
552
+ },
553
+ },
554
+ // ============ Import/Export Tools ============
555
+ {
556
+ name: 'memory_export',
557
+ description: 'Export all items from a space to JSON, Markdown, or CSV format',
558
+ inputSchema: {
559
+ type: 'object',
560
+ properties: {
561
+ space_id: {
562
+ type: 'string',
563
+ description: 'Space UUID to export',
564
+ },
565
+ format: {
566
+ type: 'string',
567
+ enum: ['json', 'markdown', 'csv'],
568
+ description: 'Export format (default: json)',
569
+ },
570
+ },
571
+ required: ['space_id'],
572
+ },
573
+ },
574
+ {
575
+ name: 'memory_import',
576
+ description: 'Import items from JSON, Markdown, Notion, or Obsidian format into a space',
577
+ inputSchema: {
578
+ type: 'object',
579
+ properties: {
580
+ space_id: {
581
+ type: 'string',
582
+ description: 'Space UUID to import into',
583
+ },
584
+ format: {
585
+ type: 'string',
586
+ enum: ['contextforge', 'markdown', 'notion', 'obsidian'],
587
+ description: 'Import format (auto-detected if not specified)',
588
+ },
589
+ data: {
590
+ type: 'object',
591
+ description: 'The data to import (format-specific structure)',
592
+ },
593
+ items: {
594
+ type: 'array',
595
+ description: 'Direct array of items to import (alternative to data)',
596
+ items: {
597
+ type: 'object',
598
+ properties: {
599
+ title: { type: 'string' },
600
+ content: { type: 'string' },
601
+ tags: { type: 'array', items: { type: 'string' } },
602
+ category: { type: 'string' },
603
+ },
604
+ required: ['content'],
605
+ },
606
+ },
607
+ },
608
+ required: ['space_id'],
609
+ },
610
+ },
611
+ // ============ Batch Operations Tools ============
612
+ {
613
+ name: 'memory_ingest_batch',
614
+ description: 'Add multiple items to memory in a single operation. More efficient than multiple single ingests.',
615
+ inputSchema: {
616
+ type: 'object',
617
+ properties: {
618
+ space_id: {
619
+ type: 'string',
620
+ description: 'Space UUID (uses default if not specified)',
621
+ },
622
+ items: {
623
+ type: 'array',
624
+ description: 'Array of items to ingest (max 100)',
625
+ items: {
626
+ type: 'object',
627
+ properties: {
628
+ content: { type: 'string', description: 'The content to store' },
629
+ title: { type: 'string', description: 'Optional title' },
630
+ source_type: {
631
+ type: 'string',
632
+ enum: ['manual', 'url', 'file_upload', 'api_ingestion'],
633
+ description: 'Type of source',
634
+ },
635
+ source_uri: { type: 'string', description: 'Source URI' },
636
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
637
+ category: { type: 'string', description: 'Category' },
638
+ },
639
+ required: ['content'],
640
+ },
641
+ },
642
+ },
643
+ required: ['items'],
644
+ },
645
+ },
646
+ {
647
+ name: 'memory_delete_batch',
648
+ description: 'Delete multiple items from memory based on filters. Use dry_run=true first to preview what will be deleted.',
649
+ inputSchema: {
650
+ type: 'object',
651
+ properties: {
652
+ space_id: {
653
+ type: 'string',
654
+ description: 'Space UUID (optional, deletes from all spaces if not specified)',
655
+ },
656
+ filter: {
657
+ type: 'object',
658
+ description: 'Filters to select items to delete',
659
+ properties: {
660
+ tags: {
661
+ type: 'array',
662
+ items: { type: 'string' },
663
+ description: 'Delete items with any of these tags',
664
+ },
665
+ source_types: {
666
+ type: 'array',
667
+ items: { type: 'string' },
668
+ description: 'Delete items with these source types',
669
+ },
670
+ category: {
671
+ type: 'string',
672
+ description: 'Delete items in this category',
673
+ },
674
+ older_than: {
675
+ type: 'string',
676
+ description: 'Delete items older than this date (ISO format)',
677
+ },
678
+ newer_than: {
679
+ type: 'string',
680
+ description: 'Delete items newer than this date (ISO format)',
681
+ },
682
+ title_contains: {
683
+ type: 'string',
684
+ description: 'Delete items with title containing this text',
685
+ },
686
+ content_contains: {
687
+ type: 'string',
688
+ description: 'Delete items with content containing this text',
689
+ },
690
+ },
691
+ },
692
+ dry_run: {
693
+ type: 'boolean',
694
+ description: 'If true, only preview what would be deleted without actually deleting (default: true for safety)',
695
+ },
696
+ },
697
+ required: [],
698
+ },
699
+ },
700
+ ];
701
+ // ============ Main Server ============
702
+ async function main() {
703
+ const config = loadConfig();
704
+ const apiClient = new ApiClient(config);
705
+ // Startup banner
706
+ console.error('');
707
+ console.error(`${colors.bgBlue}${colors.white}${colors.bright} ContextForge MCP ${colors.reset}`);
708
+ console.error(`${colors.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`);
709
+ console.error(`${colors.cyan}Version:${colors.reset} 0.1.0`);
710
+ console.error(`${colors.cyan}API URL:${colors.reset} ${config.apiUrl}`);
711
+ console.error(`${colors.cyan}Space:${colors.reset} ${config.defaultSpace || '(not set)'}`);
712
+ console.error(`${colors.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`);
713
+ console.error('');
714
+ const server = new Server({
715
+ name: 'contextforge-mcp',
716
+ version: '0.1.0',
717
+ }, {
718
+ capabilities: {
719
+ tools: {},
720
+ },
721
+ });
722
+ // List available tools
723
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
724
+ logInfo(`Tools requested (${TOOLS.length} available)`);
725
+ return { tools: TOOLS };
726
+ });
727
+ // Handle tool calls
728
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
729
+ const { name, arguments: args } = request.params;
730
+ const startTime = Date.now();
731
+ try {
732
+ switch (name) {
733
+ case 'memory_ingest': {
734
+ const input = IngestInputSchema.parse(args);
735
+ const title = input.title || input.content.slice(0, 50) + '...';
736
+ logTool(name, `"${title}"`);
737
+ const result = await apiClient.ingest(input);
738
+ const elapsed = Date.now() - startTime;
739
+ logSuccess(`Saved ${result.created} item(s) in ${elapsed}ms`);
740
+ return {
741
+ content: [
742
+ {
743
+ type: 'text',
744
+ text: JSON.stringify({
745
+ success: true,
746
+ created: result.created,
747
+ duplicates_skipped: result.duplicates_skipped,
748
+ items: result.items,
749
+ }, null, 2),
750
+ },
751
+ ],
752
+ };
753
+ }
754
+ case 'memory_query': {
755
+ const input = QueryInputSchema.parse(args);
756
+ logTool(name, `"${input.query}"`);
757
+ const result = await apiClient.query(input);
758
+ const elapsed = Date.now() - startTime;
759
+ logSuccess(`Found ${result.results.length} result(s) in ${elapsed}ms`);
760
+ return {
761
+ content: [
762
+ {
763
+ type: 'text',
764
+ text: JSON.stringify({
765
+ results: result.results.map(r => ({
766
+ id: r.id,
767
+ title: r.title,
768
+ content: r.content,
769
+ score: r.score,
770
+ source_type: r.source_type,
771
+ tags: r.tags,
772
+ relationships: r.relationships,
773
+ })),
774
+ total: result.results.length,
775
+ latency_ms: result.latency_ms,
776
+ }, null, 2),
777
+ },
778
+ ],
779
+ };
780
+ }
781
+ case 'memory_list_spaces': {
782
+ logTool(name);
783
+ const spaces = await apiClient.listSpaces();
784
+ const elapsed = Date.now() - startTime;
785
+ logSuccess(`Listed ${spaces.length} space(s) in ${elapsed}ms`);
786
+ return {
787
+ content: [
788
+ {
789
+ type: 'text',
790
+ text: JSON.stringify({
791
+ spaces: spaces.map(s => ({
792
+ id: s.id,
793
+ name: s.name,
794
+ slug: s.slug,
795
+ description: s.description,
796
+ })),
797
+ total: spaces.length,
798
+ }, null, 2),
799
+ },
800
+ ],
801
+ };
802
+ }
803
+ case 'memory_create_space': {
804
+ const input = CreateSpaceInputSchema.parse(args);
805
+ logTool(name, `"${input.name}"`);
806
+ const space = await apiClient.createSpace(input);
807
+ const elapsed = Date.now() - startTime;
808
+ logSuccess(`Created space "${space.name}" in ${elapsed}ms`);
809
+ return {
810
+ content: [
811
+ {
812
+ type: 'text',
813
+ text: JSON.stringify({
814
+ success: true,
815
+ space: {
816
+ id: space.id,
817
+ name: space.name,
818
+ slug: space.slug,
819
+ },
820
+ }, null, 2),
821
+ },
822
+ ],
823
+ };
824
+ }
825
+ case 'memory_relate': {
826
+ const input = RelateInputSchema.parse(args);
827
+ logTool(name, `${input.relationship_type}`);
828
+ const relationship = await apiClient.relate(input);
829
+ const elapsed = Date.now() - startTime;
830
+ logSuccess(`Created relationship in ${elapsed}ms`);
831
+ return {
832
+ content: [
833
+ {
834
+ type: 'text',
835
+ text: JSON.stringify({
836
+ success: true,
837
+ relationship: {
838
+ id: relationship.id,
839
+ type: relationship.relationship_type,
840
+ weight: relationship.weight,
841
+ },
842
+ }, null, 2),
843
+ },
844
+ ],
845
+ };
846
+ }
847
+ case 'memory_delete': {
848
+ const input = DeleteInputSchema.parse(args);
849
+ const identifier = input.id || `"${input.title}"`;
850
+ logTool(name, identifier);
851
+ const result = await apiClient.deleteItem(input);
852
+ const elapsed = Date.now() - startTime;
853
+ logSuccess(`Deleted "${result.title}" in ${elapsed}ms`);
854
+ return {
855
+ content: [
856
+ {
857
+ type: 'text',
858
+ text: JSON.stringify({
859
+ success: true,
860
+ deleted: {
861
+ id: result.id,
862
+ title: result.title,
863
+ },
864
+ }, null, 2),
865
+ },
866
+ ],
867
+ };
868
+ }
869
+ case 'memory_stats': {
870
+ logTool(name);
871
+ const spaceId = typeof args === 'object' && args !== null && 'space_id' in args
872
+ ? String(args.space_id)
873
+ : undefined;
874
+ const stats = await apiClient.getStats(spaceId);
875
+ const elapsed = Date.now() - startTime;
876
+ logSuccess(`Got stats in ${elapsed}ms`);
877
+ return {
878
+ content: [
879
+ {
880
+ type: 'text',
881
+ text: JSON.stringify(stats, null, 2),
882
+ },
883
+ ],
884
+ };
885
+ }
886
+ case 'memory_list_items': {
887
+ logTool(name);
888
+ const spaceId = typeof args === 'object' && args !== null && 'space_id' in args
889
+ ? String(args.space_id)
890
+ : undefined;
891
+ const limit = typeof args === 'object' && args !== null && 'limit' in args
892
+ ? Number(args.limit)
893
+ : undefined;
894
+ const offset = typeof args === 'object' && args !== null && 'offset' in args
895
+ ? Number(args.offset)
896
+ : undefined;
897
+ const result = await apiClient.listItems(spaceId, limit, offset);
898
+ const elapsed = Date.now() - startTime;
899
+ logSuccess(`Listed ${result.items.length} of ${result.total} item(s) in ${elapsed}ms`);
900
+ return {
901
+ content: [
902
+ {
903
+ type: 'text',
904
+ text: JSON.stringify({
905
+ items: result.items,
906
+ total: result.total,
907
+ showing: result.items.length,
908
+ offset: result.offset,
909
+ }, null, 2),
910
+ },
911
+ ],
912
+ };
913
+ }
914
+ // ============ Git Integration Handlers ============
915
+ case 'memory_git_connect': {
916
+ const input = GitConnectInputSchema.parse(args);
917
+ logTool(name, input.repo_url);
918
+ const result = await apiClient.gitConnect(input);
919
+ const elapsed = Date.now() - startTime;
920
+ logSuccess(`Connected ${result.repository.full_name} in ${elapsed}ms`);
921
+ return {
922
+ content: [
923
+ {
924
+ type: 'text',
925
+ text: JSON.stringify({
926
+ success: true,
927
+ repository: result.repository,
928
+ webhook_setup: result.webhook_setup,
929
+ message: `Repository connected! Follow the webhook setup instructions to complete the integration.`,
930
+ }, null, 2),
931
+ },
932
+ ],
933
+ };
934
+ }
935
+ case 'memory_git_list': {
936
+ logTool(name);
937
+ const spaceId = typeof args === 'object' && args !== null && 'space_id' in args
938
+ ? String(args.space_id)
939
+ : undefined;
940
+ const result = await apiClient.gitList(spaceId);
941
+ const elapsed = Date.now() - startTime;
942
+ logSuccess(`Listed ${result.total} repository(ies) in ${elapsed}ms`);
943
+ return {
944
+ content: [
945
+ {
946
+ type: 'text',
947
+ text: JSON.stringify({
948
+ repositories: result.repositories,
949
+ total: result.total,
950
+ }, null, 2),
951
+ },
952
+ ],
953
+ };
954
+ }
955
+ case 'memory_git_activate': {
956
+ const input = GitActivateInputSchema.parse(args);
957
+ const identifier = input.repository_id || input.repo;
958
+ logTool(name, identifier);
959
+ const result = await apiClient.gitActivate(input);
960
+ const elapsed = Date.now() - startTime;
961
+ logSuccess(`${input.active !== false ? 'Activated' : 'Deactivated'} ${result.repository.full_name} in ${elapsed}ms`);
962
+ return {
963
+ content: [
964
+ {
965
+ type: 'text',
966
+ text: JSON.stringify(result, null, 2),
967
+ },
968
+ ],
969
+ };
970
+ }
971
+ case 'memory_git_disconnect': {
972
+ const input = GitDisconnectInputSchema.parse(args);
973
+ const identifier = input.repository_id || input.repo;
974
+ logTool(name, identifier);
975
+ const result = await apiClient.gitDisconnect(input);
976
+ const elapsed = Date.now() - startTime;
977
+ logSuccess(`Disconnected repository in ${elapsed}ms`);
978
+ return {
979
+ content: [
980
+ {
981
+ type: 'text',
982
+ text: JSON.stringify(result, null, 2),
983
+ },
984
+ ],
985
+ };
986
+ }
987
+ case 'memory_git_sync': {
988
+ const input = GitSyncInputSchema.parse(args);
989
+ const identifier = input.repository_id || input.repo;
990
+ logTool(name, `${identifier} (${input.sync_type || 'all'})`);
991
+ const result = await apiClient.gitSync(input);
992
+ const elapsed = Date.now() - startTime;
993
+ logSuccess(`Synced ${result.synced} items from ${result.repository.full_name} in ${elapsed}ms`);
994
+ return {
995
+ content: [
996
+ {
997
+ type: 'text',
998
+ text: JSON.stringify({
999
+ success: true,
1000
+ repository: result.repository,
1001
+ sync_type: result.sync_type,
1002
+ synced: result.synced,
1003
+ skipped: result.skipped,
1004
+ items: result.items,
1005
+ message: `Synced ${result.synced} items (${result.skipped} already existed)`,
1006
+ }, null, 2),
1007
+ },
1008
+ ],
1009
+ };
1010
+ }
1011
+ case 'memory_git_commits': {
1012
+ const input = GitHistoryInputSchema.parse({ ...args, type: 'commits' });
1013
+ const identifier = input.repository_id || input.repo || 'all repos';
1014
+ logTool(name, identifier);
1015
+ const result = await apiClient.gitHistory(input);
1016
+ const elapsed = Date.now() - startTime;
1017
+ logSuccess(`Listed ${result.commits_count} commits in ${elapsed}ms`);
1018
+ return {
1019
+ content: [
1020
+ {
1021
+ type: 'text',
1022
+ text: JSON.stringify({
1023
+ commits: result.items,
1024
+ total: result.total,
1025
+ showing: result.items.length,
1026
+ offset: result.offset,
1027
+ }, null, 2),
1028
+ },
1029
+ ],
1030
+ };
1031
+ }
1032
+ case 'memory_git_prs': {
1033
+ const input = GitHistoryInputSchema.parse({ ...args, type: 'prs' });
1034
+ const identifier = input.repository_id || input.repo || 'all repos';
1035
+ logTool(name, identifier);
1036
+ const result = await apiClient.gitHistory(input);
1037
+ const elapsed = Date.now() - startTime;
1038
+ logSuccess(`Listed ${result.prs_count} PRs in ${elapsed}ms`);
1039
+ return {
1040
+ content: [
1041
+ {
1042
+ type: 'text',
1043
+ text: JSON.stringify({
1044
+ pull_requests: result.items,
1045
+ total: result.total,
1046
+ showing: result.items.length,
1047
+ offset: result.offset,
1048
+ }, null, 2),
1049
+ },
1050
+ ],
1051
+ };
1052
+ }
1053
+ // ============ Snapshot Handlers ============
1054
+ case 'memory_snapshot_create': {
1055
+ const input = SnapshotCreateInputSchema.parse(args);
1056
+ logTool(name, `"${input.name}"`);
1057
+ const result = await apiClient.snapshotCreate(input);
1058
+ const elapsed = Date.now() - startTime;
1059
+ logSuccess(`Created snapshot "${result.snapshot.name}" with ${result.snapshot.item_count} items in ${elapsed}ms`);
1060
+ return {
1061
+ content: [
1062
+ {
1063
+ type: 'text',
1064
+ text: JSON.stringify({
1065
+ success: true,
1066
+ snapshot: result.snapshot,
1067
+ message: `Snapshot "${result.snapshot.name}" created with ${result.snapshot.item_count} items`,
1068
+ }, null, 2),
1069
+ },
1070
+ ],
1071
+ };
1072
+ }
1073
+ case 'memory_snapshot_list': {
1074
+ logTool(name);
1075
+ const spaceId = typeof args === 'object' && args !== null && 'space_id' in args
1076
+ ? String(args.space_id)
1077
+ : undefined;
1078
+ const result = await apiClient.snapshotList(spaceId);
1079
+ const elapsed = Date.now() - startTime;
1080
+ logSuccess(`Listed ${result.total} snapshot(s) in ${elapsed}ms`);
1081
+ return {
1082
+ content: [
1083
+ {
1084
+ type: 'text',
1085
+ text: JSON.stringify({
1086
+ snapshots: result.snapshots,
1087
+ total: result.total,
1088
+ }, null, 2),
1089
+ },
1090
+ ],
1091
+ };
1092
+ }
1093
+ case 'memory_snapshot_restore': {
1094
+ const input = SnapshotRestoreInputSchema.parse(args);
1095
+ logTool(name, input.snapshot_id);
1096
+ const result = await apiClient.snapshotRestore(input);
1097
+ const elapsed = Date.now() - startTime;
1098
+ logSuccess(`Restored from "${result.restored_from.name}" (${result.stats.restored_count} items) in ${elapsed}ms`);
1099
+ return {
1100
+ content: [
1101
+ {
1102
+ type: 'text',
1103
+ text: JSON.stringify({
1104
+ success: true,
1105
+ restored_from: result.restored_from,
1106
+ mode: result.mode,
1107
+ stats: result.stats,
1108
+ auto_backup_id: result.auto_backup_id,
1109
+ message: `Restored ${result.stats.restored_count} items from snapshot "${result.restored_from.name}"`,
1110
+ }, null, 2),
1111
+ },
1112
+ ],
1113
+ };
1114
+ }
1115
+ case 'memory_snapshot_delete': {
1116
+ const input = SnapshotDeleteInputSchema.parse(args);
1117
+ logTool(name, input.snapshot_id);
1118
+ const result = await apiClient.snapshotDelete(input);
1119
+ const elapsed = Date.now() - startTime;
1120
+ logSuccess(`Deleted snapshot in ${elapsed}ms`);
1121
+ return {
1122
+ content: [
1123
+ {
1124
+ type: 'text',
1125
+ text: JSON.stringify(result, null, 2),
1126
+ },
1127
+ ],
1128
+ };
1129
+ }
1130
+ // ============ Import/Export Handlers ============
1131
+ case 'memory_export': {
1132
+ const input = ExportInputSchema.parse(args);
1133
+ logTool(name, `space ${input.space_id} as ${input.format || 'json'}`);
1134
+ const result = await apiClient.exportSpace(input);
1135
+ const elapsed = Date.now() - startTime;
1136
+ logSuccess(`Exported ${result.total_items} items in ${elapsed}ms`);
1137
+ return {
1138
+ content: [
1139
+ {
1140
+ type: 'text',
1141
+ text: JSON.stringify({
1142
+ success: true,
1143
+ exported_at: result.exported_at,
1144
+ space: result.space,
1145
+ total_items: result.total_items,
1146
+ format: input.format || 'json',
1147
+ data: result,
1148
+ }, null, 2),
1149
+ },
1150
+ ],
1151
+ };
1152
+ }
1153
+ case 'memory_import': {
1154
+ const input = ImportInputSchema.parse(args);
1155
+ logTool(name, `to space ${input.space_id}`);
1156
+ const result = await apiClient.importToSpace(input);
1157
+ const elapsed = Date.now() - startTime;
1158
+ logSuccess(`Imported ${result.imported} items (${result.skipped} skipped) in ${elapsed}ms`);
1159
+ return {
1160
+ content: [
1161
+ {
1162
+ type: 'text',
1163
+ text: JSON.stringify({
1164
+ success: true,
1165
+ imported: result.imported,
1166
+ skipped: result.skipped,
1167
+ errors: result.errors,
1168
+ total_processed: result.total_processed,
1169
+ space: result.space,
1170
+ }, null, 2),
1171
+ },
1172
+ ],
1173
+ };
1174
+ }
1175
+ // ============ Batch Operations Handlers ============
1176
+ case 'memory_ingest_batch': {
1177
+ const input = IngestBatchInputSchema.parse(args);
1178
+ logTool(name, `${input.items.length} items`);
1179
+ const result = await apiClient.ingestBatchItems(input);
1180
+ const elapsed = Date.now() - startTime;
1181
+ logSuccess(`Ingested ${result.created} items (${result.duplicates_skipped} duplicates) in ${elapsed}ms`);
1182
+ return {
1183
+ content: [
1184
+ {
1185
+ type: 'text',
1186
+ text: JSON.stringify({
1187
+ success: true,
1188
+ created: result.created,
1189
+ duplicates_skipped: result.duplicates_skipped,
1190
+ items: result.items,
1191
+ message: `Successfully ingested ${result.created} items`,
1192
+ }, null, 2),
1193
+ },
1194
+ ],
1195
+ };
1196
+ }
1197
+ case 'memory_delete_batch': {
1198
+ const input = DeleteBatchInputSchema.parse(args);
1199
+ const isDryRun = input.dry_run !== false;
1200
+ logTool(name, isDryRun ? '(dry run)' : '(executing)');
1201
+ const result = await apiClient.deleteBatch(input);
1202
+ const elapsed = Date.now() - startTime;
1203
+ if (result.dry_run) {
1204
+ logSuccess(`Would delete ${result.would_delete} items in ${elapsed}ms (dry run)`);
1205
+ }
1206
+ else {
1207
+ logSuccess(`Deleted ${result.deleted} items in ${elapsed}ms`);
1208
+ }
1209
+ return {
1210
+ content: [
1211
+ {
1212
+ type: 'text',
1213
+ text: JSON.stringify(result, null, 2),
1214
+ },
1215
+ ],
1216
+ };
1217
+ }
1218
+ case 'memory_help': {
1219
+ logTool(name);
1220
+ const helpText = `
1221
+ # ContextForge Memory - Help
1222
+
1223
+ ## Available Commands
1224
+
1225
+ ### đŸ“Ĩ Save to Memory
1226
+ Save code, documentation, decisions, or any knowledge.
1227
+ \`\`\`
1228
+ "save to memory that this project uses React 18"
1229
+ "remember how the authentication flow works"
1230
+ "store this API documentation in memory"
1231
+ \`\`\`
1232
+
1233
+ ### 🔍 Search Memory
1234
+ Find relevant information using semantic search.
1235
+ \`\`\`
1236
+ "search my memory for authentication"
1237
+ "what do I have saved about React?"
1238
+ "find information about the login flow"
1239
+ \`\`\`
1240
+
1241
+ ### 📋 List Items
1242
+ View all saved items in your memory.
1243
+ \`\`\`
1244
+ "list all my saved memory items"
1245
+ "show me what's in my memory"
1246
+ "what have I saved?"
1247
+ \`\`\`
1248
+
1249
+ ### 📂 List Spaces
1250
+ View all your memory workspaces.
1251
+ \`\`\`
1252
+ "list my memory spaces"
1253
+ "show my workspaces"
1254
+ \`\`\`
1255
+
1256
+ ### ✨ Create Space
1257
+ Create a new workspace to organize knowledge.
1258
+ \`\`\`
1259
+ "create a new memory space called 'Backend API'"
1260
+ "make a new workspace for the mobile app"
1261
+ \`\`\`
1262
+
1263
+ ### đŸ—‘ī¸ Delete Item
1264
+ Remove an item from memory by title or ID.
1265
+ \`\`\`
1266
+ "delete the item about authentication from memory"
1267
+ "remove 'React Setup' from my memory"
1268
+ "delete item with id [uuid] from memory"
1269
+ \`\`\`
1270
+
1271
+ ### 🔗 Create Relationship
1272
+ Link two knowledge items together.
1273
+ \`\`\`
1274
+ "relate [item1] to [item2] as 'implements'"
1275
+ \`\`\`
1276
+
1277
+ ### 📊 Get Stats
1278
+ View memory usage statistics.
1279
+ \`\`\`
1280
+ "show my memory stats"
1281
+ \`\`\`
1282
+
1283
+ ---
1284
+
1285
+ ## Git Integration
1286
+
1287
+ ### 🔌 Connect Repository
1288
+ Connect a GitHub repo to auto-sync commits and PRs.
1289
+ \`\`\`
1290
+ "connect my github repo owner/repo-name to space [space-id]"
1291
+ "sync my repository https://github.com/user/project"
1292
+ \`\`\`
1293
+
1294
+ ### 📡 List Connected Repos
1295
+ View all connected repositories.
1296
+ \`\`\`
1297
+ "list my connected git repositories"
1298
+ "show my synced repos"
1299
+ \`\`\`
1300
+
1301
+ ### 🔄 Sync Commits/PRs
1302
+ Sync existing commits and PRs from GitHub into memory.
1303
+ \`\`\`
1304
+ "sync commits from owner/repo"
1305
+ "sync all from my-project"
1306
+ "sync the last 50 PRs from owner/repo"
1307
+ \`\`\`
1308
+
1309
+ ### 📝 List Commits
1310
+ View commits stored in memory.
1311
+ \`\`\`
1312
+ "list commits from owner/repo"
1313
+ "show my synced commits"
1314
+ "list the last 20 commits"
1315
+ \`\`\`
1316
+
1317
+ ### 🔀 List Pull Requests
1318
+ View PRs stored in memory.
1319
+ \`\`\`
1320
+ "list PRs from owner/repo"
1321
+ "show my synced pull requests"
1322
+ \`\`\`
1323
+
1324
+ ### ✅ Activate/Deactivate
1325
+ Enable or disable syncing for a repository.
1326
+ \`\`\`
1327
+ "activate the webhook for owner/repo"
1328
+ "disable git sync for my-project"
1329
+ \`\`\`
1330
+
1331
+ ### 🔌 Disconnect Repository
1332
+ Stop syncing and remove a repository.
1333
+ \`\`\`
1334
+ "disconnect owner/repo from memory"
1335
+ "remove git sync for my-project"
1336
+ \`\`\`
1337
+
1338
+ ---
1339
+
1340
+ ## Snapshots (Version Control)
1341
+
1342
+ ### 📸 Create Snapshot
1343
+ Save the current state of your memory.
1344
+ \`\`\`
1345
+ "create a snapshot called 'Before refactoring'"
1346
+ "backup my memory state"
1347
+ \`\`\`
1348
+
1349
+ ### đŸ—‚ī¸ List Snapshots
1350
+ View all available snapshots.
1351
+ \`\`\`
1352
+ "list my memory snapshots"
1353
+ "show available backups"
1354
+ \`\`\`
1355
+
1356
+ ### âĒ Restore Snapshot
1357
+ Restore memory to a previous state.
1358
+ \`\`\`
1359
+ "restore snapshot [id]"
1360
+ "rollback to 'Before refactoring'"
1361
+ \`\`\`
1362
+
1363
+ ### đŸ—‘ī¸ Delete Snapshot
1364
+ Remove a snapshot.
1365
+ \`\`\`
1366
+ "delete snapshot [id]"
1367
+ \`\`\`
1368
+
1369
+ ---
1370
+
1371
+ ## Import/Export
1372
+
1373
+ ### 📤 Export Space
1374
+ Export all items from a space.
1375
+ \`\`\`
1376
+ "export my Backend space as JSON"
1377
+ "export space [id] to markdown"
1378
+ "download my memory as CSV"
1379
+ \`\`\`
1380
+
1381
+ ### đŸ“Ĩ Import Data
1382
+ Import from various sources.
1383
+ \`\`\`
1384
+ "import this JSON data into my space"
1385
+ "import from my Obsidian vault"
1386
+ "import these items to memory"
1387
+ \`\`\`
1388
+
1389
+ Supported formats:
1390
+ - **contextforge**: Our native JSON format
1391
+ - **markdown**: Markdown files (split by ## headers)
1392
+ - **notion**: Notion export JSON
1393
+ - **obsidian**: Obsidian vault export
1394
+
1395
+ ## Tips
1396
+ - Memory persists across all your Claude Code sessions
1397
+ - Search is semantic - it understands meaning, not just keywords
1398
+ - Use tags to categorize your knowledge
1399
+ - Create separate spaces for different projects
1400
+
1401
+ ## More Info
1402
+ https://github.com/contextforge/contextforge-mcp
1403
+ `;
1404
+ return {
1405
+ content: [
1406
+ {
1407
+ type: 'text',
1408
+ text: helpText,
1409
+ },
1410
+ ],
1411
+ };
1412
+ }
1413
+ default:
1414
+ logError(`Unknown tool: ${name}`);
1415
+ return {
1416
+ content: [
1417
+ {
1418
+ type: 'text',
1419
+ text: JSON.stringify({
1420
+ error: `Unknown tool: ${name}`,
1421
+ }),
1422
+ },
1423
+ ],
1424
+ isError: true,
1425
+ };
1426
+ }
1427
+ }
1428
+ catch (error) {
1429
+ const elapsed = Date.now() - startTime;
1430
+ if (error instanceof ApiClientError) {
1431
+ logError(`${error.message} (${error.statusCode}) [${elapsed}ms]`);
1432
+ return {
1433
+ content: [
1434
+ {
1435
+ type: 'text',
1436
+ text: JSON.stringify({
1437
+ error: error.message,
1438
+ code: error.code,
1439
+ statusCode: error.statusCode,
1440
+ }),
1441
+ },
1442
+ ],
1443
+ isError: true,
1444
+ };
1445
+ }
1446
+ if (error instanceof Error) {
1447
+ logError(`${error.message} [${elapsed}ms]`);
1448
+ return {
1449
+ content: [
1450
+ {
1451
+ type: 'text',
1452
+ text: JSON.stringify({
1453
+ error: error.message,
1454
+ }),
1455
+ },
1456
+ ],
1457
+ isError: true,
1458
+ };
1459
+ }
1460
+ logError(`Unknown error [${elapsed}ms]`);
1461
+ return {
1462
+ content: [
1463
+ {
1464
+ type: 'text',
1465
+ text: JSON.stringify({
1466
+ error: 'Unknown error occurred',
1467
+ }),
1468
+ },
1469
+ ],
1470
+ isError: true,
1471
+ };
1472
+ }
1473
+ });
1474
+ // Start the server
1475
+ const transport = new StdioServerTransport();
1476
+ await server.connect(transport);
1477
+ logInfo('Server connected and ready');
1478
+ }
1479
+ main().catch((error) => {
1480
+ console.error(`${colors.red}Fatal error:${colors.reset}`, error);
1481
+ process.exit(1);
1482
+ });
1483
+ //# sourceMappingURL=index.js.map