@revealui/mcp 0.8.9 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/servers/_kg-principal.d.ts +27 -0
- package/dist/servers/_kg-principal.d.ts.map +1 -0
- package/dist/servers/_kg-principal.js +111 -0
- package/dist/servers/_kg-principal.js.map +1 -0
- package/dist/servers/factories/knowledge-graph.d.ts +153 -14
- package/dist/servers/factories/knowledge-graph.d.ts.map +1 -1
- package/dist/servers/factories/knowledge-graph.js +571 -219
- package/dist/servers/factories/knowledge-graph.js.map +1 -1
- package/dist/servers/factories/revealui-content.d.ts +19 -0
- package/dist/servers/factories/revealui-content.d.ts.map +1 -1
- package/dist/servers/factories/revealui-content.js +117 -2
- package/dist/servers/factories/revealui-content.js.map +1 -1
- package/dist/servers/knowledge-graph.d.ts +14 -0
- package/dist/servers/knowledge-graph.d.ts.map +1 -0
- package/dist/servers/knowledge-graph.js +45 -0
- package/dist/servers/knowledge-graph.js.map +1 -0
- package/package.json +7 -7
|
@@ -32,12 +32,20 @@
|
|
|
32
32
|
* reserved for `revkg scan`), Zod-validates `episodeType` plus every node kind
|
|
33
33
|
* and edge relation against the ontology enums, and accepts no raw SQL or
|
|
34
34
|
* table-name input of any kind.
|
|
35
|
+
*
|
|
36
|
+
* Product mode (`mode: 'product'`) extracts `createKnowledgeGraphToolset` so
|
|
37
|
+
* stdio and a later hosted composite share one dispatcher. CallTool threads
|
|
38
|
+
* `extra.authInfo` / `extra.sessionId`. Writes stamp actor + scope from
|
|
39
|
+
* `principalProvider` (client `source` / `contentRef.actorDid` are ignored).
|
|
40
|
+
* Compat mode (default) keeps today's unwrapped JSON for in-repo tests.
|
|
35
41
|
*/
|
|
42
|
+
import { createHash } from 'node:crypto';
|
|
36
43
|
import { hostname } from 'node:os';
|
|
37
44
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
38
45
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
39
|
-
import { EDGE_RELATIONS, EPISODE_TYPES, ingestEpisode, kgAtTime, kgNeighbors, kgPath, kgSearch, makePoolExecutor, NODE_KINDS, } from '@revealui/knowledge-graph';
|
|
46
|
+
import { assembleContext, EDGE_RELATIONS, EPISODE_TYPES, ingestEpisode, kgAtTime, kgNeighbors, kgPath, kgSearch, makePoolExecutor, NODE_KINDS, } from '@revealui/knowledge-graph';
|
|
40
47
|
import { resolveNaturalKey } from '@revealui/knowledge-graph/ingest';
|
|
48
|
+
import { countDeniedMemoryHits, inspectNodeVisibility, MEMORY_MAX_CONTENT_CHARS, MEMORY_MAX_EDGES, MEMORY_MAX_NODES, MEMORY_SCHEMA, shouldNamespaceKeys, tenantNaturalKey, validatePrincipal, } from '@revealui/knowledge-graph/memory';
|
|
41
49
|
import { z } from 'zod/v4';
|
|
42
50
|
import { validateToolArgs } from '../../validate-tool-args.js';
|
|
43
51
|
const SERVER_NAME = 'knowledge-graph';
|
|
@@ -47,6 +55,11 @@ const DEFAULT_CONTEXT_CHAR_BUDGET = 16_000;
|
|
|
47
55
|
/** Hard caps for kg_add_episode payloads (prevents unbounded Neon writes). */
|
|
48
56
|
const MAX_EPISODE_CONTENT_CHARS = 64_000;
|
|
49
57
|
const MAX_EPISODE_ATTRIBUTES_JSON_CHARS = 16_000;
|
|
58
|
+
/** Product-mode write types. Compat still accepts the full ontology enum. */
|
|
59
|
+
const PRODUCT_EPISODE_TYPES = ['agent-fact', 'memory', 'manual'];
|
|
60
|
+
/** Dispatch Promise.race budget. Hung writes must not block the agent. */
|
|
61
|
+
export const DEFAULT_KG_TOOL_TIMEOUT_MS = 4_000;
|
|
62
|
+
const DEFAULT_MUTATING_TOOLS = new Set(['kg_add_episode']);
|
|
50
63
|
// ---------------------------------------------------------------------------
|
|
51
64
|
// Tool argument schemas (Zod 4)
|
|
52
65
|
// ---------------------------------------------------------------------------
|
|
@@ -102,22 +115,7 @@ const EdgeInputArgsSchema = z
|
|
|
102
115
|
attributes: z.record(z.string(), z.unknown()).optional(),
|
|
103
116
|
})
|
|
104
117
|
.strict();
|
|
105
|
-
|
|
106
|
-
.object({
|
|
107
|
-
episodeType: z.enum(EPISODE_TYPES),
|
|
108
|
-
source: z.string().min(1),
|
|
109
|
-
content: z
|
|
110
|
-
.string()
|
|
111
|
-
.max(MAX_EPISODE_CONTENT_CHARS, `content max ${MAX_EPISODE_CONTENT_CHARS} chars`)
|
|
112
|
-
.optional(),
|
|
113
|
-
contentRef: z.record(z.string(), z.unknown()).optional(),
|
|
114
|
-
referenceTime: z.string().datetime().optional(),
|
|
115
|
-
siteId: z.string().min(1).optional(),
|
|
116
|
-
nodes: z.array(NodeInputArgsSchema).default([]),
|
|
117
|
-
edges: z.array(EdgeInputArgsSchema).default([]),
|
|
118
|
-
})
|
|
119
|
-
.strict()
|
|
120
|
-
.superRefine((val, ctx) => {
|
|
118
|
+
function refineEpisodeJsonCaps(val, ctx) {
|
|
121
119
|
if (val.contentRef) {
|
|
122
120
|
const size = JSON.stringify(val.contentRef).length;
|
|
123
121
|
if (size > MAX_EPISODE_ATTRIBUTES_JSON_CHARS) {
|
|
@@ -154,7 +152,41 @@ export const KgAddEpisodeArgsSchema = z
|
|
|
154
152
|
});
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
|
-
}
|
|
155
|
+
}
|
|
156
|
+
export const KgAddEpisodeArgsSchema = z
|
|
157
|
+
.object({
|
|
158
|
+
episodeType: z.enum(EPISODE_TYPES),
|
|
159
|
+
source: z.string().min(1),
|
|
160
|
+
content: z
|
|
161
|
+
.string()
|
|
162
|
+
.max(MAX_EPISODE_CONTENT_CHARS, `content max ${MAX_EPISODE_CONTENT_CHARS} chars`)
|
|
163
|
+
.optional(),
|
|
164
|
+
contentRef: z.record(z.string(), z.unknown()).optional(),
|
|
165
|
+
referenceTime: z.string().datetime().optional(),
|
|
166
|
+
siteId: z.string().min(1).optional(),
|
|
167
|
+
nodes: z.array(NodeInputArgsSchema).default([]),
|
|
168
|
+
edges: z.array(EdgeInputArgsSchema).default([]),
|
|
169
|
+
})
|
|
170
|
+
.strict()
|
|
171
|
+
.superRefine(refineEpisodeJsonCaps);
|
|
172
|
+
export const KgProductAddEpisodeArgsSchema = z
|
|
173
|
+
.object({
|
|
174
|
+
episodeType: z.enum(PRODUCT_EPISODE_TYPES),
|
|
175
|
+
/** Ignored; stamped from `principalProvider`. */
|
|
176
|
+
source: z.string().min(1).optional(),
|
|
177
|
+
content: z
|
|
178
|
+
.string()
|
|
179
|
+
.max(MEMORY_MAX_CONTENT_CHARS, `content max ${MEMORY_MAX_CONTENT_CHARS} chars`)
|
|
180
|
+
.optional(),
|
|
181
|
+
contentRef: z.record(z.string(), z.unknown()).optional(),
|
|
182
|
+
classification: z.enum(['private', 'workspace']).optional(),
|
|
183
|
+
referenceTime: z.string().datetime().optional(),
|
|
184
|
+
siteId: z.string().min(1).optional(),
|
|
185
|
+
nodes: z.array(NodeInputArgsSchema).max(MEMORY_MAX_NODES).default([]),
|
|
186
|
+
edges: z.array(EdgeInputArgsSchema).max(MEMORY_MAX_EDGES).default([]),
|
|
187
|
+
})
|
|
188
|
+
.strict()
|
|
189
|
+
.superRefine(refineEpisodeJsonCaps);
|
|
158
190
|
export const KgPathArgsSchema = z
|
|
159
191
|
.object({
|
|
160
192
|
fromNaturalKey: z.string().min(1),
|
|
@@ -368,6 +400,86 @@ const TOOLS = [
|
|
|
368
400
|
},
|
|
369
401
|
},
|
|
370
402
|
];
|
|
403
|
+
function productAddEpisodeTool() {
|
|
404
|
+
return {
|
|
405
|
+
name: 'kg_add_episode',
|
|
406
|
+
description: 'Publish an episode (provenance unit) plus candidate nodes/edges into the ' +
|
|
407
|
+
'graph. The ONLY write tool. Always additive (never a rescan). Product ' +
|
|
408
|
+
'mode stamps actor and scope from the session principal — client `source` ' +
|
|
409
|
+
'and `contentRef.actorDid` are ignored. episodeType is agent-fact, memory, ' +
|
|
410
|
+
'or manual.',
|
|
411
|
+
inputSchema: {
|
|
412
|
+
type: 'object',
|
|
413
|
+
properties: {
|
|
414
|
+
episodeType: { type: 'string', enum: [...PRODUCT_EPISODE_TYPES] },
|
|
415
|
+
source: {
|
|
416
|
+
type: 'string',
|
|
417
|
+
description: 'Ignored in product mode; stamped from the session principal.',
|
|
418
|
+
},
|
|
419
|
+
content: {
|
|
420
|
+
type: 'string',
|
|
421
|
+
description: `Raw payload or pointer summary (max ${MEMORY_MAX_CONTENT_CHARS} chars).`,
|
|
422
|
+
},
|
|
423
|
+
contentRef: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
description: 'e.g. { repo, path, sha }. actorDid is ignored.',
|
|
426
|
+
},
|
|
427
|
+
classification: {
|
|
428
|
+
type: 'string',
|
|
429
|
+
enum: ['private', 'workspace'],
|
|
430
|
+
description: 'Memory classification. Default workspace. public is not allowed.',
|
|
431
|
+
},
|
|
432
|
+
referenceTime: {
|
|
433
|
+
type: 'string',
|
|
434
|
+
description: 'ISO-8601; when the described state was true. Defaults to now.',
|
|
435
|
+
},
|
|
436
|
+
siteId: {
|
|
437
|
+
type: 'string',
|
|
438
|
+
description: 'Origin machine/replica id. Defaults to hostname().',
|
|
439
|
+
},
|
|
440
|
+
nodes: {
|
|
441
|
+
type: 'array',
|
|
442
|
+
description: `Candidate nodes to upsert (max ${MEMORY_MAX_NODES}).`,
|
|
443
|
+
items: {
|
|
444
|
+
type: 'object',
|
|
445
|
+
properties: {
|
|
446
|
+
kind: { type: 'string', enum: NODE_KIND_ENUM },
|
|
447
|
+
name: { type: 'string' },
|
|
448
|
+
naturalKey: { type: 'string' },
|
|
449
|
+
repo: { type: 'string' },
|
|
450
|
+
summary: { type: 'string' },
|
|
451
|
+
attributes: { type: 'object' },
|
|
452
|
+
},
|
|
453
|
+
required: ['kind', 'name', 'naturalKey'],
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
edges: {
|
|
457
|
+
type: 'array',
|
|
458
|
+
description: `Candidate facts between nodes (max ${MEMORY_MAX_EDGES}).`,
|
|
459
|
+
items: {
|
|
460
|
+
type: 'object',
|
|
461
|
+
properties: {
|
|
462
|
+
source: NODE_REF_SCHEMA,
|
|
463
|
+
target: NODE_REF_SCHEMA,
|
|
464
|
+
relation: { type: 'string', enum: EDGE_RELATION_ENUM },
|
|
465
|
+
fact: { type: 'string' },
|
|
466
|
+
repo: { type: 'string' },
|
|
467
|
+
validAt: { type: 'string', description: 'ISO-8601; defaults to referenceTime.' },
|
|
468
|
+
attributes: { type: 'object' },
|
|
469
|
+
},
|
|
470
|
+
required: ['source', 'target', 'relation', 'fact'],
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
required: ['episodeType'],
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function toolsForMode(mode) {
|
|
479
|
+
if (mode === 'compat')
|
|
480
|
+
return TOOLS;
|
|
481
|
+
return TOOLS.map((tool) => (tool.name === 'kg_add_episode' ? productAddEpisodeTool() : tool));
|
|
482
|
+
}
|
|
371
483
|
// ---------------------------------------------------------------------------
|
|
372
484
|
// Result helpers
|
|
373
485
|
// ---------------------------------------------------------------------------
|
|
@@ -382,106 +494,140 @@ function errorResult(message) {
|
|
|
382
494
|
isError: true,
|
|
383
495
|
};
|
|
384
496
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const rankedFacts = neighbors.edges
|
|
421
|
-
.map((e) => {
|
|
422
|
-
const m = mentionById.get(e.id) ?? { mentions: 0, episodeIds: [] };
|
|
423
|
-
const nearDistance = Math.min(distanceById.get(e.sourceId) ?? Number.POSITIVE_INFINITY, distanceById.get(e.targetId) ?? Number.POSITIVE_INFINITY);
|
|
424
|
-
return {
|
|
425
|
-
id: e.id,
|
|
426
|
-
sourceId: e.sourceId,
|
|
427
|
-
targetId: e.targetId,
|
|
428
|
-
relation: e.relation,
|
|
429
|
-
fact: e.fact,
|
|
430
|
-
mentions: m.mentions,
|
|
431
|
-
episodeIds: m.episodeIds,
|
|
432
|
-
rankDistance: nearDistance,
|
|
433
|
-
};
|
|
434
|
-
})
|
|
435
|
-
.sort((a, b) => {
|
|
436
|
-
if (b.mentions - a.mentions !== 0)
|
|
437
|
-
return b.mentions - a.mentions;
|
|
438
|
-
if (a.rankDistance - b.rankDistance !== 0)
|
|
439
|
-
return a.rankDistance - b.rankDistance;
|
|
440
|
-
return a.id < b.id ? -1 : 1;
|
|
441
|
-
})
|
|
442
|
-
.map(({ rankDistance: _rankDistance, ...fact }) => fact);
|
|
443
|
-
const lines = [`# Context for ${anchorId} (depth=${opts.depth})`, '', '## Nodes'];
|
|
444
|
-
for (const n of rankedNodes) {
|
|
445
|
-
const summaryPart = n.summary ? ` — ${n.summary}` : '';
|
|
446
|
-
lines.push(`- [${n.kind}] ${n.naturalKey} (${n.distance} hop)${summaryPart}`);
|
|
447
|
-
}
|
|
448
|
-
lines.push('', '## Facts');
|
|
449
|
-
for (const f of rankedFacts) {
|
|
450
|
-
const provenance = f.episodeIds.length > 0 ? f.episodeIds.join(', ') : 'none';
|
|
451
|
-
lines.push(`- (${f.relation}) ${f.fact} [episodes: ${provenance}]`);
|
|
452
|
-
}
|
|
453
|
-
let charsUsed = 0;
|
|
454
|
-
let truncated = false;
|
|
455
|
-
const packed = [];
|
|
456
|
-
for (const line of lines) {
|
|
457
|
-
const addedLength = line.length + 1; // + newline
|
|
458
|
-
if (charsUsed + addedLength > opts.charBudget) {
|
|
459
|
-
truncated = true;
|
|
460
|
-
break;
|
|
461
|
-
}
|
|
462
|
-
packed.push(line);
|
|
463
|
-
charsUsed += addedLength;
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
// Product-mode helpers
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
500
|
+
const AUDIT_SCALAR_ALLOWLIST = [
|
|
501
|
+
'naturalKey',
|
|
502
|
+
'fromNaturalKey',
|
|
503
|
+
'toNaturalKey',
|
|
504
|
+
'episodeType',
|
|
505
|
+
'classification',
|
|
506
|
+
'query',
|
|
507
|
+
];
|
|
508
|
+
function extraToContext(extra) {
|
|
509
|
+
const record = extra;
|
|
510
|
+
return { authInfo: record?.authInfo, sessionId: record?.sessionId };
|
|
511
|
+
}
|
|
512
|
+
function canonicalJson(value) {
|
|
513
|
+
if (value === null || typeof value !== 'object')
|
|
514
|
+
return JSON.stringify(value) ?? 'null';
|
|
515
|
+
if (Array.isArray(value))
|
|
516
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
517
|
+
const entries = Object.entries(value).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
518
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
|
|
519
|
+
}
|
|
520
|
+
function digestArgs(args) {
|
|
521
|
+
return createHash('sha256').update(canonicalJson(args)).digest('hex');
|
|
522
|
+
}
|
|
523
|
+
function pickAuditScalars(args) {
|
|
524
|
+
const out = {};
|
|
525
|
+
if (!args || typeof args !== 'object')
|
|
526
|
+
return out;
|
|
527
|
+
const record = args;
|
|
528
|
+
for (const key of AUDIT_SCALAR_ALLOWLIST) {
|
|
529
|
+
const raw = record[key];
|
|
530
|
+
if (typeof raw === 'string' && raw.length > 0)
|
|
531
|
+
out[key] = raw;
|
|
464
532
|
}
|
|
533
|
+
return out;
|
|
534
|
+
}
|
|
535
|
+
function namespaceKey(principal, kind, key) {
|
|
536
|
+
if (kind === 'agent')
|
|
537
|
+
return key;
|
|
538
|
+
if (!shouldNamespaceKeys(principal))
|
|
539
|
+
return key;
|
|
540
|
+
return tenantNaturalKey(principal.tenantId, key);
|
|
541
|
+
}
|
|
542
|
+
function inboundKey(principal, key) {
|
|
543
|
+
if (!(principal && shouldNamespaceKeys(principal)))
|
|
544
|
+
return key;
|
|
545
|
+
return tenantNaturalKey(principal.tenantId, key);
|
|
546
|
+
}
|
|
547
|
+
function stampContentRef(principal, clientRef, classification) {
|
|
548
|
+
const rest = { ...(clientRef ?? {}) };
|
|
549
|
+
const repo = typeof rest.repo === 'string' ? rest.repo : undefined;
|
|
465
550
|
return {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
551
|
+
...rest,
|
|
552
|
+
schema: MEMORY_SCHEMA,
|
|
553
|
+
actorDid: principal.did,
|
|
554
|
+
harness: principal.harness,
|
|
555
|
+
scope: {
|
|
556
|
+
tenantId: principal.tenantId,
|
|
557
|
+
workspaceId: principal.workspaceId,
|
|
558
|
+
repo,
|
|
559
|
+
classification,
|
|
470
560
|
},
|
|
471
|
-
nodeCount: rankedNodes.length,
|
|
472
|
-
factCount: rankedFacts.length,
|
|
473
|
-
charBudget: opts.charBudget,
|
|
474
|
-
charsUsed,
|
|
475
|
-
truncated,
|
|
476
561
|
};
|
|
477
562
|
}
|
|
563
|
+
function productUnavailable(reason, message) {
|
|
564
|
+
return {
|
|
565
|
+
content: [
|
|
566
|
+
{
|
|
567
|
+
type: 'text',
|
|
568
|
+
text: JSON.stringify({
|
|
569
|
+
status: 'unavailable',
|
|
570
|
+
available: false,
|
|
571
|
+
reason,
|
|
572
|
+
message,
|
|
573
|
+
}),
|
|
574
|
+
},
|
|
575
|
+
],
|
|
576
|
+
isError: true,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
function wrapOk(mode, data, principal, deniedCount = 0) {
|
|
580
|
+
if (mode === 'compat')
|
|
581
|
+
return textResult(data);
|
|
582
|
+
const enforcement = principal?.trustBoundary === 'hosted' ? 'enforced' : 'deferred';
|
|
583
|
+
return textResult({
|
|
584
|
+
status: 'ok',
|
|
585
|
+
available: true,
|
|
586
|
+
enforcement,
|
|
587
|
+
deniedCount,
|
|
588
|
+
data,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function wrapDenied(principal, deniedCount) {
|
|
592
|
+
return textResult({
|
|
593
|
+
status: 'denied',
|
|
594
|
+
available: true,
|
|
595
|
+
reason: 'scope-denied',
|
|
596
|
+
deniedCount,
|
|
597
|
+
scope: {
|
|
598
|
+
tenantId: principal.tenantId,
|
|
599
|
+
workspaceId: principal.workspaceId,
|
|
600
|
+
classification: 'workspace',
|
|
601
|
+
},
|
|
602
|
+
message: 'memory-schema hits existed but none were in scope',
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
async function raceTimeout(work, timeoutMs, onTimeout) {
|
|
606
|
+
if (timeoutMs <= 0)
|
|
607
|
+
return work;
|
|
608
|
+
let timer;
|
|
609
|
+
const timeout = new Promise((resolve) => {
|
|
610
|
+
timer = setTimeout(() => resolve(onTimeout()), timeoutMs);
|
|
611
|
+
});
|
|
612
|
+
try {
|
|
613
|
+
return await Promise.race([work, timeout]);
|
|
614
|
+
}
|
|
615
|
+
finally {
|
|
616
|
+
if (timer)
|
|
617
|
+
clearTimeout(timer);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
478
620
|
/**
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
* handlers and its own lazily-resolved executor/embedder cache.
|
|
621
|
+
* Shared dispatcher for stdio and a later hosted composite. CallTool must pass
|
|
622
|
+
* `extra` so `principalProvider` can read `authInfo` / `sessionId`.
|
|
482
623
|
*/
|
|
483
|
-
export function
|
|
484
|
-
const
|
|
624
|
+
export function createKnowledgeGraphToolset(options) {
|
|
625
|
+
const mode = options?.mode ?? 'compat';
|
|
626
|
+
const trustBoundary = options?.trustBoundary ?? 'studio-local';
|
|
627
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_KG_TOOL_TIMEOUT_MS;
|
|
628
|
+
const mutatingTools = options?.mutatingTools ?? DEFAULT_MUTATING_TOOLS;
|
|
629
|
+
const tools = toolsForMode(mode);
|
|
630
|
+
const names = new Set(tools.map((tool) => tool.name));
|
|
485
631
|
const defaultSiteId = options?.siteId ?? hostname();
|
|
486
632
|
let cachedExecutor = options?.executor;
|
|
487
633
|
async function resolveExecutor() {
|
|
@@ -534,92 +680,218 @@ export function createKnowledgeGraphServer(options) {
|
|
|
534
680
|
return row ? [row] : [];
|
|
535
681
|
});
|
|
536
682
|
}
|
|
537
|
-
|
|
538
|
-
|
|
683
|
+
function dbUnavailable(err) {
|
|
684
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
685
|
+
const message = `knowledge graph database unavailable: ${detail}`;
|
|
686
|
+
return mode === 'product'
|
|
687
|
+
? productUnavailable('kg-database-unavailable', message)
|
|
688
|
+
: errorResult(message);
|
|
689
|
+
}
|
|
690
|
+
async function handleTool(request, extra) {
|
|
691
|
+
const startTime = Date.now();
|
|
539
692
|
const toolName = request.params.name;
|
|
693
|
+
const rawArgs = request.params.arguments;
|
|
694
|
+
const ctx = extraToContext(extra);
|
|
695
|
+
const mutating = mutatingTools.has(toolName);
|
|
696
|
+
async function writeReceipt(outcome, opts) {
|
|
697
|
+
if (!options?.auditSink)
|
|
698
|
+
return true;
|
|
699
|
+
const record = {
|
|
700
|
+
outcome,
|
|
701
|
+
tool: toolName,
|
|
702
|
+
argsDigest: digestArgs(rawArgs ?? null),
|
|
703
|
+
scalars: pickAuditScalars(rawArgs),
|
|
704
|
+
durationMs: Date.now() - startTime,
|
|
705
|
+
reason: opts?.reason,
|
|
706
|
+
context: ctx,
|
|
707
|
+
};
|
|
708
|
+
try {
|
|
709
|
+
await options.auditSink(record);
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (!names.has(toolName)) {
|
|
717
|
+
await writeReceipt('denied');
|
|
718
|
+
return errorResult(`Unknown tool: ${toolName}`);
|
|
719
|
+
}
|
|
720
|
+
let principal = null;
|
|
721
|
+
if (mode === 'product') {
|
|
722
|
+
try {
|
|
723
|
+
principal = (await options?.principalProvider?.(ctx)) ?? null;
|
|
724
|
+
}
|
|
725
|
+
catch {
|
|
726
|
+
principal = null;
|
|
727
|
+
}
|
|
728
|
+
const missing = validatePrincipal(principal);
|
|
729
|
+
if (missing || !principal) {
|
|
730
|
+
await writeReceipt('denied', { reason: 'principal-missing' });
|
|
731
|
+
return productUnavailable('principal-missing', missing ?? 'principal is required');
|
|
732
|
+
}
|
|
733
|
+
if (principal.trustBoundary !== trustBoundary) {
|
|
734
|
+
await writeReceipt('denied', { reason: 'principal-missing' });
|
|
735
|
+
return productUnavailable('principal-missing', 'principal trustBoundary does not match the server');
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (mutating) {
|
|
739
|
+
const recorded = await writeReceipt('invoked');
|
|
740
|
+
if (!recorded) {
|
|
741
|
+
return errorResult('audit log unavailable; mutating tool refused');
|
|
742
|
+
}
|
|
743
|
+
}
|
|
540
744
|
let exec;
|
|
541
745
|
try {
|
|
542
746
|
exec = await resolveExecutor();
|
|
543
747
|
}
|
|
544
748
|
catch (err) {
|
|
545
|
-
|
|
749
|
+
if (!mutating)
|
|
750
|
+
await writeReceipt('failed');
|
|
751
|
+
return dbUnavailable(err);
|
|
546
752
|
}
|
|
547
753
|
try {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
754
|
+
const result = await dispatchNamedTool(exec, toolName, request, principal);
|
|
755
|
+
if (!mutating) {
|
|
756
|
+
await writeReceipt(result.isError ? 'failed' : 'invoked');
|
|
757
|
+
}
|
|
758
|
+
return result;
|
|
759
|
+
}
|
|
760
|
+
catch (err) {
|
|
761
|
+
if (!mutating)
|
|
762
|
+
await writeReceipt('failed');
|
|
763
|
+
return mode === 'product'
|
|
764
|
+
? dbUnavailable(err)
|
|
765
|
+
: errorResult(err instanceof Error ? err.message : String(err));
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async function dispatchNamedTool(exec, toolName, request, principal) {
|
|
769
|
+
switch (toolName) {
|
|
770
|
+
case 'kg_search': {
|
|
771
|
+
const parsed = validateToolArgs(KgSearchArgsSchema, request.params.arguments, toolName);
|
|
772
|
+
if (!parsed.ok)
|
|
773
|
+
return parsed.error;
|
|
774
|
+
const { query, anchor, kinds, relations, at, limit, bfsDepth } = parsed.value;
|
|
775
|
+
let anchorId = anchor ? inboundKey(principal, anchor) : anchor;
|
|
776
|
+
if (anchorId) {
|
|
777
|
+
const resolved = await resolveNaturalKey(exec, anchorId);
|
|
778
|
+
if (resolved) {
|
|
779
|
+
anchorId = resolved;
|
|
561
780
|
}
|
|
562
|
-
const queryEmbedding = await tryEmbed(query);
|
|
563
|
-
const result = await kgSearch(exec, {
|
|
564
|
-
query,
|
|
565
|
-
anchor: anchorId,
|
|
566
|
-
kinds: kinds,
|
|
567
|
-
relations: relations,
|
|
568
|
-
at: at ? new Date(at) : undefined,
|
|
569
|
-
limit,
|
|
570
|
-
bfsDepth,
|
|
571
|
-
queryEmbedding,
|
|
572
|
-
});
|
|
573
|
-
return textResult(result);
|
|
574
781
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
782
|
+
const queryEmbedding = await tryEmbed(query);
|
|
783
|
+
const scoped = mode === 'product' ? (principal ?? undefined) : undefined;
|
|
784
|
+
const result = await kgSearch(exec, {
|
|
785
|
+
query,
|
|
786
|
+
anchor: anchorId,
|
|
787
|
+
kinds: kinds,
|
|
788
|
+
relations: relations,
|
|
789
|
+
at: at ? new Date(at) : undefined,
|
|
790
|
+
limit,
|
|
791
|
+
bfsDepth,
|
|
792
|
+
queryEmbedding,
|
|
793
|
+
principal: scoped,
|
|
794
|
+
});
|
|
795
|
+
const deniedCount = scoped ? await countDeniedMemoryHits(exec, query, scoped) : 0;
|
|
796
|
+
if (mode === 'product' &&
|
|
797
|
+
principal &&
|
|
798
|
+
result.nodes.length === 0 &&
|
|
799
|
+
result.facts.length === 0 &&
|
|
800
|
+
deniedCount > 0) {
|
|
801
|
+
return wrapDenied(principal, deniedCount);
|
|
802
|
+
}
|
|
803
|
+
return wrapOk(mode, result, principal, deniedCount);
|
|
804
|
+
}
|
|
805
|
+
case 'kg_get_node': {
|
|
806
|
+
const parsed = validateToolArgs(KgGetNodeArgsSchema, request.params.arguments, toolName);
|
|
807
|
+
if (!parsed.ok)
|
|
808
|
+
return parsed.error;
|
|
809
|
+
const naturalKey = inboundKey(principal, parsed.value.naturalKey);
|
|
810
|
+
const id = await resolveNaturalKey(exec, naturalKey);
|
|
811
|
+
if (!id)
|
|
812
|
+
return errorResult(`no node with natural key: ${naturalKey}`);
|
|
813
|
+
const scoped = mode === 'product' ? (principal ?? undefined) : undefined;
|
|
814
|
+
if (scoped) {
|
|
815
|
+
const visibility = await inspectNodeVisibility(exec, id, scoped);
|
|
816
|
+
if (visibility === 'missing') {
|
|
582
817
|
return errorResult(`no node with natural key: ${naturalKey}`);
|
|
818
|
+
}
|
|
583
819
|
const rows = await exec.query(`SELECT id, kind, name, natural_key, repo, summary, attributes, first_seen_at, last_confirmed_at
|
|
584
820
|
FROM kg_nodes WHERE id = $1`, [id]);
|
|
585
821
|
const node = rows[0];
|
|
586
822
|
if (!node)
|
|
587
823
|
return errorResult(`node ${id} vanished`);
|
|
588
|
-
const facts = await kgAtTime(exec, id, new Date());
|
|
589
|
-
|
|
824
|
+
const facts = await kgAtTime(exec, id, new Date(), { principal: scoped });
|
|
825
|
+
if (visibility === 'shell') {
|
|
826
|
+
return wrapOk(mode, {
|
|
827
|
+
node: {
|
|
828
|
+
id: node.id,
|
|
829
|
+
kind: node.kind,
|
|
830
|
+
name: node.name,
|
|
831
|
+
natural_key: node.natural_key,
|
|
832
|
+
},
|
|
833
|
+
facts,
|
|
834
|
+
}, principal);
|
|
835
|
+
}
|
|
836
|
+
return wrapOk(mode, { node, facts }, principal);
|
|
590
837
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
838
|
+
const rows = await exec.query(`SELECT id, kind, name, natural_key, repo, summary, attributes, first_seen_at, last_confirmed_at
|
|
839
|
+
FROM kg_nodes WHERE id = $1`, [id]);
|
|
840
|
+
const node = rows[0];
|
|
841
|
+
if (!node)
|
|
842
|
+
return errorResult(`node ${id} vanished`);
|
|
843
|
+
const facts = await kgAtTime(exec, id, new Date());
|
|
844
|
+
return wrapOk(mode, { node, facts }, principal);
|
|
845
|
+
}
|
|
846
|
+
case 'kg_neighbors': {
|
|
847
|
+
const parsed = validateToolArgs(KgNeighborsArgsSchema, request.params.arguments, toolName);
|
|
848
|
+
if (!parsed.ok)
|
|
849
|
+
return parsed.error;
|
|
850
|
+
const { depth, relations, at } = parsed.value;
|
|
851
|
+
const naturalKey = inboundKey(principal, parsed.value.naturalKey);
|
|
852
|
+
const id = await resolveNaturalKey(exec, naturalKey);
|
|
853
|
+
if (!id)
|
|
854
|
+
return errorResult(`no node with natural key: ${naturalKey}`);
|
|
855
|
+
const scoped = mode === 'product' ? (principal ?? undefined) : undefined;
|
|
856
|
+
if (scoped && (await inspectNodeVisibility(exec, id, scoped)) === 'missing') {
|
|
857
|
+
return errorResult(`no node with natural key: ${naturalKey}`);
|
|
605
858
|
}
|
|
606
|
-
|
|
607
|
-
|
|
859
|
+
const result = await kgNeighbors(exec, id, {
|
|
860
|
+
depth,
|
|
861
|
+
relations: relations,
|
|
862
|
+
at: at ? new Date(at) : undefined,
|
|
863
|
+
principal: scoped,
|
|
864
|
+
});
|
|
865
|
+
return wrapOk(mode, result, principal);
|
|
866
|
+
}
|
|
867
|
+
case 'kg_add_episode': {
|
|
868
|
+
if (mode === 'product') {
|
|
869
|
+
if (!principal) {
|
|
870
|
+
return productUnavailable('principal-missing', 'principal is required');
|
|
871
|
+
}
|
|
872
|
+
const parsed = validateToolArgs(KgProductAddEpisodeArgsSchema, request.params.arguments, toolName);
|
|
608
873
|
if (!parsed.ok)
|
|
609
874
|
return parsed.error;
|
|
610
875
|
const v = parsed.value;
|
|
876
|
+
const classification = v.classification ?? 'workspace';
|
|
611
877
|
const referenceTime = v.referenceTime ? new Date(v.referenceTime) : new Date();
|
|
612
878
|
const nodes = v.nodes.map((n) => ({
|
|
613
879
|
kind: n.kind,
|
|
614
880
|
name: n.name,
|
|
615
|
-
naturalKey: n.naturalKey,
|
|
881
|
+
naturalKey: namespaceKey(principal, n.kind, n.naturalKey),
|
|
616
882
|
repo: n.repo,
|
|
617
883
|
summary: n.summary,
|
|
618
884
|
attributes: n.attributes,
|
|
619
885
|
}));
|
|
620
886
|
const edges = v.edges.map((e) => ({
|
|
621
|
-
source:
|
|
622
|
-
|
|
887
|
+
source: {
|
|
888
|
+
kind: e.source.kind,
|
|
889
|
+
naturalKey: namespaceKey(principal, e.source.kind, e.source.naturalKey),
|
|
890
|
+
},
|
|
891
|
+
target: {
|
|
892
|
+
kind: e.target.kind,
|
|
893
|
+
naturalKey: namespaceKey(principal, e.target.kind, e.target.naturalKey),
|
|
894
|
+
},
|
|
623
895
|
relation: e.relation,
|
|
624
896
|
fact: e.fact,
|
|
625
897
|
repo: e.repo,
|
|
@@ -630,75 +902,155 @@ export function createKnowledgeGraphServer(options) {
|
|
|
630
902
|
const result = await ingestEpisode(exec, {
|
|
631
903
|
episode: {
|
|
632
904
|
episodeType: v.episodeType,
|
|
633
|
-
source:
|
|
905
|
+
source: `agent:${principal.did}`,
|
|
634
906
|
siteId: v.siteId ?? defaultSiteId,
|
|
635
907
|
content: v.content,
|
|
636
|
-
contentRef: v.contentRef,
|
|
908
|
+
contentRef: stampContentRef(principal, v.contentRef, classification),
|
|
637
909
|
referenceTime,
|
|
638
910
|
},
|
|
639
911
|
nodes,
|
|
640
912
|
edges,
|
|
641
|
-
}, { embedder, recordOutbox: true });
|
|
642
|
-
return
|
|
913
|
+
}, { embedder, recordOutbox: true, invalidateContradictions: false });
|
|
914
|
+
return wrapOk(mode, {
|
|
643
915
|
episodeId: result.episodeId,
|
|
644
916
|
nodeCount: result.nodeCount,
|
|
645
917
|
edgeCount: result.edgeCount,
|
|
646
|
-
});
|
|
918
|
+
}, principal);
|
|
647
919
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
920
|
+
const parsed = validateToolArgs(KgAddEpisodeArgsSchema, request.params.arguments, toolName);
|
|
921
|
+
if (!parsed.ok)
|
|
922
|
+
return parsed.error;
|
|
923
|
+
const v = parsed.value;
|
|
924
|
+
const referenceTime = v.referenceTime ? new Date(v.referenceTime) : new Date();
|
|
925
|
+
const nodes = v.nodes.map((n) => ({
|
|
926
|
+
kind: n.kind,
|
|
927
|
+
name: n.name,
|
|
928
|
+
naturalKey: n.naturalKey,
|
|
929
|
+
repo: n.repo,
|
|
930
|
+
summary: n.summary,
|
|
931
|
+
attributes: n.attributes,
|
|
932
|
+
}));
|
|
933
|
+
const edges = v.edges.map((e) => ({
|
|
934
|
+
source: e.source,
|
|
935
|
+
target: e.target,
|
|
936
|
+
relation: e.relation,
|
|
937
|
+
fact: e.fact,
|
|
938
|
+
repo: e.repo,
|
|
939
|
+
validAt: e.validAt ? new Date(e.validAt) : undefined,
|
|
940
|
+
attributes: e.attributes,
|
|
941
|
+
}));
|
|
942
|
+
const embedder = await resolveEmbedder();
|
|
943
|
+
const result = await ingestEpisode(exec, {
|
|
944
|
+
episode: {
|
|
945
|
+
episodeType: v.episodeType,
|
|
946
|
+
source: v.source,
|
|
947
|
+
siteId: v.siteId ?? defaultSiteId,
|
|
948
|
+
content: v.content,
|
|
949
|
+
contentRef: v.contentRef,
|
|
950
|
+
referenceTime,
|
|
951
|
+
},
|
|
952
|
+
nodes,
|
|
953
|
+
edges,
|
|
954
|
+
}, { embedder, recordOutbox: true });
|
|
955
|
+
return wrapOk(mode, {
|
|
956
|
+
episodeId: result.episodeId,
|
|
957
|
+
nodeCount: result.nodeCount,
|
|
958
|
+
edgeCount: result.edgeCount,
|
|
959
|
+
}, principal);
|
|
960
|
+
}
|
|
961
|
+
case 'kg_path': {
|
|
962
|
+
const parsed = validateToolArgs(KgPathArgsSchema, request.params.arguments, toolName);
|
|
963
|
+
if (!parsed.ok)
|
|
964
|
+
return parsed.error;
|
|
965
|
+
const { at, maxDepth } = parsed.value;
|
|
966
|
+
const fromNaturalKey = inboundKey(principal, parsed.value.fromNaturalKey);
|
|
967
|
+
const toNaturalKey = inboundKey(principal, parsed.value.toNaturalKey);
|
|
968
|
+
const fromId = await resolveNaturalKey(exec, fromNaturalKey);
|
|
969
|
+
if (!fromId)
|
|
970
|
+
return errorResult(`no node with natural key: ${fromNaturalKey}`);
|
|
971
|
+
const toId = await resolveNaturalKey(exec, toNaturalKey);
|
|
972
|
+
if (!toId)
|
|
973
|
+
return errorResult(`no node with natural key: ${toNaturalKey}`);
|
|
974
|
+
const scoped = mode === 'product' ? (principal ?? undefined) : undefined;
|
|
975
|
+
if (scoped) {
|
|
976
|
+
if ((await inspectNodeVisibility(exec, fromId, scoped)) === 'missing') {
|
|
655
977
|
return errorResult(`no node with natural key: ${fromNaturalKey}`);
|
|
656
|
-
|
|
657
|
-
if (
|
|
978
|
+
}
|
|
979
|
+
if ((await inspectNodeVisibility(exec, toId, scoped)) === 'missing') {
|
|
658
980
|
return errorResult(`no node with natural key: ${toNaturalKey}`);
|
|
659
|
-
|
|
660
|
-
at: at ? new Date(at) : undefined,
|
|
661
|
-
maxDepth,
|
|
662
|
-
});
|
|
663
|
-
if (!path)
|
|
664
|
-
return textResult({ path: null });
|
|
665
|
-
const detail = await hydratePath(exec, path);
|
|
666
|
-
return textResult({ path: detail });
|
|
981
|
+
}
|
|
667
982
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
983
|
+
const path = await kgPath(exec, fromId, toId, {
|
|
984
|
+
at: at ? new Date(at) : undefined,
|
|
985
|
+
maxDepth,
|
|
986
|
+
principal: scoped,
|
|
987
|
+
});
|
|
988
|
+
if (!path)
|
|
989
|
+
return wrapOk(mode, { path: null }, principal);
|
|
990
|
+
const detail = await hydratePath(exec, path);
|
|
991
|
+
return wrapOk(mode, { path: detail }, principal);
|
|
992
|
+
}
|
|
993
|
+
case 'kg_at_time': {
|
|
994
|
+
const parsed = validateToolArgs(KgAtTimeArgsSchema, request.params.arguments, toolName);
|
|
995
|
+
if (!parsed.ok)
|
|
996
|
+
return parsed.error;
|
|
997
|
+
const { at } = parsed.value;
|
|
998
|
+
const naturalKey = inboundKey(principal, parsed.value.naturalKey);
|
|
999
|
+
const id = await resolveNaturalKey(exec, naturalKey);
|
|
1000
|
+
if (!id)
|
|
1001
|
+
return errorResult(`no node with natural key: ${naturalKey}`);
|
|
1002
|
+
const scoped = mode === 'product' ? (principal ?? undefined) : undefined;
|
|
1003
|
+
if (scoped && (await inspectNodeVisibility(exec, id, scoped)) === 'missing') {
|
|
1004
|
+
return errorResult(`no node with natural key: ${naturalKey}`);
|
|
678
1005
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
1006
|
+
const facts = await kgAtTime(exec, id, new Date(at), { principal: scoped });
|
|
1007
|
+
return wrapOk(mode, { facts }, principal);
|
|
1008
|
+
}
|
|
1009
|
+
case 'kg_context': {
|
|
1010
|
+
const parsed = validateToolArgs(KgContextArgsSchema, request.params.arguments, toolName);
|
|
1011
|
+
if (!parsed.ok)
|
|
1012
|
+
return parsed.error;
|
|
1013
|
+
const { charBudget, depth, at } = parsed.value;
|
|
1014
|
+
const naturalKey = inboundKey(principal, parsed.value.naturalKey);
|
|
1015
|
+
const id = await resolveNaturalKey(exec, naturalKey);
|
|
1016
|
+
if (!id)
|
|
1017
|
+
return errorResult(`no node with natural key: ${naturalKey}`);
|
|
1018
|
+
const scoped = mode === 'product' ? (principal ?? undefined) : undefined;
|
|
1019
|
+
if (scoped && (await inspectNodeVisibility(exec, id, scoped)) === 'missing') {
|
|
1020
|
+
return errorResult(`no node with natural key: ${naturalKey}`);
|
|
693
1021
|
}
|
|
694
|
-
|
|
695
|
-
|
|
1022
|
+
const assembled = await assembleContext(exec, id, {
|
|
1023
|
+
charBudget: charBudget ?? DEFAULT_CONTEXT_CHAR_BUDGET,
|
|
1024
|
+
depth: depth ?? 3,
|
|
1025
|
+
at: at ? new Date(at) : undefined,
|
|
1026
|
+
principal: scoped,
|
|
1027
|
+
});
|
|
1028
|
+
return wrapOk(mode, assembled, principal);
|
|
696
1029
|
}
|
|
1030
|
+
default:
|
|
1031
|
+
return errorResult(`Unknown tool: ${toolName}`);
|
|
697
1032
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
1035
|
+
tools,
|
|
1036
|
+
names,
|
|
1037
|
+
async dispatch(request, extra) {
|
|
1038
|
+
return raceTimeout(handleTool(request, extra), timeoutMs, () => mode === 'product'
|
|
1039
|
+
? productUnavailable('timeout', 'knowledge graph tool timed out')
|
|
1040
|
+
: errorResult('knowledge graph tool timed out'));
|
|
1041
|
+
},
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Create a fresh `knowledge-graph` MCP Server instance. Safe to call multiple
|
|
1046
|
+
* times — each call returns an independent Server with its own request
|
|
1047
|
+
* handlers and its own lazily-resolved executor/embedder cache.
|
|
1048
|
+
*/
|
|
1049
|
+
export function createKnowledgeGraphServer(options) {
|
|
1050
|
+
const toolset = createKnowledgeGraphToolset(options);
|
|
1051
|
+
const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
|
|
1052
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: toolset.tools }));
|
|
1053
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => toolset.dispatch(request, extra));
|
|
702
1054
|
return server;
|
|
703
1055
|
}
|
|
704
1056
|
//# sourceMappingURL=knowledge-graph.js.map
|