graph.do 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/dist/chunk-SOVFPCTE.js +647 -0
- package/dist/chunk-SOVFPCTE.js.map +1 -0
- package/dist/chunk-VXRM2HKS.js +203 -0
- package/dist/chunk-VXRM2HKS.js.map +1 -0
- package/dist/cli/index.js +724 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index-BmsgR5Vk.d.ts +257 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +15 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/worker/index.d.ts +15 -0
- package/dist/worker/index.js +56 -0
- package/dist/worker/index.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/cli/index.ts
|
|
5
|
+
import { MongoClient } from "mongo.do";
|
|
6
|
+
|
|
7
|
+
// src/graph-manager.ts
|
|
8
|
+
var KnowledgeGraphManager = class {
|
|
9
|
+
entities;
|
|
10
|
+
relations;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.entities = config.entities;
|
|
13
|
+
this.relations = config.relations;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create new entities (ignores duplicates)
|
|
17
|
+
*/
|
|
18
|
+
async createEntities(entities) {
|
|
19
|
+
const created = [];
|
|
20
|
+
for (const entity of entities) {
|
|
21
|
+
const existing = await this.entities.findOne({ name: entity.name });
|
|
22
|
+
if (!existing) {
|
|
23
|
+
await this.entities.insertOne({
|
|
24
|
+
name: entity.name,
|
|
25
|
+
entityType: entity.entityType,
|
|
26
|
+
observations: entity.observations
|
|
27
|
+
});
|
|
28
|
+
created.push(entity);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return created;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create new relations (skips duplicates)
|
|
35
|
+
*/
|
|
36
|
+
async createRelations(relations) {
|
|
37
|
+
const created = [];
|
|
38
|
+
for (const relation of relations) {
|
|
39
|
+
const existing = await this.relations.findOne({
|
|
40
|
+
from: relation.from,
|
|
41
|
+
to: relation.to,
|
|
42
|
+
relationType: relation.relationType
|
|
43
|
+
});
|
|
44
|
+
if (!existing) {
|
|
45
|
+
await this.relations.insertOne({
|
|
46
|
+
from: relation.from,
|
|
47
|
+
to: relation.to,
|
|
48
|
+
relationType: relation.relationType
|
|
49
|
+
});
|
|
50
|
+
created.push(relation);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return created;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Add observations to existing entities
|
|
57
|
+
* Throws if entity doesn't exist
|
|
58
|
+
*/
|
|
59
|
+
async addObservations(observations) {
|
|
60
|
+
const results = [];
|
|
61
|
+
for (const { entityName, contents } of observations) {
|
|
62
|
+
const entity = await this.entities.findOne({ name: entityName });
|
|
63
|
+
if (!entity) {
|
|
64
|
+
throw new Error(`Entity with name "${entityName}" not found`);
|
|
65
|
+
}
|
|
66
|
+
const existingObservations = new Set(entity.observations);
|
|
67
|
+
const newObservations = contents.filter((obs) => !existingObservations.has(obs));
|
|
68
|
+
if (newObservations.length > 0) {
|
|
69
|
+
await this.entities.updateOne(
|
|
70
|
+
{ name: entityName },
|
|
71
|
+
{ $push: { observations: { $each: newObservations } } }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
results.push({
|
|
75
|
+
entityName,
|
|
76
|
+
addedObservations: newObservations
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Delete entities and cascade delete their relations
|
|
83
|
+
*/
|
|
84
|
+
async deleteEntities(entityNames) {
|
|
85
|
+
const deleteResult = await this.entities.deleteMany({
|
|
86
|
+
name: { $in: entityNames }
|
|
87
|
+
});
|
|
88
|
+
await this.relations.deleteMany({
|
|
89
|
+
$or: [{ from: { $in: entityNames } }, { to: { $in: entityNames } }]
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
message: `Deleted ${deleteResult.deletedCount} entities and their relations`
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Delete specific observations from entities (silent on non-existent)
|
|
98
|
+
*/
|
|
99
|
+
async deleteObservations(deletions) {
|
|
100
|
+
let totalRemoved = 0;
|
|
101
|
+
for (const { entityName, observations } of deletions) {
|
|
102
|
+
const entity = await this.entities.findOne({ name: entityName });
|
|
103
|
+
if (entity) {
|
|
104
|
+
const observationsToRemove = new Set(observations);
|
|
105
|
+
const remaining = entity.observations.filter((obs) => !observationsToRemove.has(obs));
|
|
106
|
+
const removed = entity.observations.length - remaining.length;
|
|
107
|
+
if (removed > 0) {
|
|
108
|
+
await this.entities.updateOne({ name: entityName }, { $set: { observations: remaining } });
|
|
109
|
+
totalRemoved += removed;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
message: `Removed ${totalRemoved} observations`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Delete specific relations (silent on non-existent)
|
|
120
|
+
*/
|
|
121
|
+
async deleteRelations(relations) {
|
|
122
|
+
let totalDeleted = 0;
|
|
123
|
+
for (const relation of relations) {
|
|
124
|
+
const result = await this.relations.deleteOne({
|
|
125
|
+
from: relation.from,
|
|
126
|
+
to: relation.to,
|
|
127
|
+
relationType: relation.relationType
|
|
128
|
+
});
|
|
129
|
+
totalDeleted += result.deletedCount;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
success: true,
|
|
133
|
+
message: `Deleted ${totalDeleted} relations`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Read the complete knowledge graph
|
|
138
|
+
*/
|
|
139
|
+
async readGraph() {
|
|
140
|
+
const entities = await this.entities.find({}).toArray();
|
|
141
|
+
const relations = await this.relations.find({}).toArray();
|
|
142
|
+
return {
|
|
143
|
+
entities: entities.map(({ _id, ...entity }) => entity),
|
|
144
|
+
relations: relations.map(({ _id, ...relation }) => relation)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Search nodes by query (matches name, type, or observations)
|
|
149
|
+
*/
|
|
150
|
+
async searchNodes(query) {
|
|
151
|
+
const lowerQuery = query.toLowerCase();
|
|
152
|
+
const allEntities = await this.entities.find({}).toArray();
|
|
153
|
+
const matchingEntities = allEntities.filter((entity) => {
|
|
154
|
+
const nameMatch = entity.name.toLowerCase().includes(lowerQuery);
|
|
155
|
+
const typeMatch = entity.entityType.toLowerCase().includes(lowerQuery);
|
|
156
|
+
const observationMatch = entity.observations.some(
|
|
157
|
+
(obs) => obs.toLowerCase().includes(lowerQuery)
|
|
158
|
+
);
|
|
159
|
+
return nameMatch || typeMatch || observationMatch;
|
|
160
|
+
});
|
|
161
|
+
const matchingNames = new Set(matchingEntities.map((e) => e.name));
|
|
162
|
+
const relations = await this.relations.find({}).toArray();
|
|
163
|
+
const matchingRelations = relations.filter(
|
|
164
|
+
(rel) => matchingNames.has(rel.from) && matchingNames.has(rel.to)
|
|
165
|
+
);
|
|
166
|
+
return {
|
|
167
|
+
entities: matchingEntities.map(({ _id, ...entity }) => entity),
|
|
168
|
+
relations: matchingRelations.map(({ _id, ...relation }) => relation)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Open specific nodes by name and return them with their inter-relations
|
|
173
|
+
*/
|
|
174
|
+
async openNodes(names) {
|
|
175
|
+
const nameSet = new Set(names);
|
|
176
|
+
const entities = await this.entities.find({ name: { $in: names } }).toArray();
|
|
177
|
+
const relations = await this.relations.find({
|
|
178
|
+
from: { $in: names },
|
|
179
|
+
to: { $in: names }
|
|
180
|
+
}).toArray();
|
|
181
|
+
return {
|
|
182
|
+
entities: entities.map(({ _id, ...entity }) => entity),
|
|
183
|
+
relations: relations.map(({ _id, ...relation }) => relation)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get a single entity by name
|
|
188
|
+
*/
|
|
189
|
+
async getEntity(name) {
|
|
190
|
+
const entity = await this.entities.findOne({ name });
|
|
191
|
+
if (!entity) return null;
|
|
192
|
+
const { _id, ...rest } = entity;
|
|
193
|
+
return rest;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get all relations for an entity
|
|
197
|
+
*/
|
|
198
|
+
async getRelationsFor(entityName) {
|
|
199
|
+
const relations = await this.relations.find({
|
|
200
|
+
$or: [{ from: entityName }, { to: entityName }]
|
|
201
|
+
}).toArray();
|
|
202
|
+
return relations.map(({ _id, ...relation }) => relation);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// src/types.ts
|
|
207
|
+
import { z } from "zod";
|
|
208
|
+
var EntitySchema = z.object({
|
|
209
|
+
name: z.string().describe("Unique identifier for the entity"),
|
|
210
|
+
entityType: z.string().describe("Type classification of the entity"),
|
|
211
|
+
observations: z.array(z.string()).describe("Array of atomic facts about the entity")
|
|
212
|
+
});
|
|
213
|
+
var RelationSchema = z.object({
|
|
214
|
+
from: z.string().describe("Source entity name"),
|
|
215
|
+
to: z.string().describe("Target entity name"),
|
|
216
|
+
relationType: z.string().describe("Type of relationship (active voice)")
|
|
217
|
+
});
|
|
218
|
+
var KnowledgeGraphSchema = z.object({
|
|
219
|
+
entities: z.array(EntitySchema),
|
|
220
|
+
relations: z.array(RelationSchema)
|
|
221
|
+
});
|
|
222
|
+
var CreateEntitiesInputSchema = z.object({
|
|
223
|
+
entities: z.array(EntitySchema).describe("Array of entities to create")
|
|
224
|
+
});
|
|
225
|
+
var CreateRelationsInputSchema = z.object({
|
|
226
|
+
relations: z.array(RelationSchema).describe("Array of relations to create")
|
|
227
|
+
});
|
|
228
|
+
var AddObservationsInputSchema = z.object({
|
|
229
|
+
observations: z.array(
|
|
230
|
+
z.object({
|
|
231
|
+
entityName: z.string().describe("Name of the entity to add observations to"),
|
|
232
|
+
contents: z.array(z.string()).describe("Array of observations to add")
|
|
233
|
+
})
|
|
234
|
+
).describe("Array of observation additions")
|
|
235
|
+
});
|
|
236
|
+
var DeleteEntitiesInputSchema = z.object({
|
|
237
|
+
entityNames: z.array(z.string()).describe("Array of entity names to delete")
|
|
238
|
+
});
|
|
239
|
+
var DeleteObservationsInputSchema = z.object({
|
|
240
|
+
deletions: z.array(
|
|
241
|
+
z.object({
|
|
242
|
+
entityName: z.string().describe("Name of the entity"),
|
|
243
|
+
observations: z.array(z.string()).describe("Observations to remove")
|
|
244
|
+
})
|
|
245
|
+
).describe("Array of observation deletions")
|
|
246
|
+
});
|
|
247
|
+
var DeleteRelationsInputSchema = z.object({
|
|
248
|
+
relations: z.array(RelationSchema).describe("Array of relations to delete")
|
|
249
|
+
});
|
|
250
|
+
var SearchNodesInputSchema = z.object({
|
|
251
|
+
query: z.string().describe("Search query to match against entity names, types, and observations")
|
|
252
|
+
});
|
|
253
|
+
var OpenNodesInputSchema = z.object({
|
|
254
|
+
names: z.array(z.string()).describe("Array of entity names to retrieve")
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// src/mcp/server.ts
|
|
258
|
+
var McpErrorCode = {
|
|
259
|
+
PARSE_ERROR: -32700,
|
|
260
|
+
INVALID_REQUEST: -32600,
|
|
261
|
+
METHOD_NOT_FOUND: -32601,
|
|
262
|
+
INVALID_PARAMS: -32602,
|
|
263
|
+
INTERNAL_ERROR: -32603
|
|
264
|
+
};
|
|
265
|
+
function createGraphMcpServer(config) {
|
|
266
|
+
const { name = "graph.do", version = "0.1.0", graphManager } = config;
|
|
267
|
+
const tools = [
|
|
268
|
+
{
|
|
269
|
+
name: "create_entities",
|
|
270
|
+
description: "Create multiple new entities in the knowledge graph. Entities are nodes with a unique name, type, and observations.",
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {
|
|
274
|
+
entities: {
|
|
275
|
+
type: "array",
|
|
276
|
+
items: {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: {
|
|
279
|
+
name: { type: "string", description: "Unique identifier for the entity" },
|
|
280
|
+
entityType: { type: "string", description: "Type classification of the entity" },
|
|
281
|
+
observations: {
|
|
282
|
+
type: "array",
|
|
283
|
+
items: { type: "string" },
|
|
284
|
+
description: "Array of atomic facts about the entity"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
required: ["name", "entityType", "observations"]
|
|
288
|
+
},
|
|
289
|
+
description: "Array of entities to create"
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
required: ["entities"]
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "create_relations",
|
|
297
|
+
description: "Create multiple new relations between entities. Relations are directed edges with a type.",
|
|
298
|
+
inputSchema: {
|
|
299
|
+
type: "object",
|
|
300
|
+
properties: {
|
|
301
|
+
relations: {
|
|
302
|
+
type: "array",
|
|
303
|
+
items: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
from: { type: "string", description: "Source entity name" },
|
|
307
|
+
to: { type: "string", description: "Target entity name" },
|
|
308
|
+
relationType: {
|
|
309
|
+
type: "string",
|
|
310
|
+
description: "Type of relationship (active voice)"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
required: ["from", "to", "relationType"]
|
|
314
|
+
},
|
|
315
|
+
description: "Array of relations to create"
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
required: ["relations"]
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "add_observations",
|
|
323
|
+
description: "Add new observations to existing entities. Observations are atomic facts about an entity.",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
observations: {
|
|
328
|
+
type: "array",
|
|
329
|
+
items: {
|
|
330
|
+
type: "object",
|
|
331
|
+
properties: {
|
|
332
|
+
entityName: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description: "Name of the entity to add observations to"
|
|
335
|
+
},
|
|
336
|
+
contents: {
|
|
337
|
+
type: "array",
|
|
338
|
+
items: { type: "string" },
|
|
339
|
+
description: "Array of observations to add"
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
required: ["entityName", "contents"]
|
|
343
|
+
},
|
|
344
|
+
description: "Array of observation additions"
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
required: ["observations"]
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "delete_entities",
|
|
352
|
+
description: "Delete multiple entities and their relations from the knowledge graph by name.",
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: "object",
|
|
355
|
+
properties: {
|
|
356
|
+
entityNames: {
|
|
357
|
+
type: "array",
|
|
358
|
+
items: { type: "string" },
|
|
359
|
+
description: "Array of entity names to delete"
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
required: ["entityNames"]
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: "delete_observations",
|
|
367
|
+
description: "Delete specific observations from entities.",
|
|
368
|
+
inputSchema: {
|
|
369
|
+
type: "object",
|
|
370
|
+
properties: {
|
|
371
|
+
deletions: {
|
|
372
|
+
type: "array",
|
|
373
|
+
items: {
|
|
374
|
+
type: "object",
|
|
375
|
+
properties: {
|
|
376
|
+
entityName: { type: "string", description: "Name of the entity" },
|
|
377
|
+
observations: {
|
|
378
|
+
type: "array",
|
|
379
|
+
items: { type: "string" },
|
|
380
|
+
description: "Observations to remove"
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
required: ["entityName", "observations"]
|
|
384
|
+
},
|
|
385
|
+
description: "Array of observation deletions"
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
required: ["deletions"]
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: "delete_relations",
|
|
393
|
+
description: "Delete specific relations from the knowledge graph.",
|
|
394
|
+
inputSchema: {
|
|
395
|
+
type: "object",
|
|
396
|
+
properties: {
|
|
397
|
+
relations: {
|
|
398
|
+
type: "array",
|
|
399
|
+
items: {
|
|
400
|
+
type: "object",
|
|
401
|
+
properties: {
|
|
402
|
+
from: { type: "string", description: "Source entity name" },
|
|
403
|
+
to: { type: "string", description: "Target entity name" },
|
|
404
|
+
relationType: { type: "string", description: "Type of relationship" }
|
|
405
|
+
},
|
|
406
|
+
required: ["from", "to", "relationType"]
|
|
407
|
+
},
|
|
408
|
+
description: "Array of relations to delete"
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
required: ["relations"]
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "read_graph",
|
|
416
|
+
description: "Read the entire knowledge graph including all entities and relations.",
|
|
417
|
+
inputSchema: {
|
|
418
|
+
type: "object",
|
|
419
|
+
properties: {}
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "search_nodes",
|
|
424
|
+
description: "Search for nodes in the knowledge graph by matching names, types, or observations.",
|
|
425
|
+
inputSchema: {
|
|
426
|
+
type: "object",
|
|
427
|
+
properties: {
|
|
428
|
+
query: {
|
|
429
|
+
type: "string",
|
|
430
|
+
description: "Search query to match against entity names, types, and observations"
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
required: ["query"]
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "open_nodes",
|
|
438
|
+
description: "Open specific nodes by name and retrieve them with their inter-relations.",
|
|
439
|
+
inputSchema: {
|
|
440
|
+
type: "object",
|
|
441
|
+
properties: {
|
|
442
|
+
names: {
|
|
443
|
+
type: "array",
|
|
444
|
+
items: { type: "string" },
|
|
445
|
+
description: "Array of entity names to retrieve"
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
required: ["names"]
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
];
|
|
452
|
+
const toolHandlers = {
|
|
453
|
+
create_entities: async (input) => {
|
|
454
|
+
const { entities } = CreateEntitiesInputSchema.parse(input);
|
|
455
|
+
const created = await graphManager.createEntities(entities);
|
|
456
|
+
return formatGraphResponse(created, `Created ${created.length} entities`);
|
|
457
|
+
},
|
|
458
|
+
create_relations: async (input) => {
|
|
459
|
+
const { relations } = CreateRelationsInputSchema.parse(input);
|
|
460
|
+
const created = await graphManager.createRelations(relations);
|
|
461
|
+
return formatGraphResponse(created, `Created ${created.length} relations`);
|
|
462
|
+
},
|
|
463
|
+
add_observations: async (input) => {
|
|
464
|
+
const { observations } = AddObservationsInputSchema.parse(input);
|
|
465
|
+
const results = await graphManager.addObservations(observations);
|
|
466
|
+
return formatGraphResponse(results, "Observations added");
|
|
467
|
+
},
|
|
468
|
+
delete_entities: async (input) => {
|
|
469
|
+
const { entityNames } = DeleteEntitiesInputSchema.parse(input);
|
|
470
|
+
const result = await graphManager.deleteEntities(entityNames);
|
|
471
|
+
return {
|
|
472
|
+
content: [{ type: "text", text: result.message }]
|
|
473
|
+
};
|
|
474
|
+
},
|
|
475
|
+
delete_observations: async (input) => {
|
|
476
|
+
const { deletions } = DeleteObservationsInputSchema.parse(input);
|
|
477
|
+
const result = await graphManager.deleteObservations(deletions);
|
|
478
|
+
return {
|
|
479
|
+
content: [{ type: "text", text: result.message }]
|
|
480
|
+
};
|
|
481
|
+
},
|
|
482
|
+
delete_relations: async (input) => {
|
|
483
|
+
const { relations } = DeleteRelationsInputSchema.parse(input);
|
|
484
|
+
const result = await graphManager.deleteRelations(relations);
|
|
485
|
+
return {
|
|
486
|
+
content: [{ type: "text", text: result.message }]
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
read_graph: async () => {
|
|
490
|
+
const graph = await graphManager.readGraph();
|
|
491
|
+
return formatGraphResponse(graph, `Graph contains ${graph.entities.length} entities and ${graph.relations.length} relations`);
|
|
492
|
+
},
|
|
493
|
+
search_nodes: async (input) => {
|
|
494
|
+
const { query } = SearchNodesInputSchema.parse(input);
|
|
495
|
+
const graph = await graphManager.searchNodes(query);
|
|
496
|
+
return formatGraphResponse(graph, `Found ${graph.entities.length} matching entities`);
|
|
497
|
+
},
|
|
498
|
+
open_nodes: async (input) => {
|
|
499
|
+
const { names } = OpenNodesInputSchema.parse(input);
|
|
500
|
+
const graph = await graphManager.openNodes(names);
|
|
501
|
+
return formatGraphResponse(graph, `Opened ${graph.entities.length} entities`);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
function formatGraphResponse(data, summary) {
|
|
505
|
+
return {
|
|
506
|
+
content: [
|
|
507
|
+
{ type: "text", text: summary },
|
|
508
|
+
{
|
|
509
|
+
type: "resource",
|
|
510
|
+
resource: {
|
|
511
|
+
uri: "graph://result",
|
|
512
|
+
mimeType: "application/json",
|
|
513
|
+
text: JSON.stringify(data, null, 2)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
]
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
async function handleRequest(request) {
|
|
520
|
+
const { id, method, params } = request;
|
|
521
|
+
try {
|
|
522
|
+
switch (method) {
|
|
523
|
+
case "initialize":
|
|
524
|
+
return {
|
|
525
|
+
jsonrpc: "2.0",
|
|
526
|
+
id,
|
|
527
|
+
result: {
|
|
528
|
+
protocolVersion: "2024-11-05",
|
|
529
|
+
capabilities: {
|
|
530
|
+
tools: {}
|
|
531
|
+
},
|
|
532
|
+
serverInfo: {
|
|
533
|
+
name,
|
|
534
|
+
version
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
case "notifications/initialized":
|
|
539
|
+
return { jsonrpc: "2.0", id, result: {} };
|
|
540
|
+
case "tools/list":
|
|
541
|
+
return {
|
|
542
|
+
jsonrpc: "2.0",
|
|
543
|
+
id,
|
|
544
|
+
result: { tools }
|
|
545
|
+
};
|
|
546
|
+
case "tools/call": {
|
|
547
|
+
const toolName = params?.name;
|
|
548
|
+
const toolInput = params?.arguments ?? {};
|
|
549
|
+
const handler = toolHandlers[toolName];
|
|
550
|
+
if (!handler) {
|
|
551
|
+
return {
|
|
552
|
+
jsonrpc: "2.0",
|
|
553
|
+
id,
|
|
554
|
+
error: {
|
|
555
|
+
code: McpErrorCode.METHOD_NOT_FOUND,
|
|
556
|
+
message: `Unknown tool: ${toolName}`
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
const result = await handler(toolInput);
|
|
562
|
+
return { jsonrpc: "2.0", id, result };
|
|
563
|
+
} catch (err) {
|
|
564
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
565
|
+
return {
|
|
566
|
+
jsonrpc: "2.0",
|
|
567
|
+
id,
|
|
568
|
+
result: {
|
|
569
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
570
|
+
isError: true
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
case "ping":
|
|
576
|
+
return { jsonrpc: "2.0", id, result: {} };
|
|
577
|
+
default:
|
|
578
|
+
return {
|
|
579
|
+
jsonrpc: "2.0",
|
|
580
|
+
id,
|
|
581
|
+
error: {
|
|
582
|
+
code: McpErrorCode.METHOD_NOT_FOUND,
|
|
583
|
+
message: `Unknown method: ${method}`
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
} catch (err) {
|
|
588
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
589
|
+
return {
|
|
590
|
+
jsonrpc: "2.0",
|
|
591
|
+
id,
|
|
592
|
+
error: {
|
|
593
|
+
code: McpErrorCode.INTERNAL_ERROR,
|
|
594
|
+
message: error.message
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
name,
|
|
601
|
+
version,
|
|
602
|
+
tools,
|
|
603
|
+
handleRequest,
|
|
604
|
+
graphManager
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/mcp/stdio-transport.ts
|
|
609
|
+
async function runStdioTransport(server) {
|
|
610
|
+
const stdin = process.stdin;
|
|
611
|
+
const stdout = process.stdout;
|
|
612
|
+
const stderr = process.stderr;
|
|
613
|
+
stdin.setEncoding("utf8");
|
|
614
|
+
let buffer = "";
|
|
615
|
+
stderr.write(`${server.name} v${server.version} MCP server started (stdio)
|
|
616
|
+
`);
|
|
617
|
+
stdin.on("data", async (chunk) => {
|
|
618
|
+
buffer += chunk;
|
|
619
|
+
while (true) {
|
|
620
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
621
|
+
if (newlineIndex === -1) break;
|
|
622
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
623
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
624
|
+
if (!line) continue;
|
|
625
|
+
try {
|
|
626
|
+
const request = JSON.parse(line);
|
|
627
|
+
const response = await server.handleRequest(request);
|
|
628
|
+
stdout.write(JSON.stringify(response) + "\n");
|
|
629
|
+
} catch (err) {
|
|
630
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
631
|
+
const errorResponse = {
|
|
632
|
+
jsonrpc: "2.0",
|
|
633
|
+
id: 0,
|
|
634
|
+
error: {
|
|
635
|
+
code: -32700,
|
|
636
|
+
message: `Parse error: ${error.message}`
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
stdin.on("end", () => {
|
|
644
|
+
stderr.write("stdin closed, shutting down\n");
|
|
645
|
+
process.exit(0);
|
|
646
|
+
});
|
|
647
|
+
stdin.on("error", (err) => {
|
|
648
|
+
stderr.write(`stdin error: ${err.message}
|
|
649
|
+
`);
|
|
650
|
+
process.exit(1);
|
|
651
|
+
});
|
|
652
|
+
await new Promise(() => {
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/cli/index.ts
|
|
657
|
+
var DEFAULT_URL = "https://graph.do";
|
|
658
|
+
function parseArgs(args) {
|
|
659
|
+
const options = {};
|
|
660
|
+
for (let i = 0; i < args.length; i++) {
|
|
661
|
+
const arg = args[i];
|
|
662
|
+
if (arg === "--help" || arg === "-h") {
|
|
663
|
+
options.help = true;
|
|
664
|
+
} else if (arg === "--url" || arg === "-u") {
|
|
665
|
+
options.url = args[++i];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return options;
|
|
669
|
+
}
|
|
670
|
+
function printHelp() {
|
|
671
|
+
console.log(`
|
|
672
|
+
graph.do - Knowledge Graph MCP Server
|
|
673
|
+
|
|
674
|
+
Usage:
|
|
675
|
+
graph.do [options]
|
|
676
|
+
|
|
677
|
+
Options:
|
|
678
|
+
-u, --url <url> graph.do URL (default: https://graph.do)
|
|
679
|
+
-h, --help Show this help message
|
|
680
|
+
|
|
681
|
+
Environment Variables:
|
|
682
|
+
GRAPH_URL graph.do URL (default: https://graph.do)
|
|
683
|
+
|
|
684
|
+
Examples:
|
|
685
|
+
# Start with defaults (uses https://graph.do)
|
|
686
|
+
graph.do
|
|
687
|
+
|
|
688
|
+
# Use a custom deployment
|
|
689
|
+
graph.do --url https://my-graph.workers.dev
|
|
690
|
+
|
|
691
|
+
The server communicates via stdio using the MCP protocol.
|
|
692
|
+
`);
|
|
693
|
+
}
|
|
694
|
+
async function main() {
|
|
695
|
+
const args = process.argv.slice(2);
|
|
696
|
+
const options = parseArgs(args);
|
|
697
|
+
if (options.help) {
|
|
698
|
+
printHelp();
|
|
699
|
+
process.exit(0);
|
|
700
|
+
}
|
|
701
|
+
const graphUrl = options.url || process.env.GRAPH_URL || DEFAULT_URL;
|
|
702
|
+
try {
|
|
703
|
+
const client = new MongoClient(graphUrl);
|
|
704
|
+
const db = client.db("graph");
|
|
705
|
+
const entitiesCollection = db.collection("entities");
|
|
706
|
+
const relationsCollection = db.collection("relations");
|
|
707
|
+
const graphManager = new KnowledgeGraphManager({
|
|
708
|
+
entities: entitiesCollection,
|
|
709
|
+
relations: relationsCollection
|
|
710
|
+
});
|
|
711
|
+
const server = createGraphMcpServer({
|
|
712
|
+
name: "graph.do",
|
|
713
|
+
version: "0.1.0",
|
|
714
|
+
graphManager
|
|
715
|
+
});
|
|
716
|
+
await runStdioTransport(server);
|
|
717
|
+
} catch (err) {
|
|
718
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
719
|
+
console.error(`Fatal error: ${error.message}`);
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
main();
|
|
724
|
+
//# sourceMappingURL=index.js.map
|