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/README.md +195 -0
- package/dist/api-client.d.ts +74 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +310 -0
- package/dist/api-client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1483 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +706 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +156 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
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
|