@littlebigbrain/mcp 0.2.5 → 0.2.7
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 +5 -6
- package/dist/tool-contracts.js +5 -172
- package/dist/tool-runtime.js +6 -175
- package/dist/tools.js +70 -175
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @littlebigbrain/mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Ten task-shaped [MCP](https://modelcontextprotocol.io) tools that let Claude, Cursor, Codex, or any MCP client search, query, and write a [Little Big Brain](https://littlebigbrain.com) graph. Ships two ways: a hosted endpoint with OAuth sign-in, and a local stdio server.
|
|
4
4
|
|
|
5
5
|
## Hosted (OAuth) — recommended
|
|
6
6
|
|
|
@@ -59,16 +59,15 @@ missing.
|
|
|
59
59
|
| Tool | Use it for |
|
|
60
60
|
| --- | --- |
|
|
61
61
|
| `lbb_search` | hybrid retrieval, multi-query fusion, optional path following |
|
|
62
|
-
| `lbb_ask` | grounded answers with citations |
|
|
63
62
|
| `lbb_decode` | constrained relation decoding |
|
|
64
63
|
| `lbb_ground` | vocabulary completion and resolution |
|
|
65
|
-
| `lbb_inspect` | ontology, entity, state, history, provenance, traversal |
|
|
66
|
-
| `lbb_query` | SPARQL
|
|
64
|
+
| `lbb_inspect` | ontology, schema, entity, state, history, provenance, traversal |
|
|
65
|
+
| `lbb_query` | SPARQL and structured analytics |
|
|
67
66
|
| `lbb_commit` | facts, properties, and embeddings |
|
|
68
67
|
| `lbb_observe` | conversation episodes plus reviewed extraction |
|
|
69
68
|
| `lbb_branch` | isolation branches and validated merge |
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
69
|
+
| `lbb_models` | shadow evaluation and training datasets |
|
|
70
|
+
| `lbb_configure` | ontology definition and atomic schema publication |
|
|
72
71
|
|
|
73
72
|
Read tools return compact structured envelopes by default — use `detail`, `row_limit`, and returned cursors to page without silently truncating. Write tools derive an idempotency key unless you provide one.
|
|
74
73
|
|
package/dist/tool-contracts.js
CHANGED
|
@@ -42,11 +42,6 @@ export const graphScope = {
|
|
|
42
42
|
export const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
43
43
|
export const jsonObjectArraySchema = z.array(jsonObjectSchema);
|
|
44
44
|
export const readScope = { detail: detailSchema, ...graphScope };
|
|
45
|
-
// Typed shape for inference rules (define_rules / infer / shacl include_derived).
|
|
46
|
-
// Advertising the structure is what makes a rule's full power discoverable: a
|
|
47
|
-
// term can be a fixed entity (not just a variable), so a rule can match or derive
|
|
48
|
-
// a constant value (e.g. a status); and `not_exists` adds stratified negation, so
|
|
49
|
-
// a rule can express a universal condition like "all children complete".
|
|
50
45
|
export const entitySelectorSchema = z
|
|
51
46
|
.object({
|
|
52
47
|
entity_type: z
|
|
@@ -112,54 +107,6 @@ export const searchFeedbackSchema = z
|
|
|
112
107
|
.min(1),
|
|
113
108
|
})
|
|
114
109
|
.passthrough();
|
|
115
|
-
export const ruleTermSchema = z
|
|
116
|
-
.union([
|
|
117
|
-
z
|
|
118
|
-
.object({
|
|
119
|
-
var: z.string().describe("A variable, joined across patterns by name"),
|
|
120
|
-
})
|
|
121
|
-
.passthrough(),
|
|
122
|
-
z.object({ entity: entitySelectorSchema }).passthrough(),
|
|
123
|
-
])
|
|
124
|
-
.describe('A rule term: { "var": "x" } (a variable) or { "entity": { "entity_type": "DeliveryStatus", "name": "Complete" } } (a fixed entity used as a constant in the body or head)');
|
|
125
|
-
export const ruleTriplePatternSchema = z
|
|
126
|
-
.object({
|
|
127
|
-
subject: ruleTermSchema.optional(),
|
|
128
|
-
predicate: z
|
|
129
|
-
.string()
|
|
130
|
-
.optional()
|
|
131
|
-
.describe('Relation name. In a rule body, the reserved "rdf:type" makes a type-membership constraint: the object names a class ({ entity: { entity_type: "Contact" } }, no name) and matches every entity of that class and its subtypes (rdfs:subClassOf closure), so one rule keyed on a supertype fires for all subtypes. Not allowed in a head or an exists/not_exists filter.'),
|
|
132
|
-
object: ruleTermSchema.optional(),
|
|
133
|
-
})
|
|
134
|
-
.passthrough();
|
|
135
|
-
export const ruleCombinatorSchema = z
|
|
136
|
-
.union([
|
|
137
|
-
z.object({ exists: z.array(ruleTriplePatternSchema) }).passthrough(),
|
|
138
|
-
z.object({ not_exists: z.array(ruleTriplePatternSchema) }).passthrough(),
|
|
139
|
-
])
|
|
140
|
-
.describe("An existence filter over the body solutions: { exists: [...] } (semijoin — keep rows with a compatible match) or { not_exists: [...] } (negation/antijoin — keep rows with none)");
|
|
141
|
-
export const inferenceRuleSchema = z
|
|
142
|
-
.object({
|
|
143
|
-
name: z.string(),
|
|
144
|
-
order: z
|
|
145
|
-
.number()
|
|
146
|
-
.int()
|
|
147
|
-
.optional()
|
|
148
|
-
.describe("Run order, low to high (a determinism hint)"),
|
|
149
|
-
body: z
|
|
150
|
-
.array(ruleTriplePatternSchema)
|
|
151
|
-
.optional()
|
|
152
|
-
.describe("The condition: a basic graph pattern over current edges (asserted + already-derived), joined on shared variables. Terms may be variables or fixed entities."),
|
|
153
|
-
combinators: z
|
|
154
|
-
.array(ruleCombinatorSchema)
|
|
155
|
-
.optional()
|
|
156
|
-
.describe('exists/not_exists filters folded over the body. not_exists is stratified negation — it lets a rule express a universal condition, e.g. derive "phase complete" only when not_exists an incomplete deliverable. A negation cycle is rejected.'),
|
|
157
|
-
head: ruleTriplePatternSchema
|
|
158
|
-
.optional()
|
|
159
|
-
.describe("The triple derived once per body solution. Every head variable must be bound by the body; a fixed-entity object derives a constant (e.g. set the rolled-up status to the Complete entity)."),
|
|
160
|
-
})
|
|
161
|
-
.passthrough();
|
|
162
|
-
export const inferenceRuleArraySchema = z.array(inferenceRuleSchema);
|
|
163
110
|
export const ontologyFormatSchema = z.enum([
|
|
164
111
|
"auto",
|
|
165
112
|
"turtle",
|
|
@@ -189,7 +136,7 @@ export const shapeSourceSchema = z
|
|
|
189
136
|
format: shapeFormatSchema.optional(),
|
|
190
137
|
})
|
|
191
138
|
.strict();
|
|
192
|
-
export const schemaModeSchema = z.enum(["warn", "reject"]);
|
|
139
|
+
export const schemaModeSchema = z.enum(["off", "warn", "reject"]);
|
|
193
140
|
export const ontologyEvolveOpSchema = z.discriminatedUnion("op", [
|
|
194
141
|
z
|
|
195
142
|
.object({
|
|
@@ -342,19 +289,6 @@ export const inspectInputSchema = z.discriminatedUnion("action", [
|
|
|
342
289
|
.object({ action: z.literal("ontology_conformance"), ...readScope })
|
|
343
290
|
.strict(),
|
|
344
291
|
z.object({ action: z.literal("schema"), ...readScope }).strict(),
|
|
345
|
-
z.object({ action: z.literal("schema_audit"), ...readScope }).strict(),
|
|
346
|
-
z.object({ action: z.literal("rules"), ...readScope }).strict(),
|
|
347
|
-
z
|
|
348
|
-
.object({
|
|
349
|
-
action: z.literal("schema_preview"),
|
|
350
|
-
ontology: ontologySourceSchema.optional(),
|
|
351
|
-
shapes: shapeSourceSchema.optional(),
|
|
352
|
-
base_ontology_version: z.number().int().nonnegative().optional(),
|
|
353
|
-
base_shapes_version: z.number().int().nonnegative().optional(),
|
|
354
|
-
desired_mode: schemaModeSchema.optional(),
|
|
355
|
-
...readScope,
|
|
356
|
-
})
|
|
357
|
-
.strict(),
|
|
358
292
|
z
|
|
359
293
|
.object({
|
|
360
294
|
action: z.literal("ontology_search"),
|
|
@@ -388,52 +322,6 @@ export const inspectInputSchema = z.discriminatedUnion("action", [
|
|
|
388
322
|
...readScope,
|
|
389
323
|
})
|
|
390
324
|
.strict(),
|
|
391
|
-
z
|
|
392
|
-
.object({
|
|
393
|
-
action: z.literal("edges"),
|
|
394
|
-
entity_id: z
|
|
395
|
-
.string()
|
|
396
|
-
.optional()
|
|
397
|
-
.describe("Entity id (hex); alternative to entity_type+name"),
|
|
398
|
-
entity_type: z.string().optional(),
|
|
399
|
-
name: z.string().optional(),
|
|
400
|
-
direction: z
|
|
401
|
-
.enum(["out", "in", "both"])
|
|
402
|
-
.optional()
|
|
403
|
-
.describe("Edges out of / into / touching the entity (default both)."),
|
|
404
|
-
relation: z
|
|
405
|
-
.string()
|
|
406
|
-
.optional()
|
|
407
|
-
.describe("Filter to a single relation name."),
|
|
408
|
-
row_limit: z
|
|
409
|
-
.number()
|
|
410
|
-
.int()
|
|
411
|
-
.positive()
|
|
412
|
-
.optional()
|
|
413
|
-
.describe("Max edges returned this page (server caps at 1000; default 150)."),
|
|
414
|
-
cursor: z
|
|
415
|
-
.string()
|
|
416
|
-
.optional()
|
|
417
|
-
.describe("Opaque cursor from the previous page's `next_cursor`. The response is the unified list envelope { object:'list', data, has_more, next_cursor, total_count }; page a high-degree node (entity detail hard-caps its edge sample) by feeding next_cursor back here until has_more is false. Each row carries valid_time for a per-edge timeline."),
|
|
418
|
-
offset: z
|
|
419
|
-
.number()
|
|
420
|
-
.int()
|
|
421
|
-
.nonnegative()
|
|
422
|
-
.optional()
|
|
423
|
-
.describe("Legacy alias for `cursor` (still accepted); prefer paging with `cursor`."),
|
|
424
|
-
as_of: z
|
|
425
|
-
.string()
|
|
426
|
-
.optional()
|
|
427
|
-
.describe("Valid-time snapshot pin (RFC3339)."),
|
|
428
|
-
as_of_commit_seq: z
|
|
429
|
-
.number()
|
|
430
|
-
.int()
|
|
431
|
-
.nonnegative()
|
|
432
|
-
.optional()
|
|
433
|
-
.describe("Snapshot pin: list edges as of this commit_seq."),
|
|
434
|
-
...readScope,
|
|
435
|
-
})
|
|
436
|
-
.strict(),
|
|
437
325
|
z
|
|
438
326
|
.object({
|
|
439
327
|
action: z.literal("state"),
|
|
@@ -549,52 +437,6 @@ export const queryInputSchema = z.discriminatedUnion("mode", [
|
|
|
549
437
|
...readScope,
|
|
550
438
|
})
|
|
551
439
|
.strict(),
|
|
552
|
-
z
|
|
553
|
-
.object({
|
|
554
|
-
mode: z.literal("shacl"),
|
|
555
|
-
shacl_mode: z
|
|
556
|
-
.enum(["select", "validate"])
|
|
557
|
-
.optional()
|
|
558
|
-
.describe("select returns focus nodes; validate returns a report"),
|
|
559
|
-
shapes: jsonObjectArraySchema.optional(),
|
|
560
|
-
rules: inferenceRuleArraySchema.optional(),
|
|
561
|
-
include_derived: z.boolean().optional(),
|
|
562
|
-
as_of: z.string().optional(),
|
|
563
|
-
explain: z.boolean().optional(),
|
|
564
|
-
top_k: z.number().int().positive().optional(),
|
|
565
|
-
...readScope,
|
|
566
|
-
})
|
|
567
|
-
.strict(),
|
|
568
|
-
z
|
|
569
|
-
.object({
|
|
570
|
-
mode: z.literal("infer"),
|
|
571
|
-
rules: inferenceRuleArraySchema.optional(),
|
|
572
|
-
as_of: z.string().optional(),
|
|
573
|
-
as_of_commit_seq: z
|
|
574
|
-
.number()
|
|
575
|
-
.int()
|
|
576
|
-
.nonnegative()
|
|
577
|
-
.optional()
|
|
578
|
-
.describe("Snapshot pin: run inference as of this commit_seq. Errors if past head."),
|
|
579
|
-
max_rounds: z.number().int().positive().optional(),
|
|
580
|
-
max_derived: z.number().int().positive().optional(),
|
|
581
|
-
max_solutions: z.number().int().positive().optional(),
|
|
582
|
-
...readScope,
|
|
583
|
-
})
|
|
584
|
-
.strict(),
|
|
585
|
-
z
|
|
586
|
-
.object({
|
|
587
|
-
mode: z.literal("retrieval_premises"),
|
|
588
|
-
anchor_type: z.string(),
|
|
589
|
-
anchor_name: z.string(),
|
|
590
|
-
relation: z.string(),
|
|
591
|
-
query: z.string(),
|
|
592
|
-
threshold: z.number().min(0).max(1).optional(),
|
|
593
|
-
max_premises: z.number().int().positive().optional(),
|
|
594
|
-
query_top_k: z.number().int().positive().optional(),
|
|
595
|
-
...readScope,
|
|
596
|
-
})
|
|
597
|
-
.strict(),
|
|
598
440
|
z
|
|
599
441
|
.object({
|
|
600
442
|
mode: z.literal("analyze"),
|
|
@@ -623,21 +465,12 @@ export const configureInputSchema = z.discriminatedUnion("action", [
|
|
|
623
465
|
merge_default: z.boolean().optional(),
|
|
624
466
|
})
|
|
625
467
|
.strict(),
|
|
626
|
-
z
|
|
627
|
-
.object({
|
|
628
|
-
action: z.literal("define_rules"),
|
|
629
|
-
rules: inferenceRuleArraySchema,
|
|
630
|
-
confirm_empty: z.boolean().optional(),
|
|
631
|
-
...graphScope,
|
|
632
|
-
})
|
|
633
|
-
.strict(),
|
|
634
468
|
z
|
|
635
469
|
.object({
|
|
636
470
|
action: z.literal("publish_schema"),
|
|
637
|
-
preview_digest: z.string(),
|
|
638
|
-
shapes: shapeSourceSchema,
|
|
639
471
|
ontology: ontologySourceSchema.optional(),
|
|
640
|
-
|
|
472
|
+
shapes: shapeSourceSchema.optional(),
|
|
473
|
+
desired_mode: schemaModeSchema.optional(),
|
|
641
474
|
confirm_restrictive: z.boolean().optional(),
|
|
642
475
|
...graphScope,
|
|
643
476
|
})
|
|
@@ -662,8 +495,8 @@ export const configureInputSchema = z.discriminatedUnion("action", [
|
|
|
662
495
|
* schema is a ZodObject (the SDK reads `.shape` via `normalizeObjectSchema`). A
|
|
663
496
|
* `z.discriminatedUnion` has `.options`, not `.shape`, so the SDK silently falls
|
|
664
497
|
* back to an empty `{ type: "object", properties: {} }` advertisement — and
|
|
665
|
-
* clients then stringify every object-valued argument (the
|
|
666
|
-
*
|
|
498
|
+
* clients then stringify every object-valued argument (for example, the
|
|
499
|
+
* structured-query `body`), which the server
|
|
667
500
|
* rejects as `Expected object, received string`.
|
|
668
501
|
*
|
|
669
502
|
* Flatten the union into a single ZodObject purely for advertisement and
|
package/dist/tool-runtime.js
CHANGED
|
@@ -417,55 +417,6 @@ export function toolResult(value) {
|
|
|
417
417
|
structuredContent: value,
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
|
-
// `lbb_inspect action=entity` returns the full incoming/outgoing/history arrays,
|
|
421
|
-
// but the display truncates them to the detail cap (compact shows 5) while
|
|
422
|
-
// `counts` still reports the true totals — so a high-degree node reads as "5
|
|
423
|
-
// edges" unless the caller already knows to switch to the paged `edges`/`history`
|
|
424
|
-
// reads. Make that self-documenting: when a sample is capped, attach the true
|
|
425
|
-
// counts plus ready-to-run paged reads so the workaround is discoverable in the
|
|
426
|
-
// response instead of tribal knowledge.
|
|
427
|
-
export function entityEdgeCapHint(data, detail, identity) {
|
|
428
|
-
if (!data || typeof data !== "object")
|
|
429
|
-
return {};
|
|
430
|
-
const record = data;
|
|
431
|
-
const cap = compactLimits(normalizeDetail(detail)).maxItems;
|
|
432
|
-
const lengthOf = (field) => {
|
|
433
|
-
const value = record[field];
|
|
434
|
-
return Array.isArray(value) ? value.length : 0;
|
|
435
|
-
};
|
|
436
|
-
const capped = {};
|
|
437
|
-
const fullReads = [];
|
|
438
|
-
const edgeReads = [
|
|
439
|
-
{ field: "incoming", direction: "in" },
|
|
440
|
-
{ field: "outgoing", direction: "out" },
|
|
441
|
-
];
|
|
442
|
-
for (const { field, direction } of edgeReads) {
|
|
443
|
-
const total = lengthOf(field);
|
|
444
|
-
if (total > cap) {
|
|
445
|
-
capped[field] = total;
|
|
446
|
-
fullReads.push({
|
|
447
|
-
tool: "lbb_inspect",
|
|
448
|
-
arguments: { action: "edges", direction, ...identity },
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
if (lengthOf("history") > cap) {
|
|
453
|
-
capped.history = lengthOf("history");
|
|
454
|
-
fullReads.push({
|
|
455
|
-
tool: "lbb_inspect",
|
|
456
|
-
arguments: { action: "history", ...identity },
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
if (Object.keys(capped).length === 0)
|
|
460
|
-
return {};
|
|
461
|
-
return {
|
|
462
|
-
edge_sample: {
|
|
463
|
-
note: `This node's edge/history arrays are a display sample capped at ${cap} per field; counts holds the true totals. Read the full set with these paged reads (cursor through them until has_more=false).`,
|
|
464
|
-
capped_totals: capped,
|
|
465
|
-
full_reads: fullReads,
|
|
466
|
-
},
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
420
|
export function errorResult(error) {
|
|
470
421
|
const payload = error instanceof LbbError
|
|
471
422
|
? {
|
|
@@ -635,8 +586,6 @@ export function searchBody(p) {
|
|
|
635
586
|
lexical: mode === "hybrid" || mode === "lexical",
|
|
636
587
|
bm25: mode === "hybrid" || mode === "bm25",
|
|
637
588
|
vector: mode === "hybrid" || mode === "vector",
|
|
638
|
-
bm25_source: "persisted",
|
|
639
|
-
vector_source: "persisted",
|
|
640
589
|
consistency: "strong",
|
|
641
590
|
profile: resolveProfile(p.profile),
|
|
642
591
|
},
|
|
@@ -706,7 +655,7 @@ export function sparqlKeyLabel(value) {
|
|
|
706
655
|
}
|
|
707
656
|
return String(value);
|
|
708
657
|
}
|
|
709
|
-
export function buildPossibilities(relations
|
|
658
|
+
export function buildPossibilities(relations) {
|
|
710
659
|
const possibilities = [
|
|
711
660
|
{
|
|
712
661
|
name: "Entity composition",
|
|
@@ -789,26 +738,6 @@ export function buildPossibilities(relations, entityTypes = []) {
|
|
|
789
738
|
},
|
|
790
739
|
});
|
|
791
740
|
}
|
|
792
|
-
const topType = entityTypes[0]?.name;
|
|
793
|
-
if (top && topType) {
|
|
794
|
-
possibilities.push({
|
|
795
|
-
name: `${topType} nodes that participate in ${top}`,
|
|
796
|
-
description: `Select every ${topType} that is the source of at least one ${top} edge.`,
|
|
797
|
-
chart: "bar",
|
|
798
|
-
run: {
|
|
799
|
-
tool: "lbb_query",
|
|
800
|
-
args: {
|
|
801
|
-
mode: "shacl",
|
|
802
|
-
shapes: [
|
|
803
|
-
{
|
|
804
|
-
targetClass: topType,
|
|
805
|
-
property: [{ path: top, min_count: 1, bind: "targets" }],
|
|
806
|
-
},
|
|
807
|
-
],
|
|
808
|
-
},
|
|
809
|
-
},
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
741
|
return possibilities;
|
|
813
742
|
}
|
|
814
743
|
export async function guide(scopedClient) {
|
|
@@ -827,15 +756,14 @@ export async function guide(scopedClient) {
|
|
|
827
756
|
capability: {
|
|
828
757
|
search: "Use lbb_search for natural-language retrieval, optional multi-query fusion, and optional path following.",
|
|
829
758
|
search_feedback: "After a lbb_search result set is useful or clearly wrong, write relevance labels with lbb_commit mode=search_feedback. Use grade 3 for ideal/good results, grade 1 for partially relevant results, and grade 0 for bad results. Include the original query, search_id when present, target identity, rank, score, and an optional split train/eval/unspecified. These labels are stored in __lbb_feedback/main and later exported as qrels-style training/eval data; they are not customer facts.",
|
|
830
|
-
inspect: "Use lbb_inspect for ontology,
|
|
759
|
+
inspect: "Use lbb_inspect for ontology, schema metadata, the published conformance report, metadata, state/history/why, exact traversals, and this guide.",
|
|
831
760
|
ontology_decorations: "lbb_inspect action=ontology returns a decoration_status catalog: each ontology decoration is enforced (the engine acts on it — state_reducer, value_type, super_types, properties, supernode_policy; cardinality, which GET /v1/ontology/conformance audits as sh:maxCount; and inverse_name/symmetric, which SPARQL resolves as relation aliases — an inverse name is queryable directly (lowered to ^forward, no stored inverse triple) and a symmetric relation matches both directions), advisory (transitive, temporal_semantics, required), or reserved (stored but unwired — default_weight, resolvable, alias/embedding_fields). You can also always reverse any relation in SPARQL by flipping the triple pattern or using ^forward. Each relation_def also carries edge_count — the number of current edges of that relation in this branch's snapshot — so you can tell at a glance which declared relations are actually populated (edge_count 0 = declared but unused) without a separate summary call.",
|
|
832
|
-
query:
|
|
761
|
+
query: "Use lbb_query for structured SPARQL-subset bodies, SPARQL text, and canned analysis. A mode=structured body is { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having? }; a pattern `predicate` is a relation name and is case-insensitive. mode=structured GROUP BY is not limited to entity identity: group_keys can key on a typed scalar property or calendar bucket. In SPARQL text, relations are <https://littlebigbrain.com/r/NAME> and types <https://littlebigbrain.com/class/NAME>. Each query pins one published watermark.",
|
|
833
762
|
write: "Use lbb_commit for fact writes and search relevance feedback; omitted idempotency keys are content-derived so retries dedupe. Set typed scalar attributes via entity_properties once the field is registered (add it on a live graph with lbb_configure evolve_ontology add_property). For feedback, use mode=search_feedback rather than fact triplets.",
|
|
834
|
-
configure: "Use lbb_configure to define a new ontology, evolve an existing one in place
|
|
835
|
-
inference: "Rule body/head terms are { var } or { entity: { entity_type, name } } — a fixed entity lets a rule match or derive a constant value (e.g. a status). A not_exists combinator adds stratified negation for universal conditions. Roll-up example: rule 1 head { var: phase } HAS_INCOMPLETE_DELIVERABLE { var: d }, body phase HAS_DELIVERABLE d, not_exists [ d HAS_DELIVERY_STATUS { entity: DeliveryStatus/Complete } ]; rule 2 head phase HAS_ROLLUP_STATUS { entity: DeliveryStatus/Complete }, body phase HAS_DELIVERABLE any, not_exists [ phase HAS_INCOMPLETE_DELIVERABLE x ] — derives complete only when every deliverable is complete.",
|
|
763
|
+
configure: "Use lbb_configure to define a new ontology, evolve an existing one in place, or atomically publish ontology/SHACL bundle metadata. Conformance validation runs durably after publication.",
|
|
836
764
|
},
|
|
837
|
-
possibilities: buildPossibilities(relations
|
|
838
|
-
how_to: "Ground with lbb_inspect action=guide, retrieve with lbb_search, rate useful/partial/bad retrieval results
|
|
765
|
+
possibilities: buildPossibilities(relations),
|
|
766
|
+
how_to: "Ground with lbb_inspect action=guide, retrieve with lbb_search, rate useful/partial/bad retrieval results when you have a judgment, inspect exact entities/schema with lbb_inspect, run analysis with lbb_query, then write graph facts with lbb_commit or configuration with lbb_configure only when intended.",
|
|
839
767
|
};
|
|
840
768
|
}
|
|
841
769
|
export async function analyze(scopedClient, p) {
|
|
@@ -885,8 +813,6 @@ export async function analyze(scopedClient, p) {
|
|
|
885
813
|
lexical: true,
|
|
886
814
|
bm25: true,
|
|
887
815
|
vector: true,
|
|
888
|
-
bm25_source: "persisted",
|
|
889
|
-
vector_source: "persisted",
|
|
890
816
|
consistency: "strong",
|
|
891
817
|
},
|
|
892
818
|
facets: [{ field }],
|
|
@@ -969,101 +895,6 @@ export function ontologyDefineBody(p) {
|
|
|
969
895
|
merge_default: p.merge_default ?? false,
|
|
970
896
|
};
|
|
971
897
|
}
|
|
972
|
-
export function schemaSourceBody(source) {
|
|
973
|
-
if (source === undefined)
|
|
974
|
-
return undefined;
|
|
975
|
-
return {
|
|
976
|
-
source: source.source,
|
|
977
|
-
format: source.format ?? "auto",
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
export function schemaPreviewBody(p) {
|
|
981
|
-
const ontology = schemaSourceBody(p.ontology);
|
|
982
|
-
const shapes = schemaSourceBody(p.shapes);
|
|
983
|
-
if (ontology === undefined && shapes === undefined) {
|
|
984
|
-
throw new Error("schema_preview requires an ontology or shapes source");
|
|
985
|
-
}
|
|
986
|
-
return {
|
|
987
|
-
ontology,
|
|
988
|
-
shapes,
|
|
989
|
-
base_ontology_version: p.base_ontology_version ?? null,
|
|
990
|
-
base_shapes_version: p.base_shapes_version ?? null,
|
|
991
|
-
desired_mode: p.desired_mode ?? "warn",
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
export function schemaPublishBody(p) {
|
|
995
|
-
return {
|
|
996
|
-
preview_digest: p.preview_digest,
|
|
997
|
-
ontology: schemaSourceBody(p.ontology),
|
|
998
|
-
shapes: schemaSourceBody(p.shapes),
|
|
999
|
-
desired_mode: p.desired_mode,
|
|
1000
|
-
confirm_restrictive: p.confirm_restrictive ?? false,
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
export function choosePublishMode(response, requested) {
|
|
1004
|
-
const modes = Array.isArray(response.publish_mode_allowed)
|
|
1005
|
-
? response.publish_mode_allowed
|
|
1006
|
-
: [];
|
|
1007
|
-
if (modes.includes(requested))
|
|
1008
|
-
return requested === "reject" ? "reject" : "warn";
|
|
1009
|
-
if (modes.includes("warn"))
|
|
1010
|
-
return "warn";
|
|
1011
|
-
if (modes.includes("reject"))
|
|
1012
|
-
return "reject";
|
|
1013
|
-
return undefined;
|
|
1014
|
-
}
|
|
1015
|
-
export function auditSummary(audit) {
|
|
1016
|
-
if (!audit || typeof audit !== "object")
|
|
1017
|
-
return undefined;
|
|
1018
|
-
const a = audit;
|
|
1019
|
-
return {
|
|
1020
|
-
conforms: a.conforms,
|
|
1021
|
-
result_count: a.result_count,
|
|
1022
|
-
messages: a.messages,
|
|
1023
|
-
sample_results: Array.isArray(a.results)
|
|
1024
|
-
? a.results.slice(0, 5)
|
|
1025
|
-
: undefined,
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
export async function schemaPreview(target, p) {
|
|
1029
|
-
const body = schemaPreviewBody(p);
|
|
1030
|
-
const response = (await target.schema.preview(body));
|
|
1031
|
-
const desiredMode = typeof response.desired_mode === "string"
|
|
1032
|
-
? response.desired_mode
|
|
1033
|
-
: String(body.desired_mode);
|
|
1034
|
-
const publishMode = choosePublishMode(response, desiredMode);
|
|
1035
|
-
const suggestedPublish = publishMode && body.shapes
|
|
1036
|
-
? {
|
|
1037
|
-
tool: "lbb_configure",
|
|
1038
|
-
args: {
|
|
1039
|
-
action: "publish_schema",
|
|
1040
|
-
graph: p.graph,
|
|
1041
|
-
branch: p.branch,
|
|
1042
|
-
preview_digest: response.preview_digest,
|
|
1043
|
-
desired_mode: publishMode,
|
|
1044
|
-
confirm_restrictive: response.verdict === "restrictive" && publishMode === "warn"
|
|
1045
|
-
? true
|
|
1046
|
-
: undefined,
|
|
1047
|
-
ontology: body.ontology,
|
|
1048
|
-
shapes: body.shapes,
|
|
1049
|
-
},
|
|
1050
|
-
}
|
|
1051
|
-
: undefined;
|
|
1052
|
-
return {
|
|
1053
|
-
graph: response.graph,
|
|
1054
|
-
verdict: response.verdict,
|
|
1055
|
-
can_publish: response.can_publish,
|
|
1056
|
-
publish_mode_allowed: response.publish_mode_allowed,
|
|
1057
|
-
preview_digest: response.preview_digest,
|
|
1058
|
-
desired_mode: response.desired_mode,
|
|
1059
|
-
proposed_ontology_version: response.proposed_ontology_version,
|
|
1060
|
-
proposed_shapes_version: response.proposed_shapes_version,
|
|
1061
|
-
diff: response.diff,
|
|
1062
|
-
audit: auditSummary(response.audit),
|
|
1063
|
-
messages: response.messages,
|
|
1064
|
-
suggested_publish_schema: suggestedPublish,
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
898
|
/**
|
|
1068
899
|
* Register the hard-break v2 little big brain tool belt on an MCP server. The surface is
|
|
1069
900
|
* task-oriented for agents; each tool dispatches to the existing @littlebigbrain/client
|
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { IDEMPOTENT_WRITE, MUTATING, READ_ONLY, configureInputSchema, configureWireSchema, detailSchema, graphScope, inspectInputSchema, inspectWireSchema, jsonObjectSchema, queryInputSchema, queryWireSchema, searchFeedbackSchema, } from "./tool-contracts.js";
|
|
3
|
-
import { analyze, assertCursorScope, contentHashKey, decodeQueryCursor, effectiveRowLimit,
|
|
3
|
+
import { analyze, assertCursorScope, contentHashKey, decodeQueryCursor, effectiveRowLimit, enrichError, errorResult, guide, normalizeDetail, normalizeLbbIris, ontologyDefineBody, queryCommitPin, queryEnvelope, requireString, resolveProfile, rowPageFrom, rowPageNext, run, scoped, searchBody, searchFeedbackHint, stableJson, toolResult, } from "./tool-runtime.js";
|
|
4
4
|
export function registerLbbTools(server, client) {
|
|
5
5
|
server.registerTool("lbb_search", {
|
|
6
6
|
description: "Natural-language retrieval over Little Big Brain. Use `query` for one phrasing, `queries` for reciprocal-rank fusion across phrasings, and `follow_paths: true` when you want bounded graph paths from text-resolved seed entities. When you can judge returned results, call lbb_commit mode=search_feedback with good=3, partial=1, bad=0 so Little Big Brain can build customer-specific qrels for embedding training.",
|
|
@@ -62,8 +62,6 @@ export function registerLbbTools(server, client) {
|
|
|
62
62
|
lexical: true,
|
|
63
63
|
bm25: true,
|
|
64
64
|
vector: true,
|
|
65
|
-
bm25_source: "persisted",
|
|
66
|
-
vector_source: "persisted",
|
|
67
65
|
consistency: "strong",
|
|
68
66
|
profile: resolveProfile(profile),
|
|
69
67
|
},
|
|
@@ -85,61 +83,14 @@ export function registerLbbTools(server, client) {
|
|
|
85
83
|
}, (data) => ({
|
|
86
84
|
feedback: searchFeedbackHint(data, { query, queries, graph, branch }),
|
|
87
85
|
})));
|
|
88
|
-
server.registerTool("lbb_ask", {
|
|
89
|
-
description: "Ask a natural-language question about the graph and get a grounded answer with citations. The database snaps the question to its real vocabulary (never invented), retrieves against the pinned snapshot, and answers. `mode` is `resident_planner` when a small resident model synthesized the prose, or `grounding_only` when it returns the grounded evidence for your own model to finish. Prefer this over lbb_search when you want a direct, cited answer rather than a ranked list; set `execute: false` to get only the grounding — the real vocabulary the question maps to — without retrieval. The response `explain` block reports how much the database narrowed (vocabulary candidates the question snapped to, plus retrieved entity/assertion counts) and the per-stage latency (ground / retrieve / synth / total ms), so you can see the pipeline that produced the answer.",
|
|
90
|
-
inputSchema: {
|
|
91
|
-
question: z
|
|
92
|
-
.string()
|
|
93
|
-
.describe("The natural-language question to answer from the graph"),
|
|
94
|
-
top_k: z
|
|
95
|
-
.number()
|
|
96
|
-
.int()
|
|
97
|
-
.positive()
|
|
98
|
-
.max(25)
|
|
99
|
-
.optional()
|
|
100
|
-
.describe("Max citations to return (default 8)"),
|
|
101
|
-
execute: z
|
|
102
|
-
.boolean()
|
|
103
|
-
.optional()
|
|
104
|
-
.describe("Run retrieval and answer (default true); false returns only the grounding"),
|
|
105
|
-
as_of: z
|
|
106
|
-
.string()
|
|
107
|
-
.optional()
|
|
108
|
-
.describe("Valid-time cursor (RFC 3339): retrieval reflects facts true at this instant"),
|
|
109
|
-
as_of_commit_seq: z
|
|
110
|
-
.number()
|
|
111
|
-
.int()
|
|
112
|
-
.nonnegative()
|
|
113
|
-
.optional()
|
|
114
|
-
.describe("Snapshot pin: retrieval and citations reproduce the graph as of this commit sequence"),
|
|
115
|
-
detail: detailSchema,
|
|
116
|
-
...graphScope,
|
|
117
|
-
},
|
|
118
|
-
annotations: READ_ONLY,
|
|
119
|
-
}, ({ question, top_k, execute, as_of, as_of_commit_seq, detail, graph, branch, }) => run(client, "lbb_ask", detail, () => scoped(client, graph, branch).ask({
|
|
120
|
-
question,
|
|
121
|
-
top_k,
|
|
122
|
-
execute,
|
|
123
|
-
...(as_of !== undefined ? { as_of_valid_time: as_of } : {}),
|
|
124
|
-
...(as_of_commit_seq !== undefined ? { as_of_commit_seq } : {}),
|
|
125
|
-
})));
|
|
126
86
|
server.registerTool("lbb_decode", {
|
|
127
|
-
description: "Name the relation between two entities
|
|
87
|
+
description: "Name the relation between two entities from the admissible published vocabulary. Types may be omitted and resolved from entity names.",
|
|
128
88
|
inputSchema: {
|
|
129
|
-
source_name: z.string()
|
|
130
|
-
source_type: z
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
target_name: z.string().describe("Target entity display name"),
|
|
135
|
-
target_type: z
|
|
136
|
-
.string()
|
|
137
|
-
.optional()
|
|
138
|
-
.describe("Target entity type; omit to have the DB recover it from the name"),
|
|
139
|
-
use_model_when_forced: z
|
|
140
|
-
.boolean()
|
|
141
|
-
.optional()
|
|
142
|
-
.describe("Call the model even when the type pair forces one relation (default false — a forced pair is answered by the DB alone)"),
|
|
89
|
+
source_name: z.string(),
|
|
90
|
+
source_type: z.string().optional(),
|
|
91
|
+
target_name: z.string(),
|
|
92
|
+
target_type: z.string().optional(),
|
|
93
|
+
use_model_when_forced: z.boolean().optional(),
|
|
143
94
|
detail: detailSchema,
|
|
144
95
|
...graphScope,
|
|
145
96
|
},
|
|
@@ -150,19 +101,14 @@ export function registerLbbTools(server, client) {
|
|
|
150
101
|
use_model_when_forced,
|
|
151
102
|
})));
|
|
152
103
|
server.registerTool("lbb_ground", {
|
|
153
|
-
description: "Ground
|
|
104
|
+
description: "Ground terms to the graph's published vocabulary. complete autocompletes a prefix, resolve snaps free text to real vocabulary, and audit reports graph groundability.",
|
|
154
105
|
inputSchema: {
|
|
155
|
-
action: z
|
|
156
|
-
.enum(["complete", "resolve", "audit"])
|
|
157
|
-
.describe("complete = narrowed vocabulary autocomplete; resolve = snap free text to the nearest real vocabulary; audit = groundability report"),
|
|
106
|
+
action: z.enum(["complete", "resolve", "audit"]),
|
|
158
107
|
prefix: z
|
|
159
108
|
.string()
|
|
160
109
|
.optional()
|
|
161
110
|
.describe("[complete] Text prefix to complete against the real vocabulary"),
|
|
162
|
-
text: z
|
|
163
|
-
.string()
|
|
164
|
-
.optional()
|
|
165
|
-
.describe("[resolve] Free text to snap to the nearest real vocabulary item"),
|
|
111
|
+
text: z.string().optional().describe("[resolve] Free text to resolve"),
|
|
166
112
|
src_type: z
|
|
167
113
|
.string()
|
|
168
114
|
.optional()
|
|
@@ -181,20 +127,20 @@ export function registerLbbTools(server, client) {
|
|
|
181
127
|
"property",
|
|
182
128
|
]))
|
|
183
129
|
.optional()
|
|
184
|
-
.describe("
|
|
130
|
+
.describe("Restrict to these vocabulary kinds (default: all)"),
|
|
185
131
|
top_k: z
|
|
186
132
|
.number()
|
|
187
133
|
.int()
|
|
188
134
|
.positive()
|
|
189
135
|
.max(50)
|
|
190
136
|
.optional()
|
|
191
|
-
.describe("
|
|
137
|
+
.describe("Max results (default 8)"),
|
|
192
138
|
sample: z
|
|
193
139
|
.number()
|
|
194
140
|
.int()
|
|
195
141
|
.positive()
|
|
196
142
|
.optional()
|
|
197
|
-
.describe("[audit] Entities
|
|
143
|
+
.describe("[audit] Entities sampled for narrowing recall"),
|
|
198
144
|
detail: detailSchema,
|
|
199
145
|
...graphScope,
|
|
200
146
|
},
|
|
@@ -209,7 +155,7 @@ export function registerLbbTools(server, client) {
|
|
|
209
155
|
});
|
|
210
156
|
}
|
|
211
157
|
if (action === "audit") {
|
|
212
|
-
return target.groundability(sample
|
|
158
|
+
return target.groundability(sample == null ? {} : { sample });
|
|
213
159
|
}
|
|
214
160
|
const context = src_type !== undefined || dst_type !== undefined
|
|
215
161
|
? { src_type, dst_type }
|
|
@@ -222,7 +168,7 @@ export function registerLbbTools(server, client) {
|
|
|
222
168
|
});
|
|
223
169
|
}));
|
|
224
170
|
server.registerTool("lbb_inspect", {
|
|
225
|
-
description: "Read graph context and exact graph facts. Actions: guide, ontology, ontology_conformance, schema,
|
|
171
|
+
description: "Read graph context and exact graph facts. Actions: guide, ontology, ontology_conformance, schema, ontology_search, metadata, entity, state, history, transitions, why, traverse. schema reads active ontology/SHACL bundle metadata without running validation. ontology_conformance serves the durable report referenced by the pinned published root. entity returns one node's metadata, scalar attributes, bounded edge neighborhood, history, and observations. Use traverse for bounded path expansion or lbb_query for precise SPARQL edge selection.",
|
|
226
172
|
inputSchema: inspectWireSchema,
|
|
227
173
|
annotations: READ_ONLY,
|
|
228
174
|
}, (rawArgs) => {
|
|
@@ -243,20 +189,6 @@ export function registerLbbTools(server, client) {
|
|
|
243
189
|
return target.ontologyConformance();
|
|
244
190
|
case "schema":
|
|
245
191
|
return target.schema.view();
|
|
246
|
-
case "schema_preview":
|
|
247
|
-
return schemaPreview(target, {
|
|
248
|
-
graph: args.graph,
|
|
249
|
-
branch: args.branch,
|
|
250
|
-
ontology: args.ontology,
|
|
251
|
-
shapes: args.shapes,
|
|
252
|
-
base_ontology_version: args.base_ontology_version,
|
|
253
|
-
base_shapes_version: args.base_shapes_version,
|
|
254
|
-
desired_mode: args.desired_mode,
|
|
255
|
-
});
|
|
256
|
-
case "schema_audit":
|
|
257
|
-
return target.schema.audit();
|
|
258
|
-
case "rules":
|
|
259
|
-
return target.graphRules();
|
|
260
192
|
case "ontology_search":
|
|
261
193
|
return target.ontologySearch({
|
|
262
194
|
query: args.query,
|
|
@@ -277,22 +209,6 @@ export function registerLbbTools(server, client) {
|
|
|
277
209
|
asOf: args.as_of,
|
|
278
210
|
asOfCommitSeq: args.as_of_commit_seq,
|
|
279
211
|
});
|
|
280
|
-
case "edges":
|
|
281
|
-
// Paged edge listing — the way to read every edge of a high-degree
|
|
282
|
-
// node, which `entity` hard-caps. Returns the unified list envelope;
|
|
283
|
-
// page by feeding next_cursor back as `cursor` until has_more=false.
|
|
284
|
-
return target.graphEdges({
|
|
285
|
-
id: args.entity_id,
|
|
286
|
-
type: args.entity_type,
|
|
287
|
-
name: args.name,
|
|
288
|
-
direction: args.direction,
|
|
289
|
-
relation: args.relation,
|
|
290
|
-
limit: args.row_limit,
|
|
291
|
-
cursor: args.cursor,
|
|
292
|
-
offset: args.offset,
|
|
293
|
-
asOf: args.as_of,
|
|
294
|
-
asOfCommitSeq: args.as_of_commit_seq,
|
|
295
|
-
});
|
|
296
212
|
case "state":
|
|
297
213
|
return target.currentState({
|
|
298
214
|
entity: {
|
|
@@ -346,14 +262,10 @@ export function registerLbbTools(server, client) {
|
|
|
346
262
|
as_of_commit_seq: args.as_of_commit_seq ?? null,
|
|
347
263
|
});
|
|
348
264
|
}
|
|
349
|
-
}
|
|
350
|
-
? entityEdgeCapHint(data, args.detail, args.entity_id
|
|
351
|
-
? { entity_id: args.entity_id }
|
|
352
|
-
: { entity_type: args.entity_type, name: args.name })
|
|
353
|
-
: {});
|
|
265
|
+
});
|
|
354
266
|
});
|
|
355
267
|
server.registerTool("lbb_query", {
|
|
356
|
-
description: "Analytical and expert reads. Modes: structured (SPARQL-subset JSON body), sparql (SPARQL text),
|
|
268
|
+
description: "Analytical and expert reads. Modes: structured (SPARQL-subset JSON body), sparql (SPARQL text), analyze. Relations are <https://littlebigbrain.com/r/NAME> and types <https://littlebigbrain.com/class/NAME> (both lowercased); entities are content-addressed, so anchor a named one by its rdfs:label rather than building its IRI. Structured and text queries pin one published watermark for the request.",
|
|
357
269
|
inputSchema: queryWireSchema,
|
|
358
270
|
annotations: READ_ONLY,
|
|
359
271
|
}, (rawArgs) => {
|
|
@@ -505,53 +417,53 @@ export function registerLbbTools(server, client) {
|
|
|
505
417
|
}
|
|
506
418
|
return run(client, `lbb_query.${args.mode}`, args.detail, async () => {
|
|
507
419
|
const target = scoped(client, args.graph, args.branch);
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
top_k: args.top_k ?? 10,
|
|
517
|
-
explain: args.explain ?? false,
|
|
518
|
-
});
|
|
519
|
-
case "infer":
|
|
520
|
-
return target.infer({
|
|
521
|
-
rules: args.rules ?? [],
|
|
522
|
-
max_rounds: args.max_rounds ?? null,
|
|
523
|
-
max_derived: args.max_derived ?? null,
|
|
524
|
-
max_solutions: args.max_solutions ?? null,
|
|
525
|
-
as_of_valid_time: args.as_of ?? null,
|
|
526
|
-
as_of_commit_seq: args.as_of_commit_seq ?? null,
|
|
527
|
-
});
|
|
528
|
-
case "retrieval_premises":
|
|
529
|
-
return target.retrievalPremises({
|
|
530
|
-
anchor: {
|
|
531
|
-
entity_type: args.anchor_type,
|
|
532
|
-
name: args.anchor_name,
|
|
533
|
-
},
|
|
534
|
-
relation: args.relation,
|
|
535
|
-
model_id: "lbb-hash-lexical-v1",
|
|
536
|
-
target_kind: "entity",
|
|
537
|
-
calibration: { a: -1, b: 0 },
|
|
538
|
-
threshold: args.threshold ?? 0.5,
|
|
539
|
-
max_premises: args.max_premises ?? 50,
|
|
540
|
-
query: args.query,
|
|
541
|
-
query_top_k: args.query_top_k ?? 50,
|
|
542
|
-
});
|
|
543
|
-
case "analyze":
|
|
544
|
-
return analyze(target, {
|
|
545
|
-
metric: args.metric,
|
|
546
|
-
chart: args.chart,
|
|
547
|
-
top_k: args.top_k,
|
|
548
|
-
query: args.query,
|
|
549
|
-
field: args.field,
|
|
550
|
-
sparql: args.sparql,
|
|
551
|
-
});
|
|
552
|
-
}
|
|
420
|
+
return analyze(target, {
|
|
421
|
+
metric: args.metric,
|
|
422
|
+
chart: args.chart,
|
|
423
|
+
top_k: args.top_k,
|
|
424
|
+
query: args.query,
|
|
425
|
+
field: args.field,
|
|
426
|
+
sparql: args.sparql,
|
|
427
|
+
});
|
|
553
428
|
});
|
|
554
429
|
});
|
|
430
|
+
server.registerTool("lbb_models", {
|
|
431
|
+
description: "Read model-training inputs or compare retrieval configurations over one pinned published snapshot. shadow_eval takes the API ShadowEvalRequest body; dataset actions return bounded training examples at an optional signal split.",
|
|
432
|
+
inputSchema: {
|
|
433
|
+
action: z.enum([
|
|
434
|
+
"shadow_eval",
|
|
435
|
+
"planner_dataset",
|
|
436
|
+
"planner_preference_dataset",
|
|
437
|
+
"suggest_dataset",
|
|
438
|
+
"extractor_dataset",
|
|
439
|
+
]),
|
|
440
|
+
body: jsonObjectSchema.optional(),
|
|
441
|
+
limit: z.number().int().positive().optional(),
|
|
442
|
+
split_seq: z.number().int().nonnegative().optional(),
|
|
443
|
+
detail: detailSchema,
|
|
444
|
+
...graphScope,
|
|
445
|
+
},
|
|
446
|
+
annotations: READ_ONLY,
|
|
447
|
+
}, ({ action, body, limit, split_seq, detail, graph, branch }) => run(client, `lbb_models.${action}`, detail, () => {
|
|
448
|
+
const target = scoped(client, graph, branch);
|
|
449
|
+
switch (action) {
|
|
450
|
+
case "shadow_eval":
|
|
451
|
+
if (!body)
|
|
452
|
+
throw new Error("shadow_eval requires body");
|
|
453
|
+
return target.shadowEval(body);
|
|
454
|
+
case "planner_dataset":
|
|
455
|
+
return target.plannerDataset({ limit, splitSeq: split_seq });
|
|
456
|
+
case "planner_preference_dataset":
|
|
457
|
+
return target.plannerPreferenceDataset({
|
|
458
|
+
limit,
|
|
459
|
+
splitSeq: split_seq,
|
|
460
|
+
});
|
|
461
|
+
case "suggest_dataset":
|
|
462
|
+
return target.suggestDataset({ limit, splitSeq: split_seq });
|
|
463
|
+
case "extractor_dataset":
|
|
464
|
+
return target.extractorDataset({ limit, splitSeq: split_seq });
|
|
465
|
+
}
|
|
466
|
+
}));
|
|
555
467
|
server.registerTool("lbb_commit", {
|
|
556
468
|
description: "Write graph facts, retract them, or label search results. mode=facts writes triplets/embeddings/properties; mode=retract removes a wrongly-added fact (by edge or by entity) without a full reset; mode=search_feedback labels query/result relevance after lbb_search (Feedback grades: 3=ideal/good, 1=partial, 0=bad; include query, search_id when available, target, rank, score). Explicit idempotency_key wins; when omitted, MCP derives a stable content hash so content-identical retries dedupe. Facts mode defaults edge_idempotency to append; pass skip_unchanged for re-runnable backfills.",
|
|
557
469
|
inputSchema: {
|
|
@@ -661,7 +573,7 @@ export function registerLbbTools(server, client) {
|
|
|
661
573
|
});
|
|
662
574
|
}));
|
|
663
575
|
server.registerTool("lbb_configure", {
|
|
664
|
-
description: "Mutate stored graph configuration. Actions: define_ontology
|
|
576
|
+
description: "Mutate stored graph configuration. Actions: define_ontology, evolve_ontology, and publish_schema. Schema publication atomically activates metadata and enqueues durable conformance; it never validates the whole graph in the request.",
|
|
665
577
|
inputSchema: configureWireSchema,
|
|
666
578
|
annotations: MUTATING,
|
|
667
579
|
}, (rawArgs) => {
|
|
@@ -687,34 +599,17 @@ export function registerLbbTools(server, client) {
|
|
|
687
599
|
allow_data_conflicts: args.allow_data_conflicts ?? false,
|
|
688
600
|
});
|
|
689
601
|
}
|
|
690
|
-
if (args.
|
|
691
|
-
|
|
692
|
-
preview_digest: args.preview_digest,
|
|
693
|
-
ontology: args.ontology,
|
|
694
|
-
shapes: args.shapes,
|
|
695
|
-
desired_mode: args.desired_mode,
|
|
696
|
-
confirm_restrictive: args.confirm_restrictive,
|
|
697
|
-
}));
|
|
698
|
-
}
|
|
699
|
-
if (args.rules.length === 0 && args.confirm_empty !== true) {
|
|
700
|
-
throw new Error("define_rules with an empty rules array requires confirm_empty=true");
|
|
602
|
+
if (args.ontology === undefined && args.shapes === undefined) {
|
|
603
|
+
throw new Error("publish_schema requires an ontology or shapes source");
|
|
701
604
|
}
|
|
702
|
-
return scoped(client, args.graph, args.branch).
|
|
703
|
-
|
|
605
|
+
return scoped(client, args.graph, args.branch).schema.publish({
|
|
606
|
+
ontology: args.ontology,
|
|
607
|
+
shapes: args.shapes,
|
|
608
|
+
desired_mode: args.desired_mode,
|
|
609
|
+
confirm_restrictive: args.confirm_restrictive,
|
|
704
610
|
});
|
|
705
611
|
});
|
|
706
612
|
});
|
|
707
|
-
server.registerTool("lbb_index", {
|
|
708
|
-
description: "Build or refresh persisted BM25, vector, and adjacency indexes so recently committed facts become searchable.",
|
|
709
|
-
inputSchema: {
|
|
710
|
-
background: z
|
|
711
|
-
.boolean()
|
|
712
|
-
.optional()
|
|
713
|
-
.describe("Run detached and poll metadata for completion"),
|
|
714
|
-
...graphScope,
|
|
715
|
-
},
|
|
716
|
-
annotations: MUTATING,
|
|
717
|
-
}, ({ background, graph, branch }) => run(client, "lbb_index", "standard", () => scoped(client, graph, branch).indexRun({ background })));
|
|
718
613
|
server.registerTool("lbb_branch", {
|
|
719
614
|
description: "Branch lifecycle. Actions: create (fork a new branch off from_branch — the tool's `branch` argument names the NEW branch) and merge (validate-then-merge: replay from_branch's post-fork commits onto the scoped target branch — its fork parent — as ONE commit with event ids preserved; SHACL-validates the would-be merged state first and refuses with the report on violations; a fact superseded on the target after the fork wins over the branch's version, reported as a supersedure_race conflict; delete_source consumes the merged branch).",
|
|
720
615
|
inputSchema: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@littlebigbrain/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "MCP server for little big brain — graph and hybrid search tools for agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"test:coverage": "npm run clean:test && tsc -p tsconfig.test.json && node --test --experimental-test-coverage --test-coverage-include=test-dist/*.js --test-coverage-exclude=test-dist/*.test.js --test-coverage-exclude=test-dist/stdio.js --test-coverage-exclude=test-dist/test-support.js --test-coverage-lines=90 --test-coverage-branches=70 --test-coverage-functions=85 \"test-dist/**/*.test.js\""
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@littlebigbrain/client": "^0.
|
|
55
|
+
"@littlebigbrain/client": "^0.9.0",
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1",
|
|
57
57
|
"zod": "^3"
|
|
58
58
|
},
|