@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @littlebigbrain/mcp
2
2
 
3
- Eleven 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.
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, structured analytics, SHACL, inference |
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
- | `lbb_configure` | ontology, schema, and inference rules |
71
- | `lbb_index` | BM25, vector, and adjacency refresh |
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
 
@@ -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
- desired_mode: schemaModeSchema,
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 `schema_preview`
666
- * `ontology`/`shapes` sources, the structured-query `body`, …), which the server
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
@@ -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, entityTypes = []) {
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, RDF/SHACL schema, stored rules, metadata, state/history/why, exact traversals, and this guide.",
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: 'Use lbb_query for structured SPARQL-subset bodies, SPARQL text, SHACL shapes, inference previews, retrieval premises, 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 ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: month|day|…, as } }), with scalar keys returned per group under value_keys[as] — so per-category breakdowns (e.g. by area) and time series (e.g. commits per month) are single server-side queries over typed attributes, not 700 entity fetches bucketed by hand. These typed scalar attributes are set via entity_properties and read back flat under attributes on entity/list reads (there is no nested metadata.attributes blob); discover the queryable field names via lbb_inspect action=ontology (property_defs) or action=schema, and see the lbb_query body field for a copy-paste commits-per-area-per-month example. A FILTER entry has the exact shape { compare: { op: eq|ne|lt|le|gt|ge, left: <term>, right: <term> } } (also and/or/not), where a <term> is { var }, { property: { var, field } }, or { value: { str|i64|f64|bool|date_time|entity } } — e.g. filters: [{ compare: { op: "ge", left: { property: { var: "d", field: "amount" } }, right: { value: { f64: 1000000 } } } }]. In SPARQL text, relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased; reverse with ^) and types <https://littlebigbrain.com/class/NAME> used as `?x a <…/class/NAME>` — the local name is always lowercase, and the tool auto-lowercases /r/, /class/, /p/ IRI local names (noting each rewrite) so a stray uppercase does not silently match nothing; entities are content-addressed, so match a named entity by `?e <http://www.w3.org/2000/01/rdf-schema#label> "Name"` rather than constructing its IRI. Off-graph, a stack also serves the native SPARQL 1.1 Protocol at /sparql for off-the-shelf SPARQL clients.',
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 (add_entity_type / add_relation / add_property / widen, rename, narrow/remove), publish a previewed RDF/SHACL schema bundle, or replace stored rules after previewing them with lbb_query mode=infer.",
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, entityTypes),
838
- how_to: "Ground with lbb_inspect action=guide, retrieve with lbb_search, rate useful/partial/bad retrieval results with lbb_commit mode=search_feedback when you have a judgment, inspect exact entities/schema/rules with lbb_inspect, preview analysis or inference with lbb_query, then write graph facts with lbb_commit or configuration with lbb_configure only when intended.",
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, entityEdgeCapHint, enrichError, errorResult, guide, normalizeDetail, normalizeLbbIris, ontologyDefineBody, queryCommitPin, queryEnvelope, requireString, resolveProfile, rowPageFrom, rowPageNext, run, schemaPreview, schemaPublishBody, scoped, searchBody, searchFeedbackHint, stableJson, toolResult, } from "./tool-runtime.js";
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. The database narrows the candidates to the relations its type signatures admit for the (source type, target type) pair; if the pair admits exactly one, the database answers alone (`mode: forced`, no model call). Otherwise a small model fine-tuned on this graph's own edges picks from the narrowed set (`mode: model_narrowed`), constrained to real vocabulary so it can only emit a relation that can exist. You can OMIT the types — pass just the names and the database recovers each type by resolving the name to a real entity (echoed in `resolved_source`/`resolved_target`). Use it to fill a missing edge label, verify a relationship, or assemble structured triples. Returns the `relation`, the admissible `candidates`, and `signature_forced`.",
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().describe("Source entity display name"),
130
- source_type: z
131
- .string()
132
- .optional()
133
- .describe("Source entity type; omit to have the DB recover it from the name"),
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 your terms to the graph's real vocabulary before you query or write, so you never guess a type, relation, or property name. Actions: `complete` narrowed autocomplete: completes a prefix against the real vocabulary, optionally narrowed to the relations a (src_type, dst_type) pair actually admits (so you only propose relations that can exist); `resolve` snap free text to the single nearest real vocabulary item by embedding/lexical similarity, never fabricating a name; `audit` — groundability report: signature sparsity, name semantics, sampled narrowing recall, and a narrow / narrow+finetune / lexical recommendation for this graph.",
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("[complete/resolve] Restrict to these vocabulary kinds (default: all)"),
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("[complete/resolve] Max results (default 8)"),
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 to sample for narrowing-recall"),
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 != null ? { 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, schema_preview, schema_audit, rules, ontology_search, metadata, entity, edges, state, history, transitions, why, traverse. entity returns one node's metadata, scalar attributes, current state, edges, history, and observations — its edge/history arrays are a display sample capped at the detail limit (counts holds the true totals), so on a high-degree node the response carries an `edge_sample` block with the capped totals and ready-to-run paged reads; follow those (or call edges/history directly, paged by row_limit/offset with direction/relation filters and an as_of/as_of_commit_seq pin) to read the full set — total_count tells you when to stop. transitions returns the ordered state-transition log of an entity's relation with dwell time at each value (cycle-time/process analysis). metadata includes temporal_coverage check it before attempting as-of/daily views: as_of_valid_time_degenerate or single_commit_time means every point-in-time query returns the same snapshot. Use ontology_conformance to check the live data against the ontology's own capped-cardinality rules (derived SHACL sh:maxCount, whole-snapshot, never blocks a write) — distinct from schema_audit, which runs the separately-published SHACL shape bundle.",
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
- }, (data) => args.action === "entity"
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), shacl, infer, retrieval_premises, 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 (see the sparql/structured field hints, and lbb_inspect action=ontology for exact names). For structured/sparql row paging, MCP owns limit/offset via row_limit/cursor; a cursor reuses the original query/body and pins continuation pages to the original head commit. When a single page's rows are complete server-side (row_page.has_more false) but too large for one MCP result, the envelope reports `rows_shown` (fewer than row_page.returned), keeps `truncated: true`, and hands back a `next` cursor that pages the same result set at a smaller row_limit — so a large fully-returned result is never silently cut without a way to read the rest.",
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
- switch (args.mode) {
509
- case "shacl":
510
- return target.shacl({
511
- shapes: args.shapes ?? [],
512
- mode: args.shacl_mode ?? "select",
513
- include_derived: args.include_derived ?? false,
514
- rules: args.rules ?? [],
515
- as_of_valid_time: args.as_of ?? null,
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 (create a new graph ontology), evolve_ontology (evolve an existing graph's ontology in place by name — add_entity_type / add_relation / add_property (a typed scalar field so entity_properties can write it) / widen_relation, rename and set-inverse/cardinality, and data-gated narrow/remove; bumps the ontology version, preserves every record, no migration), publish_schema (activate a previewed RDF/SHACL shape bundle), and define_rules (replace the branch's stored inference rules body/head terms may be variables or fixed entities, and not_exists combinators add stratified negation for universal conditions).",
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.action === "publish_schema") {
691
- return scoped(client, args.graph, args.branch).schema.publish(schemaPublishBody({
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).defineRules({
703
- rules: args.rules,
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.5",
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.6.0",
55
+ "@littlebigbrain/client": "^0.9.0",
56
56
  "@modelcontextprotocol/sdk": "^1",
57
57
  "zod": "^3"
58
58
  },