@utaba/deep-memory-local-mcp-server 0.16.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/LICENSE +190 -0
- package/README.md +143 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2050 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2050 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
import { resolve as resolve2 } from "path";
|
|
6
|
+
|
|
7
|
+
// src/server/McpServer.ts
|
|
8
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ListToolsRequestSchema
|
|
13
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
import {
|
|
15
|
+
DeepMemory,
|
|
16
|
+
InMemoryStorageProvider,
|
|
17
|
+
InMemorySearchProvider
|
|
18
|
+
} from "@utaba/deep-memory";
|
|
19
|
+
import { OpenAIEmbeddingProvider } from "@utaba/deep-memory-embeddings-openai";
|
|
20
|
+
import { SqlServerStorageProvider } from "@utaba/deep-memory-storage-sqlserver";
|
|
21
|
+
import { CosmosDbProvider } from "@utaba/deep-memory-storage-cosmosdb";
|
|
22
|
+
|
|
23
|
+
// src/tools/base/BaseToolController.ts
|
|
24
|
+
import { isValidUuid, EntityNotFoundError } from "@utaba/deep-memory";
|
|
25
|
+
var BaseToolController = class {
|
|
26
|
+
constructor(context, logger2) {
|
|
27
|
+
this.context = context;
|
|
28
|
+
this.logger = logger2;
|
|
29
|
+
}
|
|
30
|
+
context;
|
|
31
|
+
logger;
|
|
32
|
+
async execute(params) {
|
|
33
|
+
try {
|
|
34
|
+
this.validateParams(params);
|
|
35
|
+
return await this.handleExecute(params);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
+
this.logger.error(this.name, `Execution failed: ${message}`);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
validateParams(params) {
|
|
43
|
+
const schema = this.inputSchema;
|
|
44
|
+
const required = schema["required"];
|
|
45
|
+
if (required) {
|
|
46
|
+
for (const field of required) {
|
|
47
|
+
if (params[field] === void 0 || params[field] === null) {
|
|
48
|
+
throw new Error(`Required parameter '${field}' is missing`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve an entity identifier that may be either a GUID or a slug.
|
|
55
|
+
* Returns the GUID.
|
|
56
|
+
*/
|
|
57
|
+
async resolveEntityId(repo, entityIdOrSlug) {
|
|
58
|
+
if (isValidUuid(entityIdOrSlug)) {
|
|
59
|
+
return entityIdOrSlug;
|
|
60
|
+
}
|
|
61
|
+
const entity = await repo.getBySlug(entityIdOrSlug);
|
|
62
|
+
if (!entity) {
|
|
63
|
+
throw new EntityNotFoundError(entityIdOrSlug);
|
|
64
|
+
}
|
|
65
|
+
return entity.id;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/tools/repository/CreateRepositoryTool.ts
|
|
70
|
+
var CreateRepositoryTool = class extends BaseToolController {
|
|
71
|
+
get name() {
|
|
72
|
+
return "memory_create_repository";
|
|
73
|
+
}
|
|
74
|
+
get description() {
|
|
75
|
+
return "Create a new memory repository with an optional vocabulary definition and governance mode";
|
|
76
|
+
}
|
|
77
|
+
get inputSchema() {
|
|
78
|
+
return {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
repositoryId: { type: "string", description: "Unique identifier for the repository" },
|
|
82
|
+
label: { type: "string", description: "Human-readable name" },
|
|
83
|
+
description: { type: "string", description: "Purpose of this repository" },
|
|
84
|
+
type: { type: "string", description: "Repository type (optional classifier)" },
|
|
85
|
+
legal: { type: "string", description: "Legal or compliance notes for this repository" },
|
|
86
|
+
owner: { type: "string", description: "Owner of the repository (person, team, or org)" },
|
|
87
|
+
governanceMode: { type: "string", enum: ["locked", "managed", "open"], description: "Vocabulary governance mode (default: open)" },
|
|
88
|
+
defaultSimilarityThreshold: { type: "number", description: "Default similarity threshold for semantic search (0.0-1.0, default: 0.5). Lower for local embedding models, higher for OpenAI." },
|
|
89
|
+
metadata: {
|
|
90
|
+
type: "object",
|
|
91
|
+
description: "Extensible metadata object. Embedding model info (embeddingModelId, embeddingDimensions) is auto-detected from the configured provider if omitted."
|
|
92
|
+
},
|
|
93
|
+
vocabulary: {
|
|
94
|
+
type: "object",
|
|
95
|
+
description: "Initial vocabulary with entityTypes and relationshipTypes arrays",
|
|
96
|
+
properties: {
|
|
97
|
+
entityTypes: {
|
|
98
|
+
type: "array",
|
|
99
|
+
items: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
type: { type: "string" },
|
|
103
|
+
description: { type: "string" },
|
|
104
|
+
properties: { type: "array", items: { type: "object" } }
|
|
105
|
+
},
|
|
106
|
+
required: ["type", "description"]
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
relationshipTypes: {
|
|
110
|
+
type: "array",
|
|
111
|
+
items: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
type: { type: "string" },
|
|
115
|
+
description: { type: "string" },
|
|
116
|
+
allowedSourceTypes: { type: "array", items: { type: "string" } },
|
|
117
|
+
allowedTargetTypes: { type: "array", items: { type: "string" } },
|
|
118
|
+
bidirectional: { type: "boolean" },
|
|
119
|
+
properties: { type: "array", items: { type: "object" } }
|
|
120
|
+
},
|
|
121
|
+
required: ["type", "description", "allowedSourceTypes", "allowedTargetTypes"]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
required: ["repositoryId", "label"]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async handleExecute(params) {
|
|
131
|
+
const mode = params["governanceMode"] ?? "open";
|
|
132
|
+
const repo = await this.context.deepMemory.createRepository({
|
|
133
|
+
repositoryId: params["repositoryId"],
|
|
134
|
+
label: params["label"],
|
|
135
|
+
description: params["description"],
|
|
136
|
+
type: params["type"],
|
|
137
|
+
legal: params["legal"],
|
|
138
|
+
owner: params["owner"],
|
|
139
|
+
vocabulary: params["vocabulary"],
|
|
140
|
+
governance: {
|
|
141
|
+
mode,
|
|
142
|
+
defaultSimilarityThreshold: params["defaultSimilarityThreshold"]
|
|
143
|
+
},
|
|
144
|
+
metadata: params["metadata"]
|
|
145
|
+
});
|
|
146
|
+
return { repositoryId: repo.repositoryId, message: `Repository '${params["label"]}' created` };
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/tools/repository/OpenRepositoryTool.ts
|
|
151
|
+
var OpenRepositoryTool = class _OpenRepositoryTool extends BaseToolController {
|
|
152
|
+
get name() {
|
|
153
|
+
return "memory_open_repository";
|
|
154
|
+
}
|
|
155
|
+
get description() {
|
|
156
|
+
return "Open a memory repository by ID or label. Call this first before any entity, relationship, or graph operations. Returns vocabulary and stats.";
|
|
157
|
+
}
|
|
158
|
+
get inputSchema() {
|
|
159
|
+
return {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
repositoryId: { type: "string", description: "ID of the repository to open" },
|
|
163
|
+
label: { type: "string", description: "Repository label \u2014 use as alternative to repositoryId" }
|
|
164
|
+
},
|
|
165
|
+
required: []
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async handleExecute(params) {
|
|
169
|
+
const repositoryId = params["repositoryId"];
|
|
170
|
+
const label = params["label"];
|
|
171
|
+
if (!repositoryId && !label) {
|
|
172
|
+
throw new Error("Provide either repositoryId or label");
|
|
173
|
+
}
|
|
174
|
+
let resolvedId;
|
|
175
|
+
if (repositoryId) {
|
|
176
|
+
resolvedId = repositoryId;
|
|
177
|
+
} else {
|
|
178
|
+
const allRepos = await this.context.deepMemory.listRepositories();
|
|
179
|
+
const matches = allRepos.items.filter(
|
|
180
|
+
(r) => r.label.toLowerCase() === label.toLowerCase()
|
|
181
|
+
);
|
|
182
|
+
if (matches.length === 0) {
|
|
183
|
+
throw new Error(`No repository found with label '${label}'`);
|
|
184
|
+
}
|
|
185
|
+
if (matches.length > 1) {
|
|
186
|
+
return {
|
|
187
|
+
error: "ambiguous_label",
|
|
188
|
+
message: `Multiple repositories match label '${label}'. Provide a repositoryId.`,
|
|
189
|
+
candidates: matches.map((r) => ({ repositoryId: r.repositoryId, label: r.label }))
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
resolvedId = matches[0].repositoryId;
|
|
193
|
+
}
|
|
194
|
+
const repo = await this.context.getRepository(resolvedId);
|
|
195
|
+
const [vocabulary, stats] = await Promise.all([
|
|
196
|
+
repo.getVocabulary(),
|
|
197
|
+
repo.getStats()
|
|
198
|
+
]);
|
|
199
|
+
return {
|
|
200
|
+
repositoryId: repo.repositoryId,
|
|
201
|
+
message: `Repository '${resolvedId}' opened`,
|
|
202
|
+
vocabulary: _OpenRepositoryTool.stripAuditFields(vocabulary),
|
|
203
|
+
stats,
|
|
204
|
+
agentInstructions: repo.getQueryGuide()
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** Strip audit/versioning fields from vocabulary to reduce response size for AI consumers */
|
|
208
|
+
static stripAuditFields(resolved) {
|
|
209
|
+
const { vocabulary, governanceMode, governanceConfig } = resolved;
|
|
210
|
+
return {
|
|
211
|
+
governanceMode,
|
|
212
|
+
governanceConfig,
|
|
213
|
+
entityTypes: vocabulary.entityTypes.map(({ type, description, properties }) => ({
|
|
214
|
+
type,
|
|
215
|
+
description,
|
|
216
|
+
properties
|
|
217
|
+
})),
|
|
218
|
+
relationshipTypes: vocabulary.relationshipTypes.map(({ type, description, allowedSourceTypes, allowedTargetTypes, bidirectional, properties }) => ({
|
|
219
|
+
type,
|
|
220
|
+
description,
|
|
221
|
+
allowedSourceTypes,
|
|
222
|
+
allowedTargetTypes,
|
|
223
|
+
bidirectional,
|
|
224
|
+
...properties?.length ? { properties } : {}
|
|
225
|
+
}))
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// src/tools/repository/ListRepositoriesTool.ts
|
|
231
|
+
var ListRepositoriesTool = class extends BaseToolController {
|
|
232
|
+
get name() {
|
|
233
|
+
return "memory_list_repositories";
|
|
234
|
+
}
|
|
235
|
+
get description() {
|
|
236
|
+
return "List all available memory repositories";
|
|
237
|
+
}
|
|
238
|
+
get inputSchema() {
|
|
239
|
+
return {
|
|
240
|
+
type: "object",
|
|
241
|
+
properties: {
|
|
242
|
+
type: { type: "string", description: "Filter by repository type" },
|
|
243
|
+
limit: { type: "number", description: "Max results (default 20)" },
|
|
244
|
+
offset: { type: "number", description: "Pagination offset (default 0)" }
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
async handleExecute(params) {
|
|
249
|
+
const filter = {};
|
|
250
|
+
if (params["type"]) filter["type"] = params["type"];
|
|
251
|
+
filter["limit"] = params["limit"] ?? 20;
|
|
252
|
+
filter["offset"] = params["offset"] ?? 0;
|
|
253
|
+
return this.context.deepMemory.listRepositories(filter);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/tools/repository/UpdateRepositoryTool.ts
|
|
258
|
+
var UpdateRepositoryTool = class extends BaseToolController {
|
|
259
|
+
get name() {
|
|
260
|
+
return "memory_update_repository";
|
|
261
|
+
}
|
|
262
|
+
get description() {
|
|
263
|
+
return "Update repository metadata and settings \u2014 label, description, governance mode, similarity threshold, etc.";
|
|
264
|
+
}
|
|
265
|
+
get inputSchema() {
|
|
266
|
+
return {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
repositoryId: { type: "string", description: "Repository to update" },
|
|
270
|
+
label: { type: "string", description: "New label" },
|
|
271
|
+
description: { type: "string", description: "New description" },
|
|
272
|
+
type: { type: "string", description: "New repository type" },
|
|
273
|
+
owner: { type: "string", description: "New owner" },
|
|
274
|
+
governanceMode: { type: "string", enum: ["locked", "managed", "open"], description: "New governance mode" },
|
|
275
|
+
defaultSimilarityThreshold: { type: "number", description: "Default similarity threshold for semantic search (0.0-1.0). Model-dependent \u2014 lower for local models, higher for OpenAI." },
|
|
276
|
+
requireApproval: { type: "boolean", description: "Managed mode: if true, all vocabulary proposals queue for human approval" },
|
|
277
|
+
deduplicationEnabled: { type: "boolean", description: "Open mode: whether vocabulary deduplication is enforced (default true)" },
|
|
278
|
+
autoApproveThreshold: { type: "number", description: "Managed mode: similarity score below which vocabulary proposals auto-approve (default 0.3)" },
|
|
279
|
+
metadata: { type: "object", description: 'Metadata updates (shallow-merged with existing). E.g. { "embeddingModelId": "...", "embeddingDimensions": 4096 }' }
|
|
280
|
+
},
|
|
281
|
+
required: ["repositoryId"]
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async handleExecute(params) {
|
|
285
|
+
const repositoryId = params["repositoryId"];
|
|
286
|
+
const governanceMode = params["governanceMode"];
|
|
287
|
+
const defaultSimilarityThreshold = params["defaultSimilarityThreshold"];
|
|
288
|
+
const requireApproval = params["requireApproval"];
|
|
289
|
+
const deduplicationEnabled = params["deduplicationEnabled"];
|
|
290
|
+
const autoApproveThreshold = params["autoApproveThreshold"];
|
|
291
|
+
const hasGovernanceUpdate = governanceMode !== void 0 || defaultSimilarityThreshold !== void 0 || requireApproval !== void 0 || deduplicationEnabled !== void 0 || autoApproveThreshold !== void 0;
|
|
292
|
+
let governanceConfig;
|
|
293
|
+
if (hasGovernanceUpdate) {
|
|
294
|
+
const repo = await this.context.getRepository(repositoryId);
|
|
295
|
+
const current = (await repo.getVocabulary()).governanceConfig;
|
|
296
|
+
governanceConfig = {
|
|
297
|
+
...current,
|
|
298
|
+
...governanceMode !== void 0 ? { mode: governanceMode } : {},
|
|
299
|
+
...defaultSimilarityThreshold !== void 0 ? { defaultSimilarityThreshold } : {},
|
|
300
|
+
...requireApproval !== void 0 ? { requireApproval } : {},
|
|
301
|
+
...deduplicationEnabled !== void 0 ? { deduplicationEnabled } : {},
|
|
302
|
+
...autoApproveThreshold !== void 0 ? { autoApproveThreshold } : {}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const updated = await this.context.deepMemory.updateRepository(repositoryId, {
|
|
306
|
+
label: params["label"],
|
|
307
|
+
description: params["description"],
|
|
308
|
+
type: params["type"],
|
|
309
|
+
owner: params["owner"],
|
|
310
|
+
governanceConfig,
|
|
311
|
+
metadata: params["metadata"]
|
|
312
|
+
});
|
|
313
|
+
this.context.evictRepository(repositoryId);
|
|
314
|
+
return { repositoryId: updated.repositoryId, message: `Repository '${updated.label}' updated` };
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// src/tools/repository/DeleteRepositoryTool.ts
|
|
319
|
+
var DeleteRepositoryTool = class extends BaseToolController {
|
|
320
|
+
get name() {
|
|
321
|
+
return "memory_delete_repository";
|
|
322
|
+
}
|
|
323
|
+
get description() {
|
|
324
|
+
return "Delete a memory repository and all its data permanently. Use deleteContentsOnly to remove all entities and relationships while keeping the repository and vocabulary intact.";
|
|
325
|
+
}
|
|
326
|
+
get inputSchema() {
|
|
327
|
+
return {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties: {
|
|
330
|
+
repositoryId: { type: "string", description: "ID of the repository to delete" },
|
|
331
|
+
deleteContentsOnly: { type: "boolean", description: "When true, only delete entities and relationships \u2014 the repository and vocabulary are preserved. Defaults to false." }
|
|
332
|
+
},
|
|
333
|
+
required: ["repositoryId"]
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
async handleExecute(params) {
|
|
337
|
+
const repositoryId = params["repositoryId"];
|
|
338
|
+
const deleteContentsOnly = params["deleteContentsOnly"] === true;
|
|
339
|
+
if (deleteContentsOnly) {
|
|
340
|
+
const result = await this.context.deepMemory.deleteAllContents(repositoryId);
|
|
341
|
+
this.context.evictRepository(repositoryId);
|
|
342
|
+
return {
|
|
343
|
+
message: `Repository '${repositoryId}' contents deleted (${result.deletedEntities} entities, ${result.deletedRelationships} relationships removed). Repository and vocabulary preserved.`,
|
|
344
|
+
deletedEntities: result.deletedEntities,
|
|
345
|
+
deletedRelationships: result.deletedRelationships
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
await this.context.deepMemory.deleteRepository(repositoryId);
|
|
349
|
+
this.context.evictRepository(repositoryId);
|
|
350
|
+
return { message: `Repository '${repositoryId}' deleted` };
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// src/tools/repository/EnsureSchemaTool.ts
|
|
355
|
+
var EnsureSchemaTool = class extends BaseToolController {
|
|
356
|
+
get name() {
|
|
357
|
+
return "memory_ensure_schema";
|
|
358
|
+
}
|
|
359
|
+
get description() {
|
|
360
|
+
return "Ensure the storage provider schema exists (creates tables/indexes if needed). Idempotent \u2014 safe to call multiple times. Only relevant for persistent storage providers (e.g., SQL Server); no-op for in-memory storage.";
|
|
361
|
+
}
|
|
362
|
+
get inputSchema() {
|
|
363
|
+
return {
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: {}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async handleExecute(_params) {
|
|
369
|
+
const result = await this.context.deepMemory.ensureSchema();
|
|
370
|
+
return { success: true, ...result };
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/tools/repository/ValidateEntitiesTool.ts
|
|
375
|
+
var ValidateEntitiesTool = class extends BaseToolController {
|
|
376
|
+
get name() {
|
|
377
|
+
return "memory_validate_entities";
|
|
378
|
+
}
|
|
379
|
+
get description() {
|
|
380
|
+
return "Audit entities in the repository against the current vocabulary. Paging is issue-based: `take` caps how many issues are returned, `offset` skips that many issues. Typical workflow: call with offset=0, fix the returned issues, call again with offset=0. `offset` is only for inspecting a later slice without mutating. Loop until `done` is true. Returns issues fixable with memory_update_entity. Use memory_validate_relationships for relationships.";
|
|
381
|
+
}
|
|
382
|
+
get inputSchema() {
|
|
383
|
+
return {
|
|
384
|
+
type: "object",
|
|
385
|
+
properties: {
|
|
386
|
+
repositoryId: { type: "string", description: "Repository to validate" },
|
|
387
|
+
offset: { type: "number", description: "Number of issues to skip before returning (default 0). The normal fix-and-retry workflow always uses 0; pass nextOffset only to inspect a later slice without fixing." },
|
|
388
|
+
take: { type: "number", description: "Maximum issues to return in this call (default 200)" },
|
|
389
|
+
delayBetweenChunksMs: { type: "number", description: "Pause between export chunks for manual rate limiting (default 0)" }
|
|
390
|
+
},
|
|
391
|
+
required: ["repositoryId"]
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
async handleExecute(params) {
|
|
395
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
396
|
+
return repo.validateEntities({
|
|
397
|
+
offset: params["offset"],
|
|
398
|
+
take: params["take"],
|
|
399
|
+
delayBetweenChunksMs: params["delayBetweenChunksMs"]
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// src/tools/repository/ValidateRelationshipsTool.ts
|
|
405
|
+
var ValidateRelationshipsTool = class extends BaseToolController {
|
|
406
|
+
get name() {
|
|
407
|
+
return "memory_validate_relationships";
|
|
408
|
+
}
|
|
409
|
+
get description() {
|
|
410
|
+
return "Audit relationships in the repository against the current vocabulary. Paging is issue-based: `take` caps how many issues are returned, `offset` skips that many issues. Typical workflow: call with offset=0, fix the returned issues, call again with offset=0. `offset` is only for inspecting a later slice without mutating. Loop until `done` is true. The full entity set is loaded once per call to resolve orphan and type-mismatch checks. Returns issues fixable with memory_remove_relationships / memory_create_relationships.";
|
|
411
|
+
}
|
|
412
|
+
get inputSchema() {
|
|
413
|
+
return {
|
|
414
|
+
type: "object",
|
|
415
|
+
properties: {
|
|
416
|
+
repositoryId: { type: "string", description: "Repository to validate" },
|
|
417
|
+
offset: { type: "number", description: "Number of issues to skip before returning (default 0). The normal fix-and-retry workflow always uses 0; pass nextOffset only to inspect a later slice without fixing." },
|
|
418
|
+
take: { type: "number", description: "Maximum issues to return in this call (default 200)" },
|
|
419
|
+
delayBetweenChunksMs: { type: "number", description: "Pause between export chunks for manual rate limiting (default 0)" }
|
|
420
|
+
},
|
|
421
|
+
required: ["repositoryId"]
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
async handleExecute(params) {
|
|
425
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
426
|
+
return repo.validateRelationships({
|
|
427
|
+
offset: params["offset"],
|
|
428
|
+
take: params["take"],
|
|
429
|
+
delayBetweenChunksMs: params["delayBetweenChunksMs"]
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// src/tools/entity/CreateEntitiesTool.ts
|
|
435
|
+
var CreateEntitiesTool = class extends BaseToolController {
|
|
436
|
+
get name() {
|
|
437
|
+
return "memory_create_entities";
|
|
438
|
+
}
|
|
439
|
+
get description() {
|
|
440
|
+
return "Create one or more entities (nodes) in a repository. Entity types and properties must match the repository vocabulary \u2014 call memory_open_repository first to see valid types.";
|
|
441
|
+
}
|
|
442
|
+
get inputSchema() {
|
|
443
|
+
return {
|
|
444
|
+
type: "object",
|
|
445
|
+
properties: {
|
|
446
|
+
repositoryId: { type: "string", description: "Repository to create the entities in" },
|
|
447
|
+
entities: {
|
|
448
|
+
type: "array",
|
|
449
|
+
description: "Array of entities to create",
|
|
450
|
+
items: {
|
|
451
|
+
type: "object",
|
|
452
|
+
properties: {
|
|
453
|
+
entityType: { type: "string", description: "Entity type (must exist in vocabulary)" },
|
|
454
|
+
label: { type: "string", description: "Human-readable label" },
|
|
455
|
+
summary: { type: "string", description: "Brief description of the entity" },
|
|
456
|
+
properties: { type: "object", description: "Typed properties per vocabulary schema" },
|
|
457
|
+
data: { type: "string", description: "Raw content/data for the entity" },
|
|
458
|
+
dataFormat: { type: "string", description: "Format of the data field (e.g. text/plain, text/markdown)" }
|
|
459
|
+
},
|
|
460
|
+
required: ["entityType", "label"]
|
|
461
|
+
},
|
|
462
|
+
minItems: 1
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
required: ["repositoryId", "entities"]
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
async handleExecute(params) {
|
|
469
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
470
|
+
const entities = params["entities"];
|
|
471
|
+
return repo.createEntities(entities.map((e) => ({
|
|
472
|
+
entityType: e.entityType,
|
|
473
|
+
label: e.label,
|
|
474
|
+
summary: e.summary,
|
|
475
|
+
properties: e.properties,
|
|
476
|
+
data: e.data,
|
|
477
|
+
dataFormat: e.dataFormat
|
|
478
|
+
})));
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// src/tools/entity/UpdateEntityTool.ts
|
|
483
|
+
var UpdateEntityTool = class extends BaseToolController {
|
|
484
|
+
get name() {
|
|
485
|
+
return "memory_update_entity";
|
|
486
|
+
}
|
|
487
|
+
get description() {
|
|
488
|
+
return "Update an existing entity \u2014 entityType, label, summary, properties, or data. Accepts entity ID (GUID) or slug. Changing entityType must be valid in the vocabulary and regenerates the slug with the new type prefix. To clear an optional field, pass null: summary/data/dataFormat set to null are cleared, and property values set to null are removed from the entity (RFC 7396 JSON Merge Patch). Omitting a field leaves it unchanged.";
|
|
489
|
+
}
|
|
490
|
+
get inputSchema() {
|
|
491
|
+
return {
|
|
492
|
+
type: "object",
|
|
493
|
+
properties: {
|
|
494
|
+
repositoryId: { type: "string", description: "Repository containing the entity" },
|
|
495
|
+
entityId: { type: "string", description: "Entity ID (GUID) or slug" },
|
|
496
|
+
entityType: { type: "string", description: "New entity type \u2014 must exist in the vocabulary. Regenerates the slug and re-validates properties against the new type." },
|
|
497
|
+
label: { type: "string", description: "New label" },
|
|
498
|
+
summary: { type: ["string", "null"], description: "New summary. Pass null to clear the existing summary." },
|
|
499
|
+
properties: { type: "object", description: "Properties to merge with existing. Property values set to null are removed from the entity (RFC 7396 JSON Merge Patch semantics)." },
|
|
500
|
+
data: { type: ["string", "null"], description: "New raw content/data. Pass null to clear." },
|
|
501
|
+
dataFormat: { type: ["string", "null"], description: "Format of the data field. Pass null to clear." },
|
|
502
|
+
reembed: { type: "boolean", description: "Force regeneration of the embedding vector (e.g. after switching embedding models)" }
|
|
503
|
+
},
|
|
504
|
+
required: ["repositoryId", "entityId"]
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
async handleExecute(params) {
|
|
508
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
509
|
+
const resolvedId = await this.resolveEntityId(repo, params["entityId"]);
|
|
510
|
+
const updates = {};
|
|
511
|
+
if (params["entityType"] !== void 0) updates["entityType"] = params["entityType"];
|
|
512
|
+
if (params["label"] !== void 0) updates["label"] = params["label"];
|
|
513
|
+
if (params["summary"] !== void 0) updates["summary"] = params["summary"];
|
|
514
|
+
if (params["properties"] !== void 0) updates["properties"] = params["properties"];
|
|
515
|
+
if (params["data"] !== void 0) updates["data"] = params["data"];
|
|
516
|
+
if (params["dataFormat"] !== void 0) updates["dataFormat"] = params["dataFormat"];
|
|
517
|
+
if (params["reembed"] !== void 0) updates["reembed"] = params["reembed"];
|
|
518
|
+
return repo.updateEntity(resolvedId, updates);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// src/tools/base/stripProvenance.ts
|
|
523
|
+
function stripProvenance(obj) {
|
|
524
|
+
const { provenance, ...rest } = obj;
|
|
525
|
+
return rest;
|
|
526
|
+
}
|
|
527
|
+
function stripProvenanceArray(items) {
|
|
528
|
+
return items.map(stripProvenance);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/tools/entity/GetEntityTool.ts
|
|
532
|
+
var GetEntityTool = class extends BaseToolController {
|
|
533
|
+
get name() {
|
|
534
|
+
return "memory_get_entity";
|
|
535
|
+
}
|
|
536
|
+
get description() {
|
|
537
|
+
return "Retrieve a single entity by ID (GUID) or slug with configurable detail level (brief, summary, or full)";
|
|
538
|
+
}
|
|
539
|
+
get inputSchema() {
|
|
540
|
+
return {
|
|
541
|
+
type: "object",
|
|
542
|
+
properties: {
|
|
543
|
+
repositoryId: { type: "string", description: "Repository containing the entity" },
|
|
544
|
+
entityId: { type: "string", description: 'Entity ID (GUID) or slug (e.g. "person:john-smith")' },
|
|
545
|
+
detailLevel: { type: "string", enum: ["brief", "summary", "full"], description: "Level of detail (default: full)" }
|
|
546
|
+
},
|
|
547
|
+
required: ["repositoryId", "entityId"]
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
async handleExecute(params) {
|
|
551
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
552
|
+
const resolvedId = await this.resolveEntityId(repo, params["entityId"]);
|
|
553
|
+
const detailLevel = params["detailLevel"] ?? "full";
|
|
554
|
+
const entity = await repo.getEntity(resolvedId, detailLevel);
|
|
555
|
+
if (!entity) {
|
|
556
|
+
throw new Error(`Entity '${params["entityId"]}' not found`);
|
|
557
|
+
}
|
|
558
|
+
return detailLevel === "full" ? stripProvenance(entity) : entity;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// src/tools/base/provenanceHelper.ts
|
|
563
|
+
function buildProvenanceFilter(params) {
|
|
564
|
+
const conversationId = params["conversationId"];
|
|
565
|
+
const actor = params["actor"];
|
|
566
|
+
const dateRange = params["dateRange"];
|
|
567
|
+
if (!conversationId && !actor && !dateRange) return void 0;
|
|
568
|
+
return {
|
|
569
|
+
conversationIds: conversationId ? [conversationId] : void 0,
|
|
570
|
+
actors: actor ? [actor] : void 0,
|
|
571
|
+
dateRange
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/tools/entity/FindEntitiesTool.ts
|
|
576
|
+
var FindEntitiesTool = class extends BaseToolController {
|
|
577
|
+
get name() {
|
|
578
|
+
return "memory_find_entities";
|
|
579
|
+
}
|
|
580
|
+
get description() {
|
|
581
|
+
return "Search for entities by label, type, or properties with pagination. Requires a repository to be opened first via memory_open_repository.";
|
|
582
|
+
}
|
|
583
|
+
get inputSchema() {
|
|
584
|
+
return {
|
|
585
|
+
type: "object",
|
|
586
|
+
properties: {
|
|
587
|
+
repositoryId: { type: "string", description: "Repository to search in" },
|
|
588
|
+
searchTerm: { type: "string", description: "Search term to match against entity labels" },
|
|
589
|
+
entityType: { type: "string", description: "Filter by a single entity type (convenience alias for entityTypes)" },
|
|
590
|
+
entityTypes: { type: "array", items: { type: "string" }, description: "Filter by entity type(s)" },
|
|
591
|
+
properties: { type: "object", description: "Filter by property values" },
|
|
592
|
+
limit: { type: "number", description: "Max results (default 10, max 50)" },
|
|
593
|
+
offset: { type: "number", description: "Pagination offset" },
|
|
594
|
+
detailLevel: { type: "string", enum: ["brief", "summary", "full"], description: "Detail level for returned entities (default: summary)" },
|
|
595
|
+
conversationId: { type: "string", description: "Filter to entities from this conversation" },
|
|
596
|
+
actor: { type: "string", description: "Filter to entities created/modified by this actor" },
|
|
597
|
+
dateRange: { type: "object", properties: { from: { type: "string" }, to: { type: "string" } }, description: "Filter by date range (ISO 8601)" },
|
|
598
|
+
includeRelationshipSummary: { type: "boolean", description: "Attach a relationship count summary (outbound/inbound by type) to each entity (default false)" }
|
|
599
|
+
},
|
|
600
|
+
required: ["repositoryId"]
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
async handleExecute(params) {
|
|
604
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
605
|
+
let entityTypes = params["entityTypes"];
|
|
606
|
+
if (!entityTypes && params["entityType"]) {
|
|
607
|
+
entityTypes = [params["entityType"]];
|
|
608
|
+
}
|
|
609
|
+
let result = await repo.findEntities({
|
|
610
|
+
searchTerm: params["searchTerm"],
|
|
611
|
+
entityTypes,
|
|
612
|
+
properties: params["properties"],
|
|
613
|
+
limit: params["limit"],
|
|
614
|
+
offset: params["offset"],
|
|
615
|
+
detailLevel: params["detailLevel"],
|
|
616
|
+
provenance: buildProvenanceFilter(params)
|
|
617
|
+
});
|
|
618
|
+
if (params["detailLevel"] === "full") {
|
|
619
|
+
result = { ...result, items: stripProvenanceArray(result.items) };
|
|
620
|
+
}
|
|
621
|
+
if (!params["includeRelationshipSummary"]) {
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
const itemsWithSummary = await Promise.all(
|
|
625
|
+
result.items.map(async (entity) => {
|
|
626
|
+
const relationshipSummary = await repo.getRelationshipSummary(entity.id);
|
|
627
|
+
return { ...entity, relationshipSummary };
|
|
628
|
+
})
|
|
629
|
+
);
|
|
630
|
+
return { ...result, items: itemsWithSummary };
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
// src/tools/entity/DeleteEntitiesTool.ts
|
|
635
|
+
var DeleteEntitiesTool = class extends BaseToolController {
|
|
636
|
+
get name() {
|
|
637
|
+
return "memory_delete_entities";
|
|
638
|
+
}
|
|
639
|
+
get description() {
|
|
640
|
+
return "Delete one or more entities and their associated relationships from the knowledge graph in a single batch operation. Accepts entity IDs (GUIDs) or slugs.";
|
|
641
|
+
}
|
|
642
|
+
get inputSchema() {
|
|
643
|
+
return {
|
|
644
|
+
type: "object",
|
|
645
|
+
properties: {
|
|
646
|
+
repositoryId: { type: "string", description: "Repository containing the entities" },
|
|
647
|
+
entityIds: {
|
|
648
|
+
type: "array",
|
|
649
|
+
description: "Entity IDs (GUIDs or slugs) to delete",
|
|
650
|
+
items: { type: "string" },
|
|
651
|
+
minItems: 1
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
required: ["repositoryId", "entityIds"]
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
async handleExecute(params) {
|
|
658
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
659
|
+
const rawIds = params["entityIds"];
|
|
660
|
+
const resolvedIds = await Promise.all(rawIds.map((id) => this.resolveEntityId(repo, id)));
|
|
661
|
+
return repo.deleteEntities(resolvedIds);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// src/tools/entity/ReembedRepositoryTool.ts
|
|
666
|
+
var ReembedRepositoryTool = class extends BaseToolController {
|
|
667
|
+
get name() {
|
|
668
|
+
return "memory_reembed_repository";
|
|
669
|
+
}
|
|
670
|
+
get description() {
|
|
671
|
+
return "Re-embed all entities in a repository. Optionally switch to a different embedding model or dimensionality \u2014 the repository metadata is updated first so subsequent writes use the new configuration. Retries failed batches with exponential backoff. Aborts early if error threshold is reached.";
|
|
672
|
+
}
|
|
673
|
+
get inputSchema() {
|
|
674
|
+
return {
|
|
675
|
+
type: "object",
|
|
676
|
+
properties: {
|
|
677
|
+
repositoryId: { type: "string", description: "Repository to re-embed" },
|
|
678
|
+
model: { type: "string", description: 'New embedding model identifier to switch the repository to (e.g. "Qwen/Qwen3-Embedding-8B"). Omit to keep the current model.' },
|
|
679
|
+
dimensions: { type: "number", description: "New embedding dimensionality to switch the repository to (e.g. 1024). Omit to keep the current dimensionality." },
|
|
680
|
+
batchSize: { type: "number", description: "Entities per batch (default 50, max 200)" },
|
|
681
|
+
maxRetries: { type: "number", description: "Retries per batch on embedding API failure with exponential backoff (default 3)" },
|
|
682
|
+
errorThresholdToAbort: { type: "number", description: "Abort after this many cumulative failures. Omit for no limit." },
|
|
683
|
+
delayBetweenBatchesMs: { type: "number", description: "Milliseconds to wait between batches for rate limiting (default 0)" }
|
|
684
|
+
},
|
|
685
|
+
required: ["repositoryId"]
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async handleExecute(params) {
|
|
689
|
+
const repositoryId = params["repositoryId"];
|
|
690
|
+
const batchSize = Math.min(params["batchSize"] ?? 50, 200);
|
|
691
|
+
const result = await this.context.deepMemory.reembedAll(repositoryId, {
|
|
692
|
+
model: params["model"],
|
|
693
|
+
dimensions: params["dimensions"],
|
|
694
|
+
batchSize,
|
|
695
|
+
maxRetries: params["maxRetries"],
|
|
696
|
+
errorThresholdToAbort: params["errorThresholdToAbort"],
|
|
697
|
+
delayBetweenBatchesMs: params["delayBetweenBatchesMs"]
|
|
698
|
+
});
|
|
699
|
+
return {
|
|
700
|
+
processed: result.processed,
|
|
701
|
+
failed: result.failed,
|
|
702
|
+
errors: result.errors,
|
|
703
|
+
modelId: result.modelId,
|
|
704
|
+
dimensions: result.dimensions
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// src/tools/relationship/CreateRelationshipsTool.ts
|
|
710
|
+
var CreateRelationshipsTool = class extends BaseToolController {
|
|
711
|
+
get name() {
|
|
712
|
+
return "memory_create_relationships";
|
|
713
|
+
}
|
|
714
|
+
get description() {
|
|
715
|
+
return "Create one or more relationships (edges) between entities. Relationship types must be in the vocabulary. Entity references accept either GUID or slug.";
|
|
716
|
+
}
|
|
717
|
+
get inputSchema() {
|
|
718
|
+
return {
|
|
719
|
+
type: "object",
|
|
720
|
+
properties: {
|
|
721
|
+
repositoryId: { type: "string", description: "Repository containing the entities" },
|
|
722
|
+
relationships: {
|
|
723
|
+
type: "array",
|
|
724
|
+
description: "Array of relationships to create",
|
|
725
|
+
items: {
|
|
726
|
+
type: "object",
|
|
727
|
+
properties: {
|
|
728
|
+
relationshipType: { type: "string", description: "Relationship type (must exist in vocabulary)" },
|
|
729
|
+
sourceEntityId: { type: "string", description: "Source entity ID (GUID) or slug" },
|
|
730
|
+
targetEntityId: { type: "string", description: "Target entity ID (GUID) or slug" },
|
|
731
|
+
properties: { type: "object", description: "Typed properties per vocabulary schema" }
|
|
732
|
+
},
|
|
733
|
+
required: ["relationshipType", "sourceEntityId", "targetEntityId"]
|
|
734
|
+
},
|
|
735
|
+
minItems: 1
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
required: ["repositoryId", "relationships"]
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
async handleExecute(params) {
|
|
742
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
743
|
+
const relationships = params["relationships"];
|
|
744
|
+
const resolved = await Promise.all(
|
|
745
|
+
relationships.map(async (r) => ({
|
|
746
|
+
relationshipType: r.relationshipType,
|
|
747
|
+
sourceEntityId: await this.resolveEntityId(repo, r.sourceEntityId),
|
|
748
|
+
targetEntityId: await this.resolveEntityId(repo, r.targetEntityId),
|
|
749
|
+
properties: r.properties
|
|
750
|
+
}))
|
|
751
|
+
);
|
|
752
|
+
return repo.createRelationships(resolved);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// src/tools/relationship/RemoveRelationshipsTool.ts
|
|
757
|
+
var RemoveRelationshipsTool = class extends BaseToolController {
|
|
758
|
+
get name() {
|
|
759
|
+
return "memory_remove_relationships";
|
|
760
|
+
}
|
|
761
|
+
get description() {
|
|
762
|
+
return "Remove one or more relationships (edges) from the knowledge graph in a single batch operation";
|
|
763
|
+
}
|
|
764
|
+
get inputSchema() {
|
|
765
|
+
return {
|
|
766
|
+
type: "object",
|
|
767
|
+
properties: {
|
|
768
|
+
repositoryId: { type: "string", description: "Repository containing the relationships" },
|
|
769
|
+
relationshipIds: {
|
|
770
|
+
type: "array",
|
|
771
|
+
description: "IDs of relationships to remove",
|
|
772
|
+
items: { type: "string" },
|
|
773
|
+
minItems: 1
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
required: ["repositoryId", "relationshipIds"]
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
async handleExecute(params) {
|
|
780
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
781
|
+
return repo.removeRelationships(params["relationshipIds"]);
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// src/tools/relationship/GetRelationshipsTool.ts
|
|
786
|
+
var GetRelationshipsTool = class extends BaseToolController {
|
|
787
|
+
get name() {
|
|
788
|
+
return "memory_get_relationships";
|
|
789
|
+
}
|
|
790
|
+
get description() {
|
|
791
|
+
return "Get all relationships for a given entity, with optional type and direction filters. Accepts entity ID (GUID) or slug.";
|
|
792
|
+
}
|
|
793
|
+
get inputSchema() {
|
|
794
|
+
return {
|
|
795
|
+
type: "object",
|
|
796
|
+
properties: {
|
|
797
|
+
repositoryId: { type: "string", description: "Repository containing the entity" },
|
|
798
|
+
entityId: { type: "string", description: "Entity ID (GUID) or slug" },
|
|
799
|
+
relationshipTypes: { type: "array", items: { type: "string" }, description: "Filter by relationship type(s)" },
|
|
800
|
+
direction: { type: "string", enum: ["outbound", "inbound", "both"], description: "Direction filter (default: both)" },
|
|
801
|
+
limit: { type: "number", description: "Max results (default 20, max 100)" },
|
|
802
|
+
offset: { type: "number", description: "Pagination offset (default 0)" },
|
|
803
|
+
propertyFilters: { type: "array", items: { type: "object", properties: { key: { type: "string" }, operator: { type: "string", enum: ["eq", "neq", "isNull", "isNotNull", "gt", "lt", "gte", "lte", "contains"] }, value: {} }, required: ["key", "operator"] }, description: "Filter by relationship property values (AND)" }
|
|
804
|
+
},
|
|
805
|
+
required: ["repositoryId", "entityId"]
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
async handleExecute(params) {
|
|
809
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
810
|
+
const resolvedId = await this.resolveEntityId(repo, params["entityId"]);
|
|
811
|
+
const rawLimit = params["limit"];
|
|
812
|
+
const limit = Math.min(rawLimit ?? 20, 100);
|
|
813
|
+
const result = await repo.getRelationships(resolvedId, {
|
|
814
|
+
relationshipTypes: params["relationshipTypes"],
|
|
815
|
+
direction: params["direction"],
|
|
816
|
+
limit,
|
|
817
|
+
offset: params["offset"] ?? 0,
|
|
818
|
+
propertyFilters: params["propertyFilters"]
|
|
819
|
+
});
|
|
820
|
+
return { ...result, items: stripProvenanceArray(result.items) };
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
// src/tools/graph/ExploreNeighborhoodTool.ts
|
|
825
|
+
var ExploreNeighborhoodTool = class extends BaseToolController {
|
|
826
|
+
get name() {
|
|
827
|
+
return "memory_explore_neighborhood";
|
|
828
|
+
}
|
|
829
|
+
get description() {
|
|
830
|
+
return "Explore the neighborhood of an entity using BFS traversal (depth 1-3). Accepts entity ID (GUID) or slug.";
|
|
831
|
+
}
|
|
832
|
+
get inputSchema() {
|
|
833
|
+
return {
|
|
834
|
+
type: "object",
|
|
835
|
+
properties: {
|
|
836
|
+
repositoryId: { type: "string", description: "Repository containing the entity" },
|
|
837
|
+
entityId: { type: "string", description: "Starting entity ID (GUID) or slug" },
|
|
838
|
+
depth: { type: "number", enum: [1, 2, 3], description: "Exploration depth (default: 1)" },
|
|
839
|
+
relationshipTypes: { type: "array", items: { type: "string" }, description: "Filter by relationship type(s)" },
|
|
840
|
+
entityTypes: { type: "array", items: { type: "string" }, description: "Filter result entity types" },
|
|
841
|
+
direction: { type: "string", enum: ["outbound", "inbound", "both"], description: "Direction filter (default: both)" },
|
|
842
|
+
limitPerType: { type: "number", description: "Max entities per relationship type (default 10, max 50)" },
|
|
843
|
+
offsetPerType: { type: "number", description: "Pagination offset per relationship type (default 0)" },
|
|
844
|
+
detailLevel: { type: "string", enum: ["brief", "summary", "full"], description: "Detail level for returned entities (default: summary)" },
|
|
845
|
+
relationshipPropertyFilters: { type: "array", items: { type: "object", properties: { key: { type: "string" }, operator: { type: "string", enum: ["eq", "neq", "isNull", "isNotNull", "gt", "lt", "gte", "lte", "contains"] }, value: {} }, required: ["key", "operator"] }, description: "Filter relationships by property values (AND)" }
|
|
846
|
+
},
|
|
847
|
+
required: ["repositoryId", "entityId"]
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
async handleExecute(params) {
|
|
851
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
852
|
+
const resolvedId = await this.resolveEntityId(repo, params["entityId"]);
|
|
853
|
+
const rawLimitPerType = params["limitPerType"];
|
|
854
|
+
const limitPerType = Math.min(rawLimitPerType ?? 10, 50);
|
|
855
|
+
const detailLevel = params["detailLevel"];
|
|
856
|
+
const result = await repo.exploreNeighborhood(resolvedId, {
|
|
857
|
+
depth: params["depth"],
|
|
858
|
+
relationshipTypes: params["relationshipTypes"],
|
|
859
|
+
entityTypes: params["entityTypes"],
|
|
860
|
+
direction: params["direction"],
|
|
861
|
+
limitPerType,
|
|
862
|
+
offsetPerType: params["offsetPerType"] ?? 0,
|
|
863
|
+
detailLevel,
|
|
864
|
+
relationshipPropertyFilters: params["relationshipPropertyFilters"]
|
|
865
|
+
});
|
|
866
|
+
if (detailLevel === "full") {
|
|
867
|
+
result.center = stripProvenance(result.center);
|
|
868
|
+
for (const layer of result.layers) {
|
|
869
|
+
for (const key of Object.keys(layer)) {
|
|
870
|
+
const bucket = layer[key];
|
|
871
|
+
if (bucket && typeof bucket === "object" && "entities" in bucket) {
|
|
872
|
+
const b = bucket;
|
|
873
|
+
b.entities = stripProvenanceArray(b.entities);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return result;
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// src/tools/graph/FindPathsTool.ts
|
|
883
|
+
var FindPathsTool = class extends BaseToolController {
|
|
884
|
+
get name() {
|
|
885
|
+
return "memory_find_paths";
|
|
886
|
+
}
|
|
887
|
+
get description() {
|
|
888
|
+
return "Find paths between two entities in the knowledge graph. Accepts entity IDs (GUID) or slugs.";
|
|
889
|
+
}
|
|
890
|
+
get inputSchema() {
|
|
891
|
+
return {
|
|
892
|
+
type: "object",
|
|
893
|
+
properties: {
|
|
894
|
+
repositoryId: { type: "string", description: "Repository containing the entities" },
|
|
895
|
+
sourceEntityId: { type: "string", description: "Starting entity ID (GUID) or slug" },
|
|
896
|
+
targetEntityId: { type: "string", description: "Destination entity ID (GUID) or slug" },
|
|
897
|
+
maxDepth: { type: "number", description: "Maximum path depth (default: 3, max: 5)" },
|
|
898
|
+
relationshipTypes: { type: "array", items: { type: "string" }, description: "Filter allowed relationship types" },
|
|
899
|
+
entityTypes: { type: "array", items: { type: "string" }, description: "Filter entities in paths by type(s)" },
|
|
900
|
+
limit: { type: "number", description: "Maximum number of paths to return (default: 5)" },
|
|
901
|
+
offset: { type: "number", description: "Pagination offset (default: 0)" },
|
|
902
|
+
detailLevel: { type: "string", enum: ["brief", "summary", "full"], description: "Detail level for returned entities (default: brief)" },
|
|
903
|
+
relationshipPropertyFilters: { type: "array", items: { type: "object", properties: { key: { type: "string" }, operator: { type: "string", enum: ["eq", "neq", "isNull", "isNotNull", "gt", "lt", "gte", "lte", "contains"] }, value: {} }, required: ["key", "operator"] }, description: "Filter relationships by property values (AND)" }
|
|
904
|
+
},
|
|
905
|
+
required: ["repositoryId", "sourceEntityId", "targetEntityId"]
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
async handleExecute(params) {
|
|
909
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
910
|
+
const sourceId = await this.resolveEntityId(repo, params["sourceEntityId"]);
|
|
911
|
+
const targetId = await this.resolveEntityId(repo, params["targetEntityId"]);
|
|
912
|
+
const detailLevel = params["detailLevel"];
|
|
913
|
+
const result = await repo.findPaths(sourceId, targetId, {
|
|
914
|
+
maxDepth: params["maxDepth"],
|
|
915
|
+
relationshipTypes: params["relationshipTypes"],
|
|
916
|
+
entityTypes: params["entityTypes"],
|
|
917
|
+
limit: params["limit"],
|
|
918
|
+
offset: params["offset"],
|
|
919
|
+
detailLevel,
|
|
920
|
+
relationshipPropertyFilters: params["relationshipPropertyFilters"]
|
|
921
|
+
});
|
|
922
|
+
if (detailLevel === "full") {
|
|
923
|
+
for (const path of result.paths) {
|
|
924
|
+
path.entities = stripProvenanceArray(path.entities);
|
|
925
|
+
path.relationships = path.relationships.map((r) => stripProvenance(r));
|
|
926
|
+
}
|
|
927
|
+
} else {
|
|
928
|
+
for (const path of result.paths) {
|
|
929
|
+
path.relationships = path.relationships.map((r) => stripProvenance(r));
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return result;
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
// src/tools/graph/GetGraphTool.ts
|
|
937
|
+
var PAGE_SIZE = 200;
|
|
938
|
+
var GetGraphTool = class extends BaseToolController {
|
|
939
|
+
get name() {
|
|
940
|
+
return "memory_get_graph";
|
|
941
|
+
}
|
|
942
|
+
get description() {
|
|
943
|
+
return "Get the knowledge graph for a repository \u2014 entities, relationships, vocabulary, and stats. Returns up to 200 entities per page with a cursor for continuation. For large repositories, prefer memory_query_graph, memory_find_entities with filters and memory_explore_neighborhood for targeted exploration.";
|
|
944
|
+
}
|
|
945
|
+
get inputSchema() {
|
|
946
|
+
return {
|
|
947
|
+
type: "object",
|
|
948
|
+
properties: {
|
|
949
|
+
repositoryId: { type: "string", description: "Repository to get the graph for" },
|
|
950
|
+
cursor: { type: "string", description: "Opaque cursor from a previous response to fetch the next page" },
|
|
951
|
+
detailLevel: { type: "string", enum: ["brief", "summary", "full"], description: "Detail level for returned entities (default: summary)" }
|
|
952
|
+
},
|
|
953
|
+
required: ["repositoryId"]
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
async handleExecute(params) {
|
|
957
|
+
const repositoryId = params["repositoryId"];
|
|
958
|
+
const cursor = params["cursor"];
|
|
959
|
+
const repo = await this.context.getRepository(repositoryId);
|
|
960
|
+
const offset = cursor ? parseInt(cursor.split(":")[1], 10) : 0;
|
|
961
|
+
const detailLevel = params["detailLevel"];
|
|
962
|
+
const result = await repo.getGraph({ limit: PAGE_SIZE, offset, detailLevel });
|
|
963
|
+
result.relationships = result.relationships.map(
|
|
964
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
965
|
+
({ provenance, ...rest }) => rest
|
|
966
|
+
);
|
|
967
|
+
return result;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// src/tools/graph/QueryGraphTool.ts
|
|
972
|
+
var propertyFilterSchema = {
|
|
973
|
+
type: "object",
|
|
974
|
+
properties: {
|
|
975
|
+
key: { type: "string" },
|
|
976
|
+
operator: { type: "string", enum: ["eq", "neq", "isNull", "isNotNull", "gt", "lt", "gte", "lte", "contains"] },
|
|
977
|
+
value: {}
|
|
978
|
+
},
|
|
979
|
+
required: ["key", "operator"]
|
|
980
|
+
};
|
|
981
|
+
var QueryGraphTool = class extends BaseToolController {
|
|
982
|
+
get name() {
|
|
983
|
+
return "memory_query_graph";
|
|
984
|
+
}
|
|
985
|
+
get description() {
|
|
986
|
+
return `Query the knowledge graph \u2014 vertex lookups, property projection, and multi-hop traversals in a single tool.
|
|
987
|
+
|
|
988
|
+
## Projection (property extraction and aggregation)
|
|
989
|
+
|
|
990
|
+
Use \`projection\` to extract property values from entities. When projection is present, only the aggregated values are returned (no entity objects) \u2014 this keeps responses lightweight. Set \`projection.includeEntities: true\` if you also need the full entity objects.
|
|
991
|
+
|
|
992
|
+
- Distinct values: \`{ start: { entityType: "Equipment" }, projection: { properties: ["equipmentType"], distinct: true }, limit: 200 }\`
|
|
993
|
+
- Count by value: \`{ start: { entityType: "Equipment" }, projection: { properties: ["equipmentType"], mode: "count" }, limit: 200 }\`
|
|
994
|
+
- Multi-property: \`{ start: { entityType: "Equipment" }, projection: { properties: ["equipmentType", "tier"], distinct: true }, limit: 200 }\`
|
|
995
|
+
|
|
996
|
+
## Vertex queries (no steps, no projection)
|
|
997
|
+
|
|
998
|
+
Query entities directly by type and properties \u2014 returns entity objects:
|
|
999
|
+
- By type: \`{ start: { entityType: "Fluid" }, limit: 50 }\`
|
|
1000
|
+
- With filter: \`{ start: { entityType: "Fluid", filter: [{ key: "fluidType", operator: "eq", value: "hydraulic-oil" }] }, limit: 50 }\`
|
|
1001
|
+
|
|
1002
|
+
## Traversals (with steps)
|
|
1003
|
+
|
|
1004
|
+
Follow relationships through the graph:
|
|
1005
|
+
- 2-hop: \`{ start: { entityId: "Equipment:komatsu-pc7000-11" }, steps: [{ direction: "out", relationshipTypes: ["HAS_COMPONENT"] }, { direction: "out", relationshipTypes: ["REQUIRES_FLUID"] }] }\`
|
|
1006
|
+
- Filtered edges: \`{ start: { entityId: "Equipment:komatsu-pc7000-11" }, steps: [{ direction: "both", relationshipTypes: ["COMPATIBLE_WITH"], relationshipFilter: [{ key: "passCount", operator: "gte", value: 3 }] }] }\`
|
|
1007
|
+
- Variable depth: \`{ start: { entityId: "..." }, steps: [{ direction: "out", relationshipTypes: ["CONTAINS"], repeat: { maxDepth: 5 } }] }\`
|
|
1008
|
+
|
|
1009
|
+
Projection works with traversals too \u2014 add \`projection\` to aggregate properties from the traversal results.`;
|
|
1010
|
+
}
|
|
1011
|
+
get inputSchema() {
|
|
1012
|
+
return {
|
|
1013
|
+
type: "object",
|
|
1014
|
+
properties: {
|
|
1015
|
+
repositoryId: { type: "string", description: "Repository to query" },
|
|
1016
|
+
start: {
|
|
1017
|
+
type: "object",
|
|
1018
|
+
description: "Which entities to query or start traversing from",
|
|
1019
|
+
properties: {
|
|
1020
|
+
entityId: { type: "string", description: "Specific entity by ID (GUID) or slug" },
|
|
1021
|
+
entityType: { type: "string", description: "All entities of a given type (requires limit)" },
|
|
1022
|
+
filter: { type: "array", items: propertyFilterSchema, description: "Filter entities by property values" }
|
|
1023
|
+
}
|
|
1024
|
+
},
|
|
1025
|
+
steps: {
|
|
1026
|
+
type: "array",
|
|
1027
|
+
description: "Relationship hops to follow. Omit for vertex-only queries.",
|
|
1028
|
+
items: {
|
|
1029
|
+
type: "object",
|
|
1030
|
+
properties: {
|
|
1031
|
+
direction: { type: "string", enum: ["out", "in", "both"], description: "Direction to traverse" },
|
|
1032
|
+
relationshipTypes: { type: "array", items: { type: "string" }, description: "Relationship types to follow (omit for all)" },
|
|
1033
|
+
entityTypes: { type: "array", items: { type: "string" }, description: "Filter target entities by type" },
|
|
1034
|
+
relationshipFilter: { type: "array", items: propertyFilterSchema, description: "Filter relationships by property values" },
|
|
1035
|
+
entityFilter: { type: "array", items: propertyFilterSchema, description: "Filter target entities by property values" },
|
|
1036
|
+
repeat: {
|
|
1037
|
+
type: "object",
|
|
1038
|
+
description: "Repeat this step for variable-depth traversal",
|
|
1039
|
+
properties: {
|
|
1040
|
+
maxDepth: { type: "number", description: "Maximum iterations (required)" },
|
|
1041
|
+
until: { type: "array", items: propertyFilterSchema, description: "Stop when entity matches these filters" },
|
|
1042
|
+
emitIntermediates: { type: "boolean", description: "Include intermediate entities (default: true)" }
|
|
1043
|
+
},
|
|
1044
|
+
required: ["maxDepth"]
|
|
1045
|
+
}
|
|
1046
|
+
},
|
|
1047
|
+
required: ["direction"]
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
projection: {
|
|
1051
|
+
type: "object",
|
|
1052
|
+
description: "Property projection \u2014 extract and aggregate property values from result entities. When present, only aggregations are returned (no entity objects). Set includeEntities: true to also get entity objects.",
|
|
1053
|
+
properties: {
|
|
1054
|
+
properties: { type: "array", items: { type: "string" }, description: "Property names to extract from entities" },
|
|
1055
|
+
distinct: { type: "boolean", description: "Return only distinct value combinations (default: false)" },
|
|
1056
|
+
mode: { type: "string", enum: ["values", "count"], description: "values (default): raw property values. count: count entities per distinct combination" },
|
|
1057
|
+
includeEntities: { type: "boolean", description: "Also return full entity objects alongside projections (default: false)" }
|
|
1058
|
+
},
|
|
1059
|
+
required: ["properties"]
|
|
1060
|
+
},
|
|
1061
|
+
returnMode: { type: "string", enum: ["terminal", "path", "all"], description: "What to return (default: terminal)" },
|
|
1062
|
+
limit: { type: "number", description: "Maximum results (default: 50, max: 200)" },
|
|
1063
|
+
offset: { type: "number", description: "Pagination offset (default: 0)" },
|
|
1064
|
+
detailLevel: { type: "string", enum: ["brief", "summary", "full"], description: "Detail level for entities (default: summary)" },
|
|
1065
|
+
dedup: { type: "boolean", description: "Deduplicate entities (default: true)" },
|
|
1066
|
+
includeRelationshipSummary: { type: "boolean", description: "Attach outbound/inbound relationship counts by type to each entity (default: true). Set false to reduce response size." }
|
|
1067
|
+
},
|
|
1068
|
+
required: ["repositoryId", "start"]
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
async handleExecute(params) {
|
|
1072
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
1073
|
+
const start = params["start"];
|
|
1074
|
+
if (start.entityId) {
|
|
1075
|
+
start.entityId = await this.resolveEntityId(repo, start.entityId);
|
|
1076
|
+
}
|
|
1077
|
+
const spec = {
|
|
1078
|
+
start,
|
|
1079
|
+
steps: params["steps"],
|
|
1080
|
+
returnMode: params["returnMode"] ?? "terminal",
|
|
1081
|
+
projection: params["projection"],
|
|
1082
|
+
limit: params["limit"],
|
|
1083
|
+
offset: params["offset"],
|
|
1084
|
+
detailLevel: params["detailLevel"],
|
|
1085
|
+
dedup: params["dedup"],
|
|
1086
|
+
includeRelationshipSummary: params["includeRelationshipSummary"] ?? true,
|
|
1087
|
+
includeProvenance: false
|
|
1088
|
+
};
|
|
1089
|
+
return repo.traverse(spec);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
// src/tools/search/SearchByConceptTool.ts
|
|
1094
|
+
var SearchByConceptTool = class extends BaseToolController {
|
|
1095
|
+
get name() {
|
|
1096
|
+
return "memory_search_by_concept";
|
|
1097
|
+
}
|
|
1098
|
+
get description() {
|
|
1099
|
+
return "Semantic search for entities by concept similarity (requires an EmbeddingProvider)";
|
|
1100
|
+
}
|
|
1101
|
+
get inputSchema() {
|
|
1102
|
+
return {
|
|
1103
|
+
type: "object",
|
|
1104
|
+
properties: {
|
|
1105
|
+
repositoryId: { type: "string", description: "Repository to search in" },
|
|
1106
|
+
query: { type: "string", description: "Natural language search query" },
|
|
1107
|
+
similarityThreshold: { type: "number", description: "Minimum similarity score 0.0-1.0 (default: 0.7)" },
|
|
1108
|
+
entityTypes: { type: "array", items: { type: "string" }, description: "Filter by entity type(s)" },
|
|
1109
|
+
limit: { type: "number", description: "Max results (default 10)" },
|
|
1110
|
+
offset: { type: "number", description: "Pagination offset" },
|
|
1111
|
+
detailLevel: { type: "string", enum: ["brief", "summary", "full"], description: "Detail level for returned entities (default: summary)" }
|
|
1112
|
+
},
|
|
1113
|
+
required: ["repositoryId", "query"]
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
async handleExecute(params) {
|
|
1117
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
1118
|
+
return repo.searchByConcept(params["query"], {
|
|
1119
|
+
similarityThreshold: params["similarityThreshold"],
|
|
1120
|
+
entityTypes: params["entityTypes"],
|
|
1121
|
+
limit: params["limit"],
|
|
1122
|
+
offset: params["offset"],
|
|
1123
|
+
detailLevel: params["detailLevel"]
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
// src/tools/vocabulary/GetVocabularyTool.ts
|
|
1129
|
+
function stripVocabularyProvenance(obj) {
|
|
1130
|
+
const { createdAt, createdBy, modifiedAt, modifiedBy, ...rest } = obj;
|
|
1131
|
+
return rest;
|
|
1132
|
+
}
|
|
1133
|
+
var GetVocabularyTool = class extends BaseToolController {
|
|
1134
|
+
get name() {
|
|
1135
|
+
return "memory_get_vocabulary";
|
|
1136
|
+
}
|
|
1137
|
+
get description() {
|
|
1138
|
+
return "Get the vocabulary definition for a repository \u2014 shows available entity types, relationship types, and governance mode";
|
|
1139
|
+
}
|
|
1140
|
+
get inputSchema() {
|
|
1141
|
+
return {
|
|
1142
|
+
type: "object",
|
|
1143
|
+
properties: {
|
|
1144
|
+
repositoryId: { type: "string", description: "Repository to get vocabulary for" },
|
|
1145
|
+
includeProvenance: { type: "boolean", description: "Include createdAt/createdBy/modifiedAt/modifiedBy on type definitions (default: false)" }
|
|
1146
|
+
},
|
|
1147
|
+
required: ["repositoryId"]
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
async handleExecute(params) {
|
|
1151
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
1152
|
+
const result = await repo.getVocabulary();
|
|
1153
|
+
const includeProvenance = params["includeProvenance"] === true;
|
|
1154
|
+
if (!includeProvenance) {
|
|
1155
|
+
return {
|
|
1156
|
+
...result,
|
|
1157
|
+
vocabulary: {
|
|
1158
|
+
...result.vocabulary,
|
|
1159
|
+
entityTypes: result.vocabulary.entityTypes.map(stripVocabularyProvenance),
|
|
1160
|
+
relationshipTypes: result.vocabulary.relationshipTypes.map(stripVocabularyProvenance)
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
return result;
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
// src/tools/vocabulary/ProposeVocabularyExtensionTool.ts
|
|
1169
|
+
var ProposeVocabularyExtensionTool = class extends BaseToolController {
|
|
1170
|
+
get name() {
|
|
1171
|
+
return "memory_propose_vocabulary_extension";
|
|
1172
|
+
}
|
|
1173
|
+
get description() {
|
|
1174
|
+
return "Propose a new entity type or relationship type. Only needed when the vocabulary doesn't already include the type you need \u2014 check memory_open_repository response first.";
|
|
1175
|
+
}
|
|
1176
|
+
get inputSchema() {
|
|
1177
|
+
return {
|
|
1178
|
+
type: "object",
|
|
1179
|
+
properties: {
|
|
1180
|
+
repositoryId: { type: "string", description: "Repository to modify" },
|
|
1181
|
+
proposalType: {
|
|
1182
|
+
type: "string",
|
|
1183
|
+
enum: [
|
|
1184
|
+
"entity_type",
|
|
1185
|
+
"relationship_type",
|
|
1186
|
+
"edit_entity_type",
|
|
1187
|
+
"edit_relationship_type",
|
|
1188
|
+
"delete_entity_type",
|
|
1189
|
+
"delete_relationship_type"
|
|
1190
|
+
],
|
|
1191
|
+
description: "Type of vocabulary change"
|
|
1192
|
+
},
|
|
1193
|
+
entityType: {
|
|
1194
|
+
type: "object",
|
|
1195
|
+
description: "Entity type definition (when proposalType is entity_type)",
|
|
1196
|
+
properties: {
|
|
1197
|
+
type: { type: "string", description: "Type name" },
|
|
1198
|
+
description: { type: "string", description: "Type description" },
|
|
1199
|
+
properties: { type: "array", items: { type: "object" }, description: "Property schema definitions" }
|
|
1200
|
+
},
|
|
1201
|
+
required: ["type", "description"]
|
|
1202
|
+
},
|
|
1203
|
+
relationshipType: {
|
|
1204
|
+
type: "object",
|
|
1205
|
+
description: "Relationship type definition (when proposalType is relationship_type)",
|
|
1206
|
+
properties: {
|
|
1207
|
+
type: { type: "string", description: "Type name" },
|
|
1208
|
+
description: { type: "string", description: "Type description" },
|
|
1209
|
+
allowedSourceTypes: { type: "array", items: { type: "string" } },
|
|
1210
|
+
allowedTargetTypes: { type: "array", items: { type: "string" } },
|
|
1211
|
+
bidirectional: { type: "boolean" },
|
|
1212
|
+
properties: { type: "array", items: { type: "object" }, description: "Property schema definitions" }
|
|
1213
|
+
},
|
|
1214
|
+
required: ["type", "description", "allowedSourceTypes", "allowedTargetTypes"]
|
|
1215
|
+
},
|
|
1216
|
+
editEntityType: {
|
|
1217
|
+
type: "object",
|
|
1218
|
+
description: "Edit an existing entity type (when proposalType is edit_entity_type)",
|
|
1219
|
+
properties: {
|
|
1220
|
+
type: { type: "string", description: "Name of the entity type to edit" },
|
|
1221
|
+
description: { type: "string", description: "New description (optional)" },
|
|
1222
|
+
addProperties: { type: "array", items: { type: "object" }, description: "Properties to add" },
|
|
1223
|
+
removeProperties: { type: "array", items: { type: "string" }, description: "Property names to remove" },
|
|
1224
|
+
updateProperties: { type: "array", items: { type: "object" }, description: "Properties to update (matched by name)" }
|
|
1225
|
+
},
|
|
1226
|
+
required: ["type"]
|
|
1227
|
+
},
|
|
1228
|
+
editRelationshipType: {
|
|
1229
|
+
type: "object",
|
|
1230
|
+
description: "Edit an existing relationship type (when proposalType is edit_relationship_type)",
|
|
1231
|
+
properties: {
|
|
1232
|
+
type: { type: "string", description: "Name of the relationship type to edit" },
|
|
1233
|
+
description: { type: "string", description: "New description (optional)" },
|
|
1234
|
+
allowedSourceTypes: { type: "array", items: { type: "string" }, description: "New allowed source types" },
|
|
1235
|
+
allowedTargetTypes: { type: "array", items: { type: "string" }, description: "New allowed target types" },
|
|
1236
|
+
bidirectional: { type: "boolean", description: "New bidirectional flag" },
|
|
1237
|
+
addProperties: { type: "array", items: { type: "object" }, description: "Properties to add" },
|
|
1238
|
+
removeProperties: { type: "array", items: { type: "string" }, description: "Property names to remove" },
|
|
1239
|
+
updateProperties: { type: "array", items: { type: "object" }, description: "Properties to update (matched by name)" }
|
|
1240
|
+
},
|
|
1241
|
+
required: ["type"]
|
|
1242
|
+
},
|
|
1243
|
+
deleteEntityType: {
|
|
1244
|
+
type: "object",
|
|
1245
|
+
description: "Delete an entity type and all its instances (when proposalType is delete_entity_type)",
|
|
1246
|
+
properties: {
|
|
1247
|
+
type: { type: "string", description: "Name of the entity type to delete" }
|
|
1248
|
+
},
|
|
1249
|
+
required: ["type"]
|
|
1250
|
+
},
|
|
1251
|
+
deleteRelationshipType: {
|
|
1252
|
+
type: "object",
|
|
1253
|
+
description: "Delete a relationship type and all its instances (when proposalType is delete_relationship_type)",
|
|
1254
|
+
properties: {
|
|
1255
|
+
type: { type: "string", description: "Name of the relationship type to delete" }
|
|
1256
|
+
},
|
|
1257
|
+
required: ["type"]
|
|
1258
|
+
},
|
|
1259
|
+
justification: { type: "string", description: "Why this vocabulary change is needed" }
|
|
1260
|
+
},
|
|
1261
|
+
required: ["repositoryId", "proposalType"]
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
async handleExecute(params) {
|
|
1265
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
1266
|
+
const proposal = {
|
|
1267
|
+
proposalType: params["proposalType"],
|
|
1268
|
+
entityType: params["entityType"],
|
|
1269
|
+
relationshipType: params["relationshipType"],
|
|
1270
|
+
editEntityType: params["editEntityType"],
|
|
1271
|
+
editRelationshipType: params["editRelationshipType"],
|
|
1272
|
+
deleteEntityType: params["deleteEntityType"],
|
|
1273
|
+
deleteRelationshipType: params["deleteRelationshipType"],
|
|
1274
|
+
justification: params["justification"] ?? ""
|
|
1275
|
+
};
|
|
1276
|
+
return repo.proposeVocabularyChange(proposal);
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
// src/tools/stats/GetStatsTool.ts
|
|
1281
|
+
var GetStatsTool = class extends BaseToolController {
|
|
1282
|
+
get name() {
|
|
1283
|
+
return "memory_get_stats";
|
|
1284
|
+
}
|
|
1285
|
+
get description() {
|
|
1286
|
+
return "Get statistics for a repository \u2014 entity count, relationship count, type breakdowns";
|
|
1287
|
+
}
|
|
1288
|
+
get inputSchema() {
|
|
1289
|
+
return {
|
|
1290
|
+
type: "object",
|
|
1291
|
+
properties: {
|
|
1292
|
+
repositoryId: { type: "string", description: "Repository to get stats for" }
|
|
1293
|
+
},
|
|
1294
|
+
required: ["repositoryId"]
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
async handleExecute(params) {
|
|
1298
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
1299
|
+
return repo.getStats();
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
// src/tools/stats/GetTimelineTool.ts
|
|
1304
|
+
var GetTimelineTool = class extends BaseToolController {
|
|
1305
|
+
get name() {
|
|
1306
|
+
return "memory_get_timeline";
|
|
1307
|
+
}
|
|
1308
|
+
get description() {
|
|
1309
|
+
return "Get the activity timeline for an entity \u2014 creation, updates, and relationship changes. Accepts entity ID (GUID) or slug.";
|
|
1310
|
+
}
|
|
1311
|
+
get inputSchema() {
|
|
1312
|
+
return {
|
|
1313
|
+
type: "object",
|
|
1314
|
+
properties: {
|
|
1315
|
+
repositoryId: { type: "string", description: "Repository containing the entity" },
|
|
1316
|
+
entityId: { type: "string", description: "Entity ID (GUID) or slug" },
|
|
1317
|
+
from: { type: "string", description: "Start of time range (ISO 8601)" },
|
|
1318
|
+
to: { type: "string", description: "End of time range (ISO 8601)" },
|
|
1319
|
+
eventTypes: { type: "array", items: { type: "string" }, description: "Filter by event types" },
|
|
1320
|
+
limit: { type: "number", description: "Max events (default 20, max 100)" },
|
|
1321
|
+
offset: { type: "number", description: "Pagination offset" },
|
|
1322
|
+
conversationId: { type: "string", description: "Filter to events from this conversation" },
|
|
1323
|
+
actor: { type: "string", description: "Filter to events by this actor" }
|
|
1324
|
+
},
|
|
1325
|
+
required: ["repositoryId", "entityId"]
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
async handleExecute(params) {
|
|
1329
|
+
const repo = await this.context.getRepository(params["repositoryId"]);
|
|
1330
|
+
const resolvedId = await this.resolveEntityId(repo, params["entityId"]);
|
|
1331
|
+
const timeRange = params["from"] && params["to"] ? { from: params["from"], to: params["to"] } : void 0;
|
|
1332
|
+
const rawLimit = params["limit"];
|
|
1333
|
+
const limit = Math.min(rawLimit ?? 20, 100);
|
|
1334
|
+
return repo.getTimeline(resolvedId, {
|
|
1335
|
+
timeRange,
|
|
1336
|
+
eventTypes: params["eventTypes"],
|
|
1337
|
+
limit,
|
|
1338
|
+
offset: params["offset"] ?? 0,
|
|
1339
|
+
provenance: buildProvenanceFilter(params)
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
// src/tools/portability/ExportRepositoryTool.ts
|
|
1345
|
+
import { mkdirSync } from "fs";
|
|
1346
|
+
import { writeFile } from "fs/promises";
|
|
1347
|
+
import { join, resolve } from "path";
|
|
1348
|
+
|
|
1349
|
+
// src/tools/portability/zip.ts
|
|
1350
|
+
import { deflateRawSync, inflateRawSync } from "zlib";
|
|
1351
|
+
var crc32Table = new Uint32Array(256);
|
|
1352
|
+
for (let i = 0; i < 256; i++) {
|
|
1353
|
+
let c = i;
|
|
1354
|
+
for (let j = 0; j < 8; j++) {
|
|
1355
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
1356
|
+
}
|
|
1357
|
+
crc32Table[i] = c;
|
|
1358
|
+
}
|
|
1359
|
+
function computeCrc32(data) {
|
|
1360
|
+
let crc = 4294967295;
|
|
1361
|
+
for (let i = 0; i < data.length; i++) {
|
|
1362
|
+
crc = (crc32Table[(crc ^ data[i]) & 255] ^ crc >>> 8) >>> 0;
|
|
1363
|
+
}
|
|
1364
|
+
return (crc ^ 4294967295) >>> 0;
|
|
1365
|
+
}
|
|
1366
|
+
function createZip(entries) {
|
|
1367
|
+
const now = /* @__PURE__ */ new Date();
|
|
1368
|
+
const dosTime = (now.getHours() << 11 | now.getMinutes() << 5 | now.getSeconds() >> 1) & 65535;
|
|
1369
|
+
const dosDate = ((now.getFullYear() - 1980 & 127) << 9 | (now.getMonth() + 1 & 15) << 5 | now.getDate() & 31) & 65535;
|
|
1370
|
+
const localParts = [];
|
|
1371
|
+
const centralParts = [];
|
|
1372
|
+
let offset = 0;
|
|
1373
|
+
for (const entry of entries) {
|
|
1374
|
+
const nameBytes = Buffer.from(entry.name, "utf8");
|
|
1375
|
+
const compressed = deflateRawSync(entry.data);
|
|
1376
|
+
const crc = computeCrc32(entry.data);
|
|
1377
|
+
const local = Buffer.allocUnsafe(30 + nameBytes.length);
|
|
1378
|
+
local.writeUInt32LE(67324752, 0);
|
|
1379
|
+
local.writeUInt16LE(20, 4);
|
|
1380
|
+
local.writeUInt16LE(0, 6);
|
|
1381
|
+
local.writeUInt16LE(8, 8);
|
|
1382
|
+
local.writeUInt16LE(dosTime, 10);
|
|
1383
|
+
local.writeUInt16LE(dosDate, 12);
|
|
1384
|
+
local.writeUInt32LE(crc, 14);
|
|
1385
|
+
local.writeUInt32LE(compressed.length, 18);
|
|
1386
|
+
local.writeUInt32LE(entry.data.length, 22);
|
|
1387
|
+
local.writeUInt16LE(nameBytes.length, 26);
|
|
1388
|
+
local.writeUInt16LE(0, 28);
|
|
1389
|
+
nameBytes.copy(local, 30);
|
|
1390
|
+
const central = Buffer.allocUnsafe(46 + nameBytes.length);
|
|
1391
|
+
central.writeUInt32LE(33639248, 0);
|
|
1392
|
+
central.writeUInt16LE(20, 4);
|
|
1393
|
+
central.writeUInt16LE(20, 6);
|
|
1394
|
+
central.writeUInt16LE(0, 8);
|
|
1395
|
+
central.writeUInt16LE(8, 10);
|
|
1396
|
+
central.writeUInt16LE(dosTime, 12);
|
|
1397
|
+
central.writeUInt16LE(dosDate, 14);
|
|
1398
|
+
central.writeUInt32LE(crc, 16);
|
|
1399
|
+
central.writeUInt32LE(compressed.length, 20);
|
|
1400
|
+
central.writeUInt32LE(entry.data.length, 24);
|
|
1401
|
+
central.writeUInt16LE(nameBytes.length, 28);
|
|
1402
|
+
central.writeUInt16LE(0, 30);
|
|
1403
|
+
central.writeUInt16LE(0, 32);
|
|
1404
|
+
central.writeUInt16LE(0, 34);
|
|
1405
|
+
central.writeUInt16LE(0, 36);
|
|
1406
|
+
central.writeUInt32LE(0, 38);
|
|
1407
|
+
central.writeUInt32LE(offset, 42);
|
|
1408
|
+
nameBytes.copy(central, 46);
|
|
1409
|
+
localParts.push(local, compressed);
|
|
1410
|
+
centralParts.push(central);
|
|
1411
|
+
offset += local.length + compressed.length;
|
|
1412
|
+
}
|
|
1413
|
+
const centralDirOffset = offset;
|
|
1414
|
+
const centralDirBuf = Buffer.concat(centralParts);
|
|
1415
|
+
const eocd = Buffer.allocUnsafe(22);
|
|
1416
|
+
eocd.writeUInt32LE(101010256, 0);
|
|
1417
|
+
eocd.writeUInt16LE(0, 4);
|
|
1418
|
+
eocd.writeUInt16LE(0, 6);
|
|
1419
|
+
eocd.writeUInt16LE(entries.length, 8);
|
|
1420
|
+
eocd.writeUInt16LE(entries.length, 10);
|
|
1421
|
+
eocd.writeUInt32LE(centralDirBuf.length, 12);
|
|
1422
|
+
eocd.writeUInt32LE(centralDirOffset, 16);
|
|
1423
|
+
eocd.writeUInt16LE(0, 20);
|
|
1424
|
+
return Buffer.concat([...localParts, centralDirBuf, eocd]);
|
|
1425
|
+
}
|
|
1426
|
+
function readZip(zipBuffer) {
|
|
1427
|
+
let eocdOffset = -1;
|
|
1428
|
+
for (let i = zipBuffer.length - 22; i >= 0; i--) {
|
|
1429
|
+
if (zipBuffer.readUInt32LE(i) === 101010256) {
|
|
1430
|
+
eocdOffset = i;
|
|
1431
|
+
break;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if (eocdOffset === -1) {
|
|
1435
|
+
throw new Error("Invalid ZIP file: end of central directory record not found");
|
|
1436
|
+
}
|
|
1437
|
+
const entryCount = zipBuffer.readUInt16LE(eocdOffset + 10);
|
|
1438
|
+
const centralDirOffset = zipBuffer.readUInt32LE(eocdOffset + 16);
|
|
1439
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1440
|
+
let pos = centralDirOffset;
|
|
1441
|
+
for (let i = 0; i < entryCount; i++) {
|
|
1442
|
+
if (zipBuffer.readUInt32LE(pos) !== 33639248) {
|
|
1443
|
+
throw new Error("Invalid ZIP file: central directory header not found");
|
|
1444
|
+
}
|
|
1445
|
+
const compressionMethod = zipBuffer.readUInt16LE(pos + 10);
|
|
1446
|
+
const compressedSize = zipBuffer.readUInt32LE(pos + 20);
|
|
1447
|
+
const nameLength = zipBuffer.readUInt16LE(pos + 28);
|
|
1448
|
+
const extraLength = zipBuffer.readUInt16LE(pos + 30);
|
|
1449
|
+
const commentLength = zipBuffer.readUInt16LE(pos + 32);
|
|
1450
|
+
const localHeaderOffset = zipBuffer.readUInt32LE(pos + 42);
|
|
1451
|
+
const name = zipBuffer.subarray(pos + 46, pos + 46 + nameLength).toString("utf8");
|
|
1452
|
+
pos += 46 + nameLength + extraLength + commentLength;
|
|
1453
|
+
const localNameLength = zipBuffer.readUInt16LE(localHeaderOffset + 26);
|
|
1454
|
+
const localExtraLength = zipBuffer.readUInt16LE(localHeaderOffset + 28);
|
|
1455
|
+
const dataOffset = localHeaderOffset + 30 + localNameLength + localExtraLength;
|
|
1456
|
+
const compressedData = zipBuffer.subarray(dataOffset, dataOffset + compressedSize);
|
|
1457
|
+
if (compressionMethod === 0) {
|
|
1458
|
+
entries.set(name, Buffer.from(compressedData));
|
|
1459
|
+
} else if (compressionMethod === 8) {
|
|
1460
|
+
entries.set(name, Buffer.from(inflateRawSync(compressedData)));
|
|
1461
|
+
} else {
|
|
1462
|
+
throw new Error(`Unsupported ZIP compression method: ${compressionMethod}`);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
return entries;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// src/tools/portability/ExportRepositoryTool.ts
|
|
1469
|
+
var CHUNK_SIZE = 500;
|
|
1470
|
+
function slugify(text) {
|
|
1471
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "repository";
|
|
1472
|
+
}
|
|
1473
|
+
function formatDatetime(date) {
|
|
1474
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
1475
|
+
return `${date.getFullYear()}${p(date.getMonth() + 1)}${p(date.getDate())}-${p(date.getHours())}${p(date.getMinutes())}${p(date.getSeconds())}`;
|
|
1476
|
+
}
|
|
1477
|
+
function toBuffer(obj) {
|
|
1478
|
+
return Buffer.from(JSON.stringify(obj, null, 2), "utf8");
|
|
1479
|
+
}
|
|
1480
|
+
var ExportRepositoryTool = class extends BaseToolController {
|
|
1481
|
+
constructor(context, logger2, exportDir) {
|
|
1482
|
+
super(context, logger2);
|
|
1483
|
+
this.exportDir = exportDir;
|
|
1484
|
+
}
|
|
1485
|
+
exportDir;
|
|
1486
|
+
get name() {
|
|
1487
|
+
return "memory_export_repository";
|
|
1488
|
+
}
|
|
1489
|
+
get description() {
|
|
1490
|
+
return "Export a memory repository to a .dkg (Deep Knowledge Graph) file on the local filesystem. The archive contains manifest.json, vocabulary.json, and chunked entities/relationships files. Returns the path \u2014 no data is sent to the AI.";
|
|
1491
|
+
}
|
|
1492
|
+
get inputSchema() {
|
|
1493
|
+
return {
|
|
1494
|
+
type: "object",
|
|
1495
|
+
properties: {
|
|
1496
|
+
repositoryId: {
|
|
1497
|
+
type: "string",
|
|
1498
|
+
description: "UUID of the repository to export"
|
|
1499
|
+
},
|
|
1500
|
+
legal: {
|
|
1501
|
+
type: "object",
|
|
1502
|
+
description: "Optional legal/copyright metadata to embed in the archive manifest",
|
|
1503
|
+
properties: {
|
|
1504
|
+
copyright: {
|
|
1505
|
+
type: "string",
|
|
1506
|
+
description: 'Copyright holder (e.g. "\xA9 2026 Caterpillar Inc.")'
|
|
1507
|
+
},
|
|
1508
|
+
license: {
|
|
1509
|
+
type: "string",
|
|
1510
|
+
description: 'SPDX license identifier or custom license name (e.g. "Apache-2.0", "LicenseRef-Proprietary")'
|
|
1511
|
+
},
|
|
1512
|
+
licenseUrl: {
|
|
1513
|
+
type: "string",
|
|
1514
|
+
description: "Full license text or URL pointing to license terms"
|
|
1515
|
+
},
|
|
1516
|
+
terms: {
|
|
1517
|
+
type: "string",
|
|
1518
|
+
description: "Human-readable usage terms or restrictions summary"
|
|
1519
|
+
},
|
|
1520
|
+
publisher: {
|
|
1521
|
+
type: "string",
|
|
1522
|
+
description: "Organization that published this archive"
|
|
1523
|
+
},
|
|
1524
|
+
contact: {
|
|
1525
|
+
type: "string",
|
|
1526
|
+
description: "Contact for licensing questions (e.g. email address)"
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
required: ["copyright"]
|
|
1530
|
+
}
|
|
1531
|
+
},
|
|
1532
|
+
required: ["repositoryId"]
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
async handleExecute(params) {
|
|
1536
|
+
const repositoryId = params["repositoryId"];
|
|
1537
|
+
const legal = params["legal"];
|
|
1538
|
+
this.logger.info(this.name, `Exporting repository ${repositoryId}`);
|
|
1539
|
+
const startTime = Date.now();
|
|
1540
|
+
const exportOptions = legal ? { legal } : void 0;
|
|
1541
|
+
const entries = [];
|
|
1542
|
+
let entityCount = 0;
|
|
1543
|
+
let relationshipCount = 0;
|
|
1544
|
+
let entityChunkNum = 0;
|
|
1545
|
+
let relationshipChunkNum = 0;
|
|
1546
|
+
let label = "";
|
|
1547
|
+
let vocabVersion = "";
|
|
1548
|
+
let exportedAt = "";
|
|
1549
|
+
for await (const item of this.context.deepMemory.exportRepositoryStream(repositoryId, exportOptions)) {
|
|
1550
|
+
switch (item.type) {
|
|
1551
|
+
case "manifest":
|
|
1552
|
+
entries.push({ name: "manifest.json", data: toBuffer(item.data) });
|
|
1553
|
+
label = item.data.repository.label;
|
|
1554
|
+
vocabVersion = item.data.repository.vocabularyVersion;
|
|
1555
|
+
exportedAt = item.data.exportedAt;
|
|
1556
|
+
break;
|
|
1557
|
+
case "vocabulary":
|
|
1558
|
+
entries.push({ name: "vocabulary.json", data: toBuffer(item.data) });
|
|
1559
|
+
break;
|
|
1560
|
+
case "entities": {
|
|
1561
|
+
const allEntities = item.data;
|
|
1562
|
+
for (let i = 0; i < allEntities.length; i += CHUNK_SIZE) {
|
|
1563
|
+
const chunk = allEntities.slice(i, i + CHUNK_SIZE);
|
|
1564
|
+
entityChunkNum++;
|
|
1565
|
+
entries.push({
|
|
1566
|
+
name: `entities-${String(entityChunkNum).padStart(4, "0")}.json`,
|
|
1567
|
+
data: toBuffer(chunk)
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
entityCount += allEntities.length;
|
|
1571
|
+
break;
|
|
1572
|
+
}
|
|
1573
|
+
case "relationships": {
|
|
1574
|
+
const allRels = item.data;
|
|
1575
|
+
for (let i = 0; i < allRels.length; i += CHUNK_SIZE) {
|
|
1576
|
+
const chunk = allRels.slice(i, i + CHUNK_SIZE);
|
|
1577
|
+
relationshipChunkNum++;
|
|
1578
|
+
entries.push({
|
|
1579
|
+
name: `relationships-${String(relationshipChunkNum).padStart(4, "0")}.json`,
|
|
1580
|
+
data: toBuffer(chunk)
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
relationshipCount += allRels.length;
|
|
1584
|
+
break;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
const slug = slugify(label);
|
|
1589
|
+
const datetime = formatDatetime(new Date(exportedAt));
|
|
1590
|
+
const zipName = `${slug}-v${vocabVersion}-${datetime}.dkg`;
|
|
1591
|
+
const exportDir = resolve(this.exportDir);
|
|
1592
|
+
mkdirSync(exportDir, { recursive: true });
|
|
1593
|
+
const filePath = join(exportDir, zipName);
|
|
1594
|
+
const zipBuffer = createZip(entries);
|
|
1595
|
+
await writeFile(filePath, zipBuffer);
|
|
1596
|
+
const elapsedMs = Date.now() - startTime;
|
|
1597
|
+
const elapsedSec = (elapsedMs / 1e3).toFixed(1);
|
|
1598
|
+
this.logger.info(this.name, `Exported to ${filePath} (${entries.length} files) in ${elapsedSec}s`);
|
|
1599
|
+
return {
|
|
1600
|
+
path: filePath,
|
|
1601
|
+
filename: zipName,
|
|
1602
|
+
repository: label,
|
|
1603
|
+
vocabularyVersion: vocabVersion,
|
|
1604
|
+
statistics: {
|
|
1605
|
+
entities: entityCount,
|
|
1606
|
+
relationships: relationshipCount,
|
|
1607
|
+
files: entries.length
|
|
1608
|
+
},
|
|
1609
|
+
timing: {
|
|
1610
|
+
elapsedMs,
|
|
1611
|
+
elapsedFormatted: `${elapsedSec}s`
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
// src/tools/portability/ImportRepositoryTool.ts
|
|
1618
|
+
import { readFile } from "fs/promises";
|
|
1619
|
+
var ImportRepositoryTool = class extends BaseToolController {
|
|
1620
|
+
get name() {
|
|
1621
|
+
return "memory_import_repository";
|
|
1622
|
+
}
|
|
1623
|
+
get description() {
|
|
1624
|
+
return 'Import a .dkg (Deep Knowledge Graph) archive. Also accepts legacy .zip archives. Mode "create" creates a new repository and uses fast bulk inserts (no existence checks). Mode "merge" (default) imports into an existing repository \u2014 entities with matching IDs are overwritten; new vocabulary types are added automatically. Supports both multi-file (manifest.json + chunks) and legacy single-file archives.';
|
|
1625
|
+
}
|
|
1626
|
+
get inputSchema() {
|
|
1627
|
+
return {
|
|
1628
|
+
type: "object",
|
|
1629
|
+
properties: {
|
|
1630
|
+
repositoryId: {
|
|
1631
|
+
type: "string",
|
|
1632
|
+
description: "UUID of the target repository to import into (merge) or for the new repository (create)"
|
|
1633
|
+
},
|
|
1634
|
+
path: {
|
|
1635
|
+
type: "string",
|
|
1636
|
+
description: "Absolute path to the .dkg or .zip archive to import"
|
|
1637
|
+
},
|
|
1638
|
+
mode: {
|
|
1639
|
+
type: "string",
|
|
1640
|
+
enum: ["create", "merge"],
|
|
1641
|
+
description: 'Import mode: "create" for a new repository (fast, no existence checks), "merge" to import into an existing repository (default: merge)'
|
|
1642
|
+
}
|
|
1643
|
+
},
|
|
1644
|
+
required: ["repositoryId", "path"]
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
async handleExecute(params) {
|
|
1648
|
+
const repositoryId = params["repositoryId"];
|
|
1649
|
+
const filePath = params["path"];
|
|
1650
|
+
const mode = params["mode"] ?? "merge";
|
|
1651
|
+
this.logger.info(this.name, `Importing ${filePath} into repository ${repositoryId} (mode: ${mode})`);
|
|
1652
|
+
const startTime = Date.now();
|
|
1653
|
+
const zipBuffer = await readFile(filePath);
|
|
1654
|
+
const files = readZip(zipBuffer);
|
|
1655
|
+
const manifestBuf = files.get("manifest.json");
|
|
1656
|
+
let manifest;
|
|
1657
|
+
let vocabulary;
|
|
1658
|
+
const entities = [];
|
|
1659
|
+
const relationships = [];
|
|
1660
|
+
if (manifestBuf) {
|
|
1661
|
+
manifest = JSON.parse(manifestBuf.toString("utf8"));
|
|
1662
|
+
const vocabBuf = files.get("vocabulary.json");
|
|
1663
|
+
if (!vocabBuf) {
|
|
1664
|
+
throw new Error("Invalid archive: manifest.json present but vocabulary.json missing");
|
|
1665
|
+
}
|
|
1666
|
+
vocabulary = JSON.parse(vocabBuf.toString("utf8"));
|
|
1667
|
+
const sortedNames = [...files.keys()].sort();
|
|
1668
|
+
for (const name of sortedNames) {
|
|
1669
|
+
if (name.startsWith("entities-") && name.endsWith(".json")) {
|
|
1670
|
+
const chunk = JSON.parse(files.get(name).toString("utf8"));
|
|
1671
|
+
entities.push(...chunk);
|
|
1672
|
+
} else if (name.startsWith("relationships-") && name.endsWith(".json")) {
|
|
1673
|
+
const chunk = JSON.parse(files.get(name).toString("utf8"));
|
|
1674
|
+
relationships.push(...chunk);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
} else {
|
|
1678
|
+
const firstEntry = files.values().next().value;
|
|
1679
|
+
if (!firstEntry) {
|
|
1680
|
+
throw new Error("Invalid archive: zip file is empty");
|
|
1681
|
+
}
|
|
1682
|
+
const legacy = JSON.parse(firstEntry.toString("utf8"));
|
|
1683
|
+
manifest = legacy.manifest;
|
|
1684
|
+
vocabulary = legacy.vocabulary;
|
|
1685
|
+
entities.push(...legacy.entities);
|
|
1686
|
+
relationships.push(...legacy.relationships);
|
|
1687
|
+
}
|
|
1688
|
+
const onAdjust = (event) => {
|
|
1689
|
+
this.logger.info(
|
|
1690
|
+
this.name,
|
|
1691
|
+
`Adaptive concurrency ${event.reason}: ${event.previousConcurrency} -> ${event.concurrency} (tasks=${event.tasksCompleted}, throttled=${event.throttledCount})`
|
|
1692
|
+
);
|
|
1693
|
+
};
|
|
1694
|
+
const importOptions = mode === "create" ? {
|
|
1695
|
+
target: {
|
|
1696
|
+
mode: "create",
|
|
1697
|
+
repositoryId,
|
|
1698
|
+
config: {
|
|
1699
|
+
label: manifest.repository.label,
|
|
1700
|
+
type: manifest.repository.type,
|
|
1701
|
+
description: manifest.repository.description,
|
|
1702
|
+
governance: { mode: manifest.repository.governanceMode }
|
|
1703
|
+
}
|
|
1704
|
+
},
|
|
1705
|
+
bulk: {
|
|
1706
|
+
adaptiveConcurrency: { onAdjust }
|
|
1707
|
+
}
|
|
1708
|
+
} : {
|
|
1709
|
+
target: { mode: "merge", repositoryId },
|
|
1710
|
+
vocabularyConflict: "extend",
|
|
1711
|
+
entityConflict: "overwrite",
|
|
1712
|
+
bulk: {
|
|
1713
|
+
adaptiveConcurrency: { onAdjust }
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
const result = await this.context.deepMemory.importRepository(
|
|
1717
|
+
{ manifest, vocabulary, entities, relationships },
|
|
1718
|
+
importOptions
|
|
1719
|
+
);
|
|
1720
|
+
const elapsedMs = Date.now() - startTime;
|
|
1721
|
+
const elapsedSec = (elapsedMs / 1e3).toFixed(1);
|
|
1722
|
+
this.logger.info(
|
|
1723
|
+
this.name,
|
|
1724
|
+
`Import ${result.success ? "succeeded" : "failed"}: ${result.statistics.entitiesImported} entities, ${result.statistics.relationshipsImported} relationships in ${elapsedSec}s`
|
|
1725
|
+
);
|
|
1726
|
+
return {
|
|
1727
|
+
success: result.success,
|
|
1728
|
+
repositoryId: result.repositoryId,
|
|
1729
|
+
statistics: result.statistics,
|
|
1730
|
+
warnings: result.warnings,
|
|
1731
|
+
timing: {
|
|
1732
|
+
elapsedMs,
|
|
1733
|
+
elapsedFormatted: `${elapsedSec}s`
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
// src/server/ToolRegistry.ts
|
|
1740
|
+
var ToolRegistry = class {
|
|
1741
|
+
constructor(context, logger2) {
|
|
1742
|
+
this.context = context;
|
|
1743
|
+
this.logger = logger2;
|
|
1744
|
+
this.registerTools();
|
|
1745
|
+
}
|
|
1746
|
+
context;
|
|
1747
|
+
logger;
|
|
1748
|
+
tools = /* @__PURE__ */ new Map();
|
|
1749
|
+
registerTools() {
|
|
1750
|
+
this.register(new CreateRepositoryTool(this.context, this.logger));
|
|
1751
|
+
this.register(new OpenRepositoryTool(this.context, this.logger));
|
|
1752
|
+
this.register(new ListRepositoriesTool(this.context, this.logger));
|
|
1753
|
+
this.register(new UpdateRepositoryTool(this.context, this.logger));
|
|
1754
|
+
this.register(new DeleteRepositoryTool(this.context, this.logger));
|
|
1755
|
+
this.register(new EnsureSchemaTool(this.context, this.logger));
|
|
1756
|
+
this.register(new ValidateEntitiesTool(this.context, this.logger));
|
|
1757
|
+
this.register(new ValidateRelationshipsTool(this.context, this.logger));
|
|
1758
|
+
this.register(new CreateEntitiesTool(this.context, this.logger));
|
|
1759
|
+
this.register(new UpdateEntityTool(this.context, this.logger));
|
|
1760
|
+
this.register(new GetEntityTool(this.context, this.logger));
|
|
1761
|
+
this.register(new FindEntitiesTool(this.context, this.logger));
|
|
1762
|
+
this.register(new DeleteEntitiesTool(this.context, this.logger));
|
|
1763
|
+
this.register(new ReembedRepositoryTool(this.context, this.logger));
|
|
1764
|
+
this.register(new CreateRelationshipsTool(this.context, this.logger));
|
|
1765
|
+
this.register(new RemoveRelationshipsTool(this.context, this.logger));
|
|
1766
|
+
this.register(new GetRelationshipsTool(this.context, this.logger));
|
|
1767
|
+
this.register(new ExploreNeighborhoodTool(this.context, this.logger));
|
|
1768
|
+
this.register(new FindPathsTool(this.context, this.logger));
|
|
1769
|
+
this.register(new GetGraphTool(this.context, this.logger));
|
|
1770
|
+
this.register(new QueryGraphTool(this.context, this.logger));
|
|
1771
|
+
this.register(new SearchByConceptTool(this.context, this.logger));
|
|
1772
|
+
this.register(new GetVocabularyTool(this.context, this.logger));
|
|
1773
|
+
this.register(new ProposeVocabularyExtensionTool(this.context, this.logger));
|
|
1774
|
+
this.register(new GetStatsTool(this.context, this.logger));
|
|
1775
|
+
this.register(new GetTimelineTool(this.context, this.logger));
|
|
1776
|
+
this.register(new ExportRepositoryTool(this.context, this.logger, this.context.exportDir));
|
|
1777
|
+
this.register(new ImportRepositoryTool(this.context, this.logger));
|
|
1778
|
+
this.logger.info("ToolRegistry", `Registered ${this.tools.size} tools`);
|
|
1779
|
+
}
|
|
1780
|
+
register(tool) {
|
|
1781
|
+
if (this.tools.has(tool.name)) {
|
|
1782
|
+
this.logger.warn("ToolRegistry", `Tool ${tool.name} already registered, overwriting`);
|
|
1783
|
+
}
|
|
1784
|
+
this.tools.set(tool.name, tool);
|
|
1785
|
+
}
|
|
1786
|
+
listTools() {
|
|
1787
|
+
return Array.from(this.tools.values()).map((tool) => ({
|
|
1788
|
+
name: tool.name,
|
|
1789
|
+
description: tool.description,
|
|
1790
|
+
inputSchema: tool.inputSchema
|
|
1791
|
+
}));
|
|
1792
|
+
}
|
|
1793
|
+
async executeTool(name, params) {
|
|
1794
|
+
const tool = this.tools.get(name);
|
|
1795
|
+
if (!tool) {
|
|
1796
|
+
throw new Error(`Tool '${name}' not found`);
|
|
1797
|
+
}
|
|
1798
|
+
this.logger.debug("ToolRegistry", `Executing tool: ${name}`);
|
|
1799
|
+
try {
|
|
1800
|
+
const result = await tool.execute(params);
|
|
1801
|
+
this.logger.debug("ToolRegistry", `Tool ${name} completed`);
|
|
1802
|
+
return result;
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
this.logger.error("ToolRegistry", `Tool ${name} failed`, error);
|
|
1805
|
+
throw error;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
getToolCount() {
|
|
1809
|
+
return this.tools.size;
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
|
|
1813
|
+
// src/server/McpHandler.ts
|
|
1814
|
+
var McpHandler = class {
|
|
1815
|
+
constructor(toolRegistry, logger2) {
|
|
1816
|
+
this.toolRegistry = toolRegistry;
|
|
1817
|
+
this.logger = logger2;
|
|
1818
|
+
}
|
|
1819
|
+
toolRegistry;
|
|
1820
|
+
logger;
|
|
1821
|
+
async handleToolsList() {
|
|
1822
|
+
this.logger.debug("McpHandler", "Received tools/list request");
|
|
1823
|
+
const tools = this.toolRegistry.listTools();
|
|
1824
|
+
this.logger.info("McpHandler", `Returning ${tools.length} tools`);
|
|
1825
|
+
return {
|
|
1826
|
+
tools: tools.map((tool) => ({
|
|
1827
|
+
name: tool.name,
|
|
1828
|
+
description: tool.description,
|
|
1829
|
+
inputSchema: tool.inputSchema
|
|
1830
|
+
}))
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
async handleToolCall(request) {
|
|
1834
|
+
const { name, arguments: args } = request.params;
|
|
1835
|
+
this.logger.debug("McpHandler", `Received tool call: ${name}`);
|
|
1836
|
+
try {
|
|
1837
|
+
if (!name) {
|
|
1838
|
+
throw new Error("Tool name is required");
|
|
1839
|
+
}
|
|
1840
|
+
const result = await this.toolRegistry.executeTool(name, args ?? {});
|
|
1841
|
+
this.logger.info("McpHandler", `Tool ${name} executed successfully`);
|
|
1842
|
+
if (result && typeof result === "object" && "content" in result && Array.isArray(result["content"])) {
|
|
1843
|
+
return result;
|
|
1844
|
+
}
|
|
1845
|
+
if (typeof result === "string") {
|
|
1846
|
+
return { content: [{ type: "text", text: result }] };
|
|
1847
|
+
}
|
|
1848
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1849
|
+
} catch (error) {
|
|
1850
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1851
|
+
this.logger.error("McpHandler", `Tool ${name} failed: ${message}`);
|
|
1852
|
+
return {
|
|
1853
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1854
|
+
isError: true
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
// src/server/McpServer.ts
|
|
1861
|
+
var McpServer = class {
|
|
1862
|
+
constructor(config, logger2) {
|
|
1863
|
+
this.logger = logger2;
|
|
1864
|
+
let storage;
|
|
1865
|
+
let graphTraversal;
|
|
1866
|
+
if (config.storageType === "sqlserver") {
|
|
1867
|
+
storage = new SqlServerStorageProvider({
|
|
1868
|
+
connection: {
|
|
1869
|
+
server: config.sqlServerHost,
|
|
1870
|
+
port: config.sqlServerPort ?? 1433,
|
|
1871
|
+
database: config.sqlServerDatabase,
|
|
1872
|
+
user: config.sqlServerUser,
|
|
1873
|
+
password: config.sqlServerPassword,
|
|
1874
|
+
options: {
|
|
1875
|
+
encrypt: false,
|
|
1876
|
+
trustServerCertificate: config.sqlServerTrustCert ?? false
|
|
1877
|
+
}
|
|
1878
|
+
},
|
|
1879
|
+
schema: config.sqlServerSchema ?? "dbo"
|
|
1880
|
+
});
|
|
1881
|
+
} else if (config.storageType === "cosmosdb") {
|
|
1882
|
+
const cosmos = new CosmosDbProvider({
|
|
1883
|
+
endpoint: config.cosmosDbEndpoint,
|
|
1884
|
+
restEndpoint: config.cosmosDbRestEndpoint,
|
|
1885
|
+
key: config.cosmosDbKey,
|
|
1886
|
+
database: config.cosmosDbDatabase,
|
|
1887
|
+
container: config.cosmosDbContainer,
|
|
1888
|
+
rejectUnauthorized: config.cosmosDbRejectUnauthorized ?? true
|
|
1889
|
+
});
|
|
1890
|
+
storage = cosmos;
|
|
1891
|
+
graphTraversal = cosmos;
|
|
1892
|
+
} else {
|
|
1893
|
+
storage = new InMemoryStorageProvider();
|
|
1894
|
+
}
|
|
1895
|
+
const search = config.storageType === "memory" || !config.storageType ? new InMemorySearchProvider() : void 0;
|
|
1896
|
+
const embeddingFactory = config.embeddingsBaseUrl ? ({ model, dimensions }) => new OpenAIEmbeddingProvider({
|
|
1897
|
+
baseUrl: config.embeddingsBaseUrl,
|
|
1898
|
+
model,
|
|
1899
|
+
dimensions,
|
|
1900
|
+
apiKey: config.embeddingsApiKey
|
|
1901
|
+
}) : void 0;
|
|
1902
|
+
this.deepMemory = new DeepMemory({
|
|
1903
|
+
storage,
|
|
1904
|
+
search,
|
|
1905
|
+
embeddingFactory,
|
|
1906
|
+
defaultEmbeddingModel: config.embeddingsModel,
|
|
1907
|
+
defaultEmbeddingDimensions: config.embeddingsDimensions,
|
|
1908
|
+
graphTraversal,
|
|
1909
|
+
provenance: {
|
|
1910
|
+
actorId: config.actorId ?? "mcp-agent",
|
|
1911
|
+
actorType: config.actorType ?? "agent"
|
|
1912
|
+
}
|
|
1913
|
+
});
|
|
1914
|
+
const toolContext = {
|
|
1915
|
+
deepMemory: this.deepMemory,
|
|
1916
|
+
storage,
|
|
1917
|
+
getRepository: this.getRepository.bind(this),
|
|
1918
|
+
evictRepository: this.evictRepository.bind(this),
|
|
1919
|
+
exportDir: config.exportDir ?? "./exports"
|
|
1920
|
+
};
|
|
1921
|
+
const toolRegistry = new ToolRegistry(toolContext, this.logger);
|
|
1922
|
+
this.handler = new McpHandler(toolRegistry, this.logger);
|
|
1923
|
+
this.server = new Server(
|
|
1924
|
+
{
|
|
1925
|
+
name: "@utaba/deep-memory-local-mcp-server",
|
|
1926
|
+
version: "0.1.0"
|
|
1927
|
+
},
|
|
1928
|
+
{
|
|
1929
|
+
capabilities: {
|
|
1930
|
+
tools: {}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
);
|
|
1934
|
+
this.setupHandlers();
|
|
1935
|
+
}
|
|
1936
|
+
logger;
|
|
1937
|
+
server;
|
|
1938
|
+
handler;
|
|
1939
|
+
deepMemory;
|
|
1940
|
+
repositories = /* @__PURE__ */ new Map();
|
|
1941
|
+
setupHandlers() {
|
|
1942
|
+
this.server.setRequestHandler(ListToolsRequestSchema, this.handler.handleToolsList.bind(this.handler));
|
|
1943
|
+
this.server.setRequestHandler(CallToolRequestSchema, this.handler.handleToolCall.bind(this.handler));
|
|
1944
|
+
}
|
|
1945
|
+
async getRepository(repositoryId) {
|
|
1946
|
+
let repo = this.repositories.get(repositoryId);
|
|
1947
|
+
if (!repo) {
|
|
1948
|
+
repo = await this.deepMemory.openRepository(repositoryId);
|
|
1949
|
+
this.repositories.set(repositoryId, repo);
|
|
1950
|
+
}
|
|
1951
|
+
return repo;
|
|
1952
|
+
}
|
|
1953
|
+
evictRepository(repositoryId) {
|
|
1954
|
+
this.repositories.delete(repositoryId);
|
|
1955
|
+
}
|
|
1956
|
+
async start() {
|
|
1957
|
+
const transport = new StdioServerTransport();
|
|
1958
|
+
await this.server.connect(transport);
|
|
1959
|
+
this.logger.info("McpServer", "Deep Memory MCP server started on stdio");
|
|
1960
|
+
this.deepMemory.ensureSchema().catch((error) => {
|
|
1961
|
+
this.logger.warn("McpServer", `Storage unavailable on startup \u2014 repository tools will fail until storage is reachable: ${error instanceof Error ? error.message : String(error)}`);
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
async stop() {
|
|
1965
|
+
this.repositories.clear();
|
|
1966
|
+
await this.deepMemory.dispose();
|
|
1967
|
+
await this.server.close();
|
|
1968
|
+
this.logger.info("McpServer", "Deep Memory MCP server stopped");
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
|
|
1972
|
+
// src/interfaces/ILogger.ts
|
|
1973
|
+
var ConsoleLogger = class {
|
|
1974
|
+
debug(context, message) {
|
|
1975
|
+
console.error(`[DEBUG] [${context}] ${message}`);
|
|
1976
|
+
}
|
|
1977
|
+
info(context, message) {
|
|
1978
|
+
console.error(`[INFO] [${context}] ${message}`);
|
|
1979
|
+
}
|
|
1980
|
+
warn(context, message) {
|
|
1981
|
+
console.error(`[WARN] [${context}] ${message}`);
|
|
1982
|
+
}
|
|
1983
|
+
error(context, message, detail) {
|
|
1984
|
+
console.error(`[ERROR] [${context}] ${message}`, detail ?? "");
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
|
|
1988
|
+
// src/index.ts
|
|
1989
|
+
function loadEnvFile(path) {
|
|
1990
|
+
if (!existsSync(path)) return;
|
|
1991
|
+
const content = readFileSync(path, "utf8");
|
|
1992
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
1993
|
+
let line = raw.trim();
|
|
1994
|
+
if (!line || line.startsWith("#")) continue;
|
|
1995
|
+
if (line.startsWith("export ")) line = line.slice("export ".length).trim();
|
|
1996
|
+
const eq = line.indexOf("=");
|
|
1997
|
+
if (eq === -1) continue;
|
|
1998
|
+
const key = line.slice(0, eq).trim();
|
|
1999
|
+
let value = line.slice(eq + 1).trim();
|
|
2000
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2001
|
+
value = value.slice(1, -1);
|
|
2002
|
+
}
|
|
2003
|
+
if (!(key in process.env)) {
|
|
2004
|
+
process.env[key] = value;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
loadEnvFile(resolve2(process.cwd(), ".env.local"));
|
|
2009
|
+
var logger = new ConsoleLogger();
|
|
2010
|
+
var storageSetting = process.env["DEEP_MEMORY_STORAGE"];
|
|
2011
|
+
var storageType = storageSetting === "sqlserver" ? "sqlserver" : storageSetting === "cosmosdb" ? "cosmosdb" : "memory";
|
|
2012
|
+
var server = new McpServer(
|
|
2013
|
+
{
|
|
2014
|
+
actorId: process.env["DEEP_MEMORY_ACTOR_ID"] ?? "mcp-agent",
|
|
2015
|
+
actorType: process.env["DEEP_MEMORY_ACTOR_TYPE"] ?? "agent",
|
|
2016
|
+
embeddingsBaseUrl: process.env["DEEP_MEMORY_EMBEDDINGS_BASE_URL"],
|
|
2017
|
+
embeddingsModel: process.env["DEEP_MEMORY_EMBEDDINGS_MODEL"],
|
|
2018
|
+
embeddingsDimensions: process.env["DEEP_MEMORY_EMBEDDINGS_DIMENSIONS"] ? Number(process.env["DEEP_MEMORY_EMBEDDINGS_DIMENSIONS"]) : void 0,
|
|
2019
|
+
embeddingsApiKey: process.env["DEEP_MEMORY_EMBEDDINGS_API_KEY"],
|
|
2020
|
+
storageType,
|
|
2021
|
+
sqlServerHost: process.env["DEEP_MEMORY_SQL_HOST"],
|
|
2022
|
+
sqlServerPort: process.env["DEEP_MEMORY_SQL_PORT"] ? Number(process.env["DEEP_MEMORY_SQL_PORT"]) : void 0,
|
|
2023
|
+
sqlServerDatabase: process.env["DEEP_MEMORY_SQL_DATABASE"],
|
|
2024
|
+
sqlServerUser: process.env["DEEP_MEMORY_SQL_USER"],
|
|
2025
|
+
sqlServerPassword: process.env["DEEP_MEMORY_SQL_PASSWORD"],
|
|
2026
|
+
sqlServerSchema: process.env["DEEP_MEMORY_SQL_SCHEMA"],
|
|
2027
|
+
sqlServerTrustCert: process.env["DEEP_MEMORY_SQL_TRUST_CERT"] === "true",
|
|
2028
|
+
cosmosDbEndpoint: process.env["DEEP_MEMORY_COSMOSDB_ENDPOINT"],
|
|
2029
|
+
cosmosDbRestEndpoint: process.env["DEEP_MEMORY_COSMOSDB_REST_ENDPOINT"],
|
|
2030
|
+
cosmosDbKey: process.env["DEEP_MEMORY_COSMOSDB_KEY"],
|
|
2031
|
+
cosmosDbDatabase: process.env["DEEP_MEMORY_COSMOSDB_DATABASE"],
|
|
2032
|
+
cosmosDbContainer: process.env["DEEP_MEMORY_COSMOSDB_CONTAINER"],
|
|
2033
|
+
cosmosDbRejectUnauthorized: process.env["DEEP_MEMORY_COSMOSDB_REJECT_UNAUTHORIZED"] !== "false",
|
|
2034
|
+
exportDir: process.env["DEEP_MEMORY_EXPORT_DIR"] ?? "./exports"
|
|
2035
|
+
},
|
|
2036
|
+
logger
|
|
2037
|
+
);
|
|
2038
|
+
process.on("SIGINT", async () => {
|
|
2039
|
+
await server.stop();
|
|
2040
|
+
process.exit(0);
|
|
2041
|
+
});
|
|
2042
|
+
process.on("SIGTERM", async () => {
|
|
2043
|
+
await server.stop();
|
|
2044
|
+
process.exit(0);
|
|
2045
|
+
});
|
|
2046
|
+
server.start().catch((error) => {
|
|
2047
|
+
logger.error("main", "Failed to start MCP server", error);
|
|
2048
|
+
process.exit(1);
|
|
2049
|
+
});
|
|
2050
|
+
//# sourceMappingURL=index.js.map
|