@littlebigbrain/mcp 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,8 +64,8 @@ Requires Node.js 18+. Copy the complete endpoint and a stack API key from
64
64
  ```
65
65
 
66
66
  The client starts the server and communicates through standard input and output
67
- (stdio). Keep the key in your local configuration. Set `LBB_GRAPH` or
68
- `LBB_BRANCH` to change the default scope from `main`.
67
+ (stdio). Keep the key in your local configuration. Set `LBB_GRAPH` to change
68
+ the default graph from `main`.
69
69
 
70
70
  ## Run a first query
71
71
 
@@ -112,7 +112,7 @@ its original identifiers; inspect it with SPARQL when choosing query predicates.
112
112
 
113
113
  | Tool | Purpose |
114
114
  | --- | --- |
115
- | `lbb_inspect` | Read the schema, graph status, entities, history, and evidence. |
115
+ | `lbb_inspect` | Read the schema, graph status, and entities. |
116
116
  | `lbb_query` | Run SPARQL queries, search by meaning, or request summary statistics. |
117
117
  | `lbb_rdf` | Import RDF documents or add facts with SPARQL `INSERT DATA`. |
118
118
  | `lbb_embeddings` | Inspect search setup and preview the text to embed. |
@@ -120,8 +120,6 @@ its original identifiers; inspect it with SPARQL when choosing query predicates.
120
120
  | `lbb_embeddings_delete` | Delete an embedding and its stored vectors. |
121
121
  | `lbb_commit` | Write or retract JSON facts, or record search feedback. |
122
122
  | `lbb_configure` | Define record types and relationships, or publish validation rules. |
123
- | `lbb_branch` | Create branches and merge reviewed changes. |
124
- | `lbb_observe` | Store conversation episodes and reviewed facts extracted from them. |
125
123
  | `lbb_evals` | Label query results and check whether later queries return the expected answers. |
126
124
  | `lbb_models` | Compare retrieval settings and read model training datasets. |
127
125
 
@@ -130,8 +128,8 @@ for embedding setup. To define constraints with the Shapes Constraint Language
130
128
  (SHACL), see [query and validation](https://docs.littlebigbrain.com/guides/sparql-and-shacl/).
131
129
 
132
130
  RDF imports and JSON facts use different write workflows. A graph first written
133
- through `lbb_rdf` does not accept JSON fact writes through `lbb_commit`, or branch
134
- forks and merges. Choose the workflow when creating the graph. RDF updates
131
+ through `lbb_rdf` does not accept JSON fact writes through `lbb_commit`. Choose
132
+ the workflow when creating the graph. RDF updates
135
133
  support additive `INSERT DATA`;
136
134
  see the [RDF guide](https://docs.littlebigbrain.com/guides/load-rdf/) for format and
137
135
  update limits.
@@ -22,7 +22,6 @@ export interface McpHttpServerOptions {
22
22
  baseUrl: string;
23
23
  apiKey: string;
24
24
  graph?: string;
25
- branch?: string;
26
25
  }) => LbbClient;
27
26
  }
28
27
  export declare function bearer(req: IncomingMessage): string | undefined;
@@ -150,7 +150,6 @@ export function createMcpHttpServer(options) {
150
150
  baseUrl,
151
151
  apiKey,
152
152
  graph: url.searchParams.get("graph") ?? undefined,
153
- branch: url.searchParams.get("branch") ?? undefined,
154
153
  };
155
154
  const client = options.clientFactory
156
155
  ? options.clientFactory(clientOptions)
@@ -4,7 +4,6 @@ import { metadataPageSchema } from "./tool-contracts.js";
4
4
  type Request = z.infer<z.ZodObject<typeof metadataPageSchema>> & {
5
5
  action: "ontology" | "schema";
6
6
  graph?: string;
7
- branch?: string;
8
7
  };
9
8
  /** Stateless pagination of complete entries, including their full nested values.
10
9
  * A digest rejects mixed-version reads instead of skipping/repeating definitions
@@ -24,7 +23,6 @@ export declare function metadataPage(client: LbbClient, args: Request): Promise<
24
23
  next: {
25
24
  action: "ontology" | "schema";
26
25
  graph: string | undefined;
27
- branch: string | undefined;
28
26
  section: string | undefined;
29
27
  page_size: number;
30
28
  cursor: string;
@@ -7,7 +7,6 @@ const cursorSchema = z
7
7
  v: z.literal(1),
8
8
  action: z.enum(["ontology", "schema"]),
9
9
  graph: z.string().optional(),
10
- branch: z.string().optional(),
11
10
  section: z.string().optional(),
12
11
  page_size: z.number().int().min(1).max(500),
13
12
  offset: z.number().int().nonnegative().safe(),
@@ -27,23 +26,16 @@ export async function metadataPage(client, args) {
27
26
  catch {
28
27
  throw new Error("invalid lbb_inspect cursor; restart without cursor");
29
28
  }
30
- for (const key of [
31
- "action",
32
- "graph",
33
- "branch",
34
- "section",
35
- "page_size",
36
- ]) {
29
+ for (const key of ["action", "graph", "section", "page_size"]) {
37
30
  if (args[key] !== undefined && args[key] !== cursor[key]) {
38
31
  throw new Error(`cursor ${key} does not match the supplied ${key}`);
39
32
  }
40
33
  }
41
34
  }
42
35
  const graph = cursor?.graph ?? args.graph;
43
- const branch = cursor?.branch ?? args.branch;
44
36
  const section = cursor?.section ?? args.section;
45
37
  const pageSize = cursor?.page_size ?? args.page_size ?? 50;
46
- const target = client.withScope({ graph, branch });
38
+ const target = client.withScope({ graph });
47
39
  const value = (args.action === "ontology"
48
40
  ? await target.ontologyView({ counts: true })
49
41
  : await target.schema.view());
@@ -62,7 +54,6 @@ export async function metadataPage(client, args) {
62
54
  v: 1,
63
55
  action: args.action,
64
56
  graph,
65
- branch,
66
57
  section,
67
58
  page_size: pageSize,
68
59
  fingerprint,
@@ -94,7 +85,6 @@ export async function metadataPage(client, args) {
94
85
  ? {
95
86
  action: args.action,
96
87
  graph,
97
- branch,
98
88
  section,
99
89
  page_size: pageSize,
100
90
  cursor: Buffer.from(JSON.stringify({
package/dist/rdf-tool.js CHANGED
@@ -49,13 +49,12 @@ export function registerRdfTool(server, client) {
49
49
  return errorResult(parsed.error);
50
50
  const args = parsed.data;
51
51
  return run(client, `lbb_rdf.${args.action}`, args.detail, async () => {
52
- const target = scoped(client, args.graph, args.branch);
52
+ const target = scoped(client, args.graph);
53
53
  const { idempotency_key } = args;
54
54
  const operation = { ...args };
55
55
  delete operation.detail;
56
56
  delete operation.idempotency_key;
57
- const key = idempotency_key ??
58
- contentHashKey({ graph: args.graph, branch: args.branch }, operation);
57
+ const key = idempotency_key ?? contentHashKey({ graph: args.graph }, operation);
59
58
  if (args.action === "import") {
60
59
  return target.importRdf(args.source, {
61
60
  format: args.format ?? "turtle",
package/dist/stdio.js CHANGED
@@ -12,7 +12,7 @@ const BASE_URL_HELP = "LBB_BASE_URL is required. Copy endpoint_url from your Lit
12
12
  *
13
13
  * LBB_BASE_URL (required for tool calls; copy endpoint_url from the stack Connect page)
14
14
  * LBB_API_KEY stack API key (lbb_sk_live_…) or single-mode token
15
- * LBB_GRAPH / LBB_BRANCH (optional; server defaults to main/main)
15
+ * LBB_GRAPH (optional; server defaults to main)
16
16
  *
17
17
  * The base URL is required lazily, at the first tool invocation, not at
18
18
  * process start: MCP hosts launch servers to complete the initialize
@@ -44,7 +44,6 @@ async function main() {
44
44
  baseUrl,
45
45
  apiKey: process.env.LBB_API_KEY,
46
46
  graph: process.env.LBB_GRAPH,
47
- branch: process.env.LBB_BRANCH,
48
47
  })
49
48
  : unconfiguredClient();
50
49
  const server = buildLbbServer(client);
@@ -40,10 +40,6 @@ export const graphScope = {
40
40
  .string()
41
41
  .optional()
42
42
  .describe("Graph to target; defaults to the connection's graph"),
43
- branch: z
44
- .string()
45
- .optional()
46
- .describe("Branch to target; defaults to the connection's branch"),
47
43
  };
48
44
  export const jsonObjectSchema = z.record(z.string(), z.unknown());
49
45
  export const jsonObjectArraySchema = z.array(jsonObjectSchema);
@@ -375,56 +371,7 @@ export const inspectInputSchema = z.discriminatedUnion("action", [
375
371
  .int()
376
372
  .nonnegative()
377
373
  .optional()
378
- .describe("Snapshot pin: reproduce the node (state, edges, history) as of this commit_seq."),
379
- ...readScope,
380
- })
381
- .strict(),
382
- z
383
- .object({
384
- action: z.literal("state"),
385
- entity_type: z.string(),
386
- name: z.string(),
387
- relation: z.string().optional(),
388
- as_of: z.string().optional(),
389
- as_of_commit_seq: z
390
- .number()
391
- .int()
392
- .nonnegative()
393
- .optional()
394
- .describe("Snapshot pin: reproduce the state as of this commit_seq, hiding later commits. Errors if past head."),
395
- ...readScope,
396
- })
397
- .strict(),
398
- z
399
- .object({
400
- action: z.literal("history"),
401
- entity_type: z.string(),
402
- name: z.string(),
403
- relation: z.string().optional(),
404
- ...readScope,
405
- })
406
- .strict(),
407
- z
408
- .object({
409
- action: z.literal("why"),
410
- source_type: z.string(),
411
- source_name: z.string(),
412
- relation: z.string(),
413
- target_type: z.string(),
414
- target_name: z.string(),
415
- ...readScope,
416
- })
417
- .strict(),
418
- z
419
- .object({
420
- action: z.literal("transitions"),
421
- entity_type: z.string(),
422
- name: z.string(),
423
- relation: z
424
- .string()
425
- .describe("State/status relation to trace, e.g. IN_STAGE or HAS_STATUS"),
426
- as_of: z.string().optional(),
427
- as_of_commit_seq: z.number().int().nonnegative().optional(),
374
+ .describe("Snapshot pin: reproduce the node's attributes and relationships as of this commit_seq."),
428
375
  ...readScope,
429
376
  })
430
377
  .strict(),
@@ -576,7 +523,6 @@ export const configureInputSchema = z.discriminatedUnion("action", [
576
523
  .optional()
577
524
  .describe("Preview the exact definition without creating a graph or writing metadata."),
578
525
  graph: z.string().describe("Graph to create or redefine"),
579
- branch: graphScope.branch,
580
526
  entity_types: z.array(z.union([z.string(), jsonObjectSchema])).optional(),
581
527
  relations: z.array(z.union([z.string(), jsonObjectSchema])).optional(),
582
528
  source: z.string().optional(),
@@ -627,7 +573,7 @@ export const configureInputSchema = z.discriminatedUnion("action", [
627
573
  * rejects as `Expected object, received string`.
628
574
  *
629
575
  * Flatten the union into a single ZodObject purely for advertisement and
630
- * transport: the discriminant becomes an enum, every branch field is merged in
576
+ * transport: the discriminant becomes an enum, every variant field is merged in
631
577
  * as optional, and unknown keys pass through. Each handler still `safeParse`s the
632
578
  * raw args against the original strict union before dispatching, so per-variant
633
579
  * required/forbidden fields are enforced exactly as before.
@@ -2,9 +2,7 @@ import { Buffer } from "node:buffer";
2
2
  import { createHash } from "node:crypto";
3
3
  import { LbbError } from "@littlebigbrain/client";
4
4
  import { DEFAULT_DETAIL, HARD_OUTPUT_CHARS, MAX_QUERY_ROW_LIMIT, } from "./tool-contracts.js";
5
- export const scoped = (client, graph, branch) => graph !== undefined || branch !== undefined
6
- ? client.withScope({ graph, branch })
7
- : client;
5
+ export const scoped = (client, graph) => graph !== undefined ? client.withScope({ graph }) : client;
8
6
  export function normalizeDetail(detail) {
9
7
  return detail === "standard" || detail === "full" ? detail : DEFAULT_DETAIL;
10
8
  }
@@ -50,9 +48,6 @@ export function assertCursorScope(args, cursor) {
50
48
  if (args.graph !== undefined && args.graph !== cursor.graph) {
51
49
  throw new Error("cursor graph does not match the supplied graph argument");
52
50
  }
53
- if (args.branch !== undefined && args.branch !== cursor.branch) {
54
- throw new Error("cursor branch does not match the supplied branch argument");
55
- }
56
51
  }
57
52
  export function rowPageFrom(value) {
58
53
  if (!value || typeof value !== "object")
@@ -190,7 +185,7 @@ export function normalizeLbbIris(query) {
190
185
  export function contentHashKey(scope, payload) {
191
186
  const digest = createHash("sha256")
192
187
  .update(stableJson({
193
- scope: { graph: scope.graph ?? null, branch: scope.branch ?? null },
188
+ scope: { graph: scope.graph ?? null },
194
189
  payload,
195
190
  }))
196
191
  .digest("hex");
@@ -440,60 +435,38 @@ export function errorResult(error) {
440
435
  };
441
436
  }
442
437
  /**
443
- * A graph-scope 404 surfaces as a raw object-storage key
444
- * (`not found: tenants/<t>/graphs/<g>/branches/<b>/heads/current.json`), which
445
- * only means something if you already know the graph's real name. Rewrite it
446
- * into an actionable message: name the graph/branch this request targeted, and —
447
- * via the tenant-scoped `GET /v1/graphs`, which resolves even when the scoped
448
- * graph is absent — list the graphs (or branches) that do exist and tell the
449
- * caller to pass `graph=`/`branch=`. Every other error passes through untouched.
438
+ * A graph-scope 404 surfaces as a raw object-storage key ending in
439
+ * `heads/current.json`, which only means something if you already know the
440
+ * graph's real name. Rewrite it into an actionable message: name the graph this
441
+ * request targeted and — via the tenant-scoped `GET /v1/graphs`, which resolves
442
+ * even when the scoped graph is absent — list the graphs that do exist and tell
443
+ * the caller to pass `graph=`. Every other error passes through untouched,
444
+ * including a head 404 on a graph the listing still reports.
450
445
  */
451
446
  export async function enrichError(client, error) {
452
447
  if (!(error instanceof LbbError) || error.status !== 404)
453
448
  return error;
454
- const match = /graphs\/([^/]+)\/branches\/([^/]+)\/heads\/current\.json/.exec(error.message);
449
+ const match = /graphs\/([^/\s]+)\/(?:[^/\s]+\/)*heads\/current\.json/.exec(error.message);
455
450
  if (!match)
456
451
  return error;
457
- const [, graph, branch] = match;
458
- let graphs = [];
452
+ const [, graph] = match;
453
+ let names = [];
459
454
  try {
460
455
  const listed = (await client.listGraphs());
461
- graphs = (listed.data ?? []).flatMap((g) => typeof g.graph_id === "string"
462
- ? [
463
- {
464
- graph_id: g.graph_id,
465
- branches: Array.isArray(g.branches)
466
- ? g.branches
467
- : undefined,
468
- },
469
- ]
470
- : []);
456
+ names = (listed.data ?? []).flatMap((g) => typeof g.graph_id === "string" ? [g.graph_id] : []);
471
457
  }
472
458
  catch {
473
459
  // Listing failed too (auth, transport); fall back to the generic-but-actionable hint.
474
460
  }
475
- const existing = graphs.find((g) => g.graph_id === graph);
476
- let message;
477
- if (existing) {
478
- const branches = existing.branches ?? [];
479
- const list = branches.length > 0
480
- ? ` Existing branches: ${branches.slice(0, 50).join(", ")}.`
481
- : "";
482
- message =
483
- `branch "${branch}" was not found on graph "${graph}" in this tenant.${list} ` +
484
- "Pass an existing branch as the `branch` argument.";
485
- }
486
- else {
487
- const names = graphs.map((g) => g.graph_id);
488
- const list = names.length > 0
489
- ? ` Available graphs in this tenant: ${names.slice(0, 50).join(", ")}.`
490
- : "";
491
- const example = names.length > 0 ? ` (e.g. graph="${names[0]}")` : "";
492
- message =
493
- `graph "${graph}" was not found in this tenant — this request targeted graph "${graph}", branch "${branch}" ` +
494
- `(either you passed it or it is the connection default).${list} ` +
495
- `Pass an existing graph as the \`graph\` argument${example}.`;
496
- }
461
+ if (names.includes(graph))
462
+ return error;
463
+ const list = names.length > 0
464
+ ? ` Available graphs in this tenant: ${names.slice(0, 50).join(", ")}.`
465
+ : "";
466
+ const example = names.length > 0 ? ` (e.g. graph="${names[0]}")` : "";
467
+ const message = `graph "${graph}" was not found in this tenant — this request targeted graph "${graph}" ` +
468
+ `(either you passed it or it is the connection default).${list} ` +
469
+ `Pass an existing graph as the \`graph\` argument${example}.`;
497
470
  return new LbbError(error.status, error.body, {
498
471
  type: error.type,
499
472
  code: error.code,
@@ -683,8 +656,8 @@ export async function guide(scopedClient) {
683
656
  relations,
684
657
  capability: {
685
658
  search_feedback: "When a 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.",
686
- inspect: "Use lbb_inspect for ontology, schema metadata, the published conformance report, metadata, state/history/why, and this guide. Use lbb_query with SPARQL property paths for exact path queries.",
687
- 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.",
659
+ inspect: "Use lbb_inspect for ontology, schema metadata, the published conformance report, metadata, entities, and this guide. Use lbb_query with SPARQL property paths for exact path queries.",
660
+ 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 graph'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.",
688
661
  search: "Use lbb_query mode=search to find instances by meaning. It covers every searchable class; filter narrows it by class and by relationship, and the graph checks every hit. To plan a search: (1) lbb_embeddings action=list gives the searchable classes, the facts each text is made from, and the graph's model. (2) lbb_query mode=sparql shows the relationships of a class on a sample: PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?p (COUNT(DISTINCT ?s) AS ?n) (SAMPLE(?l) AS ?example) WHERE { { SELECT ?s WHERE { ?s a <CLASS> } LIMIT 500 } ?s ?p ?o . FILTER(isIRI(?o) && ?p != rdf:type) OPTIONAL { ?o rdfs:label ?l } } GROUP BY ?p ORDER BY DESC(?n); write ?o ?p ?s for the links into the class, and SELECT ?c WHERE { ?c rdfs:subClassOf* <CLASS> } (same prefixes) for its subclasses. (3) lbb_query mode=search with filter [{class}, {via, to}] and explain=true returns the plan: the resolved relationship and entities, the allowed count, and the mode, with no model call. Check it, then run the search without explain. A name in to resolves against labels (a small typo too); an unknown relationship or name answers with the options.",
689
662
  query: "Use lbb_query for structured SPARQL-subset bodies, SPARQL text, and canned analysis. SPARQL is the only query surface. 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.",
690
663
  write: "Use lbb_commit for fact writes and 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.",
package/dist/tools.js CHANGED
@@ -7,7 +7,7 @@ import { analyze, assertCursorScope, contentHashKey, decodeQueryCursor, effectiv
7
7
  export function registerLbbTools(server, client, options = {}) {
8
8
  registerRdfTool(server, client);
9
9
  server.registerTool("lbb_inspect", {
10
- description: "Read graph context and exact graph facts. Actions: guide, graphs, publication, ontology, ontology_conformance, schema, ontology_search, metadata, entity, state, history, transitions, why. graphs works before bootstrap; publication reports whether writes are queryable. ontology and schema return complete entries with page_size, section and cursor; follow next until absent. schema reads active native ontology/SHACL metadata without running validation. Query asserted RDF/OWL axioms separately with lbb_query. ontology_conformance serves the durable report referenced by the pinned published root. entity returns one node's metadata, scalar attributes, bounded Base-backed edge neighborhood, history, and observations. Use lbb_query with SPARQL property paths for precise path selection.",
10
+ description: "Read graph context and exact graph facts. Actions: guide, graphs, publication, ontology, ontology_conformance, schema, ontology_search, metadata, entity. graphs works before bootstrap; publication reports whether writes are queryable. ontology and schema return complete entries with page_size, section and cursor; follow next until absent. schema reads active native ontology/SHACL metadata without running validation. Query asserted RDF/OWL axioms separately with lbb_query. ontology_conformance serves the durable report referenced by the pinned published root. entity returns one node's attributes and current relationships from the RDF read. For a node's past values, run SPARQL with as_of_commit_seq through lbb_query. Use lbb_query with SPARQL property paths for precise path selection.",
11
11
  inputSchema: inspectWireSchema,
12
12
  annotations: READ_ONLY,
13
13
  }, (rawArgs) => {
@@ -21,7 +21,7 @@ export function registerLbbTools(server, client, options = {}) {
21
21
  .catch(async (error) => errorResult(await enrichError(client, error)));
22
22
  }
23
23
  return run(client, `lbb_inspect.${args.action}`, args.detail, () => {
24
- const target = scoped(client, args.graph, args.branch);
24
+ const target = scoped(client, args.graph);
25
25
  switch (args.action) {
26
26
  case "guide":
27
27
  return guide(target);
@@ -51,46 +51,6 @@ export function registerLbbTools(server, client, options = {}) {
51
51
  asOf: args.as_of,
52
52
  asOfCommitSeq: args.as_of_commit_seq,
53
53
  });
54
- case "state":
55
- return target.currentState({
56
- entity: {
57
- entity_type: args.entity_type,
58
- name: args.name,
59
- },
60
- relations: args.relation ? [args.relation] : null,
61
- as_of_valid_time: args.as_of ?? null,
62
- as_of_commit_seq: args.as_of_commit_seq ?? null,
63
- });
64
- case "history":
65
- return target.history({
66
- source: {
67
- entity_type: args.entity_type,
68
- name: args.name,
69
- },
70
- relation: args.relation ?? null,
71
- });
72
- case "why":
73
- return target.why({
74
- source: {
75
- entity_type: args.source_type,
76
- name: args.source_name,
77
- },
78
- relation: args.relation,
79
- target: {
80
- entity_type: args.target_type,
81
- name: args.target_name,
82
- },
83
- });
84
- case "transitions":
85
- return target.transitions({
86
- entity: {
87
- entity_type: args.entity_type,
88
- name: args.name,
89
- },
90
- relation: args.relation,
91
- as_of_valid_time: args.as_of ?? null,
92
- as_of_commit_seq: args.as_of_commit_seq ?? null,
93
- });
94
54
  }
95
55
  });
96
56
  });
@@ -110,7 +70,7 @@ export function registerLbbTools(server, client, options = {}) {
110
70
  if (cursor && cursor.mode !== args.mode) {
111
71
  throw new Error(`cursor is for ${cursor.mode}, not ${args.mode}`);
112
72
  }
113
- assertCursorScope({ graph: args.graph, branch: args.branch }, cursor);
73
+ assertCursorScope({ graph: args.graph }, cursor);
114
74
  if (cursor &&
115
75
  args.row_limit !== undefined &&
116
76
  args.row_limit !== cursor.row_limit) {
@@ -119,9 +79,8 @@ export function registerLbbTools(server, client, options = {}) {
119
79
  const detail = normalizeDetail(args.detail ?? cursor?.detail);
120
80
  const rowLimit = effectiveRowLimit(detail, args.row_limit ?? cursor?.row_limit);
121
81
  const graph = cursor?.graph ?? args.graph;
122
- const branch = cursor?.branch ?? args.branch;
123
82
  const offset = cursor?.offset ?? 0;
124
- const target = scoped(client, graph, branch);
83
+ const target = scoped(client, graph);
125
84
  const timing = queryTiming(options, {
126
85
  mode: args.mode,
127
86
  continuation: cursor !== undefined,
@@ -230,7 +189,6 @@ export function registerLbbTools(server, client, options = {}) {
230
189
  v: 1,
231
190
  mode: "structured",
232
191
  graph,
233
- branch,
234
192
  detail,
235
193
  row_limit: rowLimit,
236
194
  body,
@@ -284,7 +242,6 @@ export function registerLbbTools(server, client, options = {}) {
284
242
  v: 1,
285
243
  mode: "sparql",
286
244
  graph,
287
- branch,
288
245
  detail,
289
246
  row_limit: rowLimit,
290
247
  query,
@@ -305,7 +262,7 @@ export function registerLbbTools(server, client, options = {}) {
305
262
  if (args.mode === "search") {
306
263
  const searchArgs = args;
307
264
  return run(client, "lbb_query.search", undefined, async () => {
308
- const target = scoped(client, searchArgs.graph, searchArgs.branch);
265
+ const target = scoped(client, searchArgs.graph);
309
266
  return target.embeddings.search({
310
267
  embedding: searchArgs.embedding,
311
268
  text: searchArgs.text,
@@ -319,7 +276,7 @@ export function registerLbbTools(server, client, options = {}) {
319
276
  });
320
277
  }
321
278
  return run(client, `lbb_query.${args.mode}`, args.detail, async () => {
322
- const target = scoped(client, args.graph, args.branch);
279
+ const target = scoped(client, args.graph);
323
280
  return analyze(target, {
324
281
  metric: args.metric,
325
282
  chart: args.chart,
@@ -333,13 +290,7 @@ export function registerLbbTools(server, client, options = {}) {
333
290
  server.registerTool("lbb_models", {
334
291
  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.",
335
292
  inputSchema: {
336
- action: z.enum([
337
- "shadow_eval",
338
- "planner_dataset",
339
- "planner_preference_dataset",
340
- "suggest_dataset",
341
- "extractor_dataset",
342
- ]),
293
+ action: z.enum(["shadow_eval", "suggest_dataset", "extractor_dataset"]),
343
294
  body: jsonObjectSchema.optional(),
344
295
  limit: z.number().int().positive().optional(),
345
296
  split_seq: z.number().int().nonnegative().optional(),
@@ -347,20 +298,13 @@ export function registerLbbTools(server, client, options = {}) {
347
298
  ...graphScope,
348
299
  },
349
300
  annotations: READ_ONLY,
350
- }, ({ action, body, limit, split_seq, detail, graph, branch }) => run(client, `lbb_models.${action}`, detail, () => {
351
- const target = scoped(client, graph, branch);
301
+ }, ({ action, body, limit, split_seq, detail, graph }) => run(client, `lbb_models.${action}`, detail, () => {
302
+ const target = scoped(client, graph);
352
303
  switch (action) {
353
304
  case "shadow_eval":
354
305
  if (!body)
355
306
  throw new Error("shadow_eval requires body");
356
307
  return target.shadowEval(body);
357
- case "planner_dataset":
358
- return target.plannerDataset({ limit, splitSeq: split_seq });
359
- case "planner_preference_dataset":
360
- return target.plannerPreferenceDataset({
361
- limit,
362
- splitSeq: split_seq,
363
- });
364
308
  case "suggest_dataset":
365
309
  return target.suggestDataset({ limit, splitSeq: split_seq });
366
310
  case "extractor_dataset":
@@ -416,7 +360,7 @@ export function registerLbbTools(server, client, options = {}) {
416
360
  },
417
361
  annotations: READ_ONLY,
418
362
  }, (args) => run(client, `lbb_embeddings.${args.action}`, args.detail, () => {
419
- const target = scoped(client, args.graph, args.branch);
363
+ const target = scoped(client, args.graph);
420
364
  switch (args.action) {
421
365
  case "list":
422
366
  return target.embeddings.list();
@@ -446,7 +390,7 @@ export function registerLbbTools(server, client, options = {}) {
446
390
  },
447
391
  annotations: MUTATING,
448
392
  }, (args) => run(client, `lbb_embeddings_manage.${args.action}`, args.detail, () => {
449
- const target = scoped(client, args.graph, args.branch);
393
+ const target = scoped(client, args.graph);
450
394
  switch (args.action) {
451
395
  case "declare":
452
396
  return target.embeddings.declare(recipeOf(args));
@@ -472,11 +416,11 @@ export function registerLbbTools(server, client, options = {}) {
472
416
  ...graphScope,
473
417
  },
474
418
  annotations: DESTRUCTIVE,
475
- }, ({ name, confirm, detail, graph, branch }) => run(client, "lbb_embeddings_delete", detail, () => {
419
+ }, ({ name, confirm, detail, graph }) => run(client, "lbb_embeddings_delete", detail, () => {
476
420
  if (confirm !== name) {
477
421
  throw new Error("confirm must repeat the embedding name");
478
422
  }
479
- return scoped(client, graph, branch).embeddings.delete(name);
423
+ return scoped(client, graph).embeddings.delete(name);
480
424
  }));
481
425
  server.registerTool("lbb_evals", {
482
426
  description: "Managed evals: the thumbs up / thumbs down of the graph, per result. A query run with `request` (lbb_query) records a trace with one item per result (hit or row); trace reads it back with the item ids. label marks one result (item + valid) or several (items) relevant or not; the labels become the golden's ground truth. golden freezes a query (every result it returns now is relevant). run replays every golden at the current commit: pass when every relevant result is back and no wrong one is; new results open a review trace. judge lets the platform's judge model (the hosted frontier model) label unlabeled results. summary, traces, goldens, results, and settings read state.",
@@ -562,8 +506,8 @@ export function registerLbbTools(server, client, options = {}) {
562
506
  ...graphScope,
563
507
  },
564
508
  annotations: MUTATING,
565
- }, ({ action, trace_id, item, valid, items, by, note, sparql, surface, embedding, top_k, request, golden_id, limit, unlabeled, consistency, detail, graph, branch, }) => run(client, `lbb_evals.${action}`, detail, () => {
566
- const target = scoped(client, graph, branch);
509
+ }, ({ action, trace_id, item, valid, items, by, note, sparql, surface, embedding, top_k, request, golden_id, limit, unlabeled, consistency, detail, graph, }) => run(client, `lbb_evals.${action}`, detail, () => {
510
+ const target = scoped(client, graph);
567
511
  switch (action) {
568
512
  case "summary":
569
513
  return target.evals.summary();
@@ -684,7 +628,7 @@ export function registerLbbTools(server, client, options = {}) {
684
628
  ...graphScope,
685
629
  },
686
630
  annotations: IDEMPOTENT_WRITE,
687
- }, ({ idempotency_key, mode, triplets, entity_embeddings, entity_properties, search_feedback, dry_run, observed_at, edge_idempotency, retract_edges, retract_entities, graph, branch, }) => run(client, "lbb_commit", "standard", () => {
631
+ }, ({ idempotency_key, mode, triplets, entity_embeddings, entity_properties, search_feedback, dry_run, observed_at, edge_idempotency, retract_edges, retract_entities, graph, }) => run(client, "lbb_commit", "standard", () => {
688
632
  const commitMode = mode ??
689
633
  (search_feedback
690
634
  ? "search_feedback"
@@ -700,8 +644,8 @@ export function registerLbbTools(server, client, options = {}) {
700
644
  throw new Error("lbb_commit mode=retract requires retract_edges or retract_entities");
701
645
  }
702
646
  const key = idempotency_key ??
703
- contentHashKey({ graph, branch }, { mode: "retract", edges, entities });
704
- return scoped(client, graph, branch).retract({ edges, entities }, {
647
+ contentHashKey({ graph }, { mode: "retract", edges, entities });
648
+ return scoped(client, graph).retract({ edges, entities }, {
705
649
  idempotencyKey: key,
706
650
  });
707
651
  }
@@ -709,8 +653,8 @@ export function registerLbbTools(server, client, options = {}) {
709
653
  if (!search_feedback)
710
654
  throw new Error("lbb_commit mode=search_feedback requires search_feedback");
711
655
  const key = idempotency_key ??
712
- contentHashKey({ graph, branch }, { mode: "search_feedback", search_feedback });
713
- return scoped(client, graph, branch).searchFeedback(search_feedback, { idempotencyKey: key });
656
+ contentHashKey({ graph }, { mode: "search_feedback", search_feedback });
657
+ return scoped(client, graph).searchFeedback(search_feedback, { idempotencyKey: key });
714
658
  }
715
659
  if (search_feedback) {
716
660
  throw new Error("lbb_commit facts mode cannot include search_feedback");
@@ -727,10 +671,10 @@ export function registerLbbTools(server, client, options = {}) {
727
671
  payload.entity_properties.length === 0) {
728
672
  throw new Error("lbb_commit requires at least one triplet, entity embedding, or entity property");
729
673
  }
730
- const key = idempotency_key ?? contentHashKey({ graph, branch }, payload);
674
+ const key = idempotency_key ?? contentHashKey({ graph }, payload);
731
675
  if (dry_run)
732
- return scoped(client, graph, branch).commitDryRun(payload);
733
- return scoped(client, graph, branch).commit(payload, {
676
+ return scoped(client, graph).commitDryRun(payload);
677
+ return scoped(client, graph).commit(payload, {
734
678
  idempotencyKey: key,
735
679
  });
736
680
  }));
@@ -745,9 +689,7 @@ export function registerLbbTools(server, client, options = {}) {
745
689
  const args = parsed.data;
746
690
  return run(client, `lbb_configure.${args.action}`, "standard", () => {
747
691
  if (args.action === "define_ontology") {
748
- return client
749
- .withScope({ graph: args.graph, branch: args.branch })
750
- .ontologyDefine(ontologyDefineBody({
692
+ return client.withScope({ graph: args.graph }).ontologyDefine(ontologyDefineBody({
751
693
  entity_types: args.entity_types,
752
694
  relations: args.relations,
753
695
  source: args.source,
@@ -757,7 +699,7 @@ export function registerLbbTools(server, client, options = {}) {
757
699
  }));
758
700
  }
759
701
  if (args.action === "evolve_ontology") {
760
- return scoped(client, args.graph, args.branch).ontology.evolve({
702
+ return scoped(client, args.graph).ontology.evolve({
761
703
  ops: args.ops,
762
704
  allow_data_conflicts: args.allow_data_conflicts ?? false,
763
705
  }, { dryRun: args.dry_run });
@@ -765,7 +707,7 @@ export function registerLbbTools(server, client, options = {}) {
765
707
  if (args.shapes === undefined) {
766
708
  throw new Error("publish_schema requires a SHACL shapes source; use define_ontology or evolve_ontology for native metadata changes");
767
709
  }
768
- return scoped(client, args.graph, args.branch).schema.publish({
710
+ return scoped(client, args.graph).schema.publish({
769
711
  ontology: args.ontology,
770
712
  shapes: args.shapes,
771
713
  desired_mode: args.desired_mode,
@@ -773,85 +715,4 @@ export function registerLbbTools(server, client, options = {}) {
773
715
  }, { dryRun: args.dry_run });
774
716
  });
775
717
  });
776
- server.registerTool("lbb_branch", {
777
- 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).",
778
- inputSchema: {
779
- action: z
780
- .enum(["create", "merge"])
781
- .describe("create = fork a new branch; merge = replay a child branch onto its fork parent"),
782
- from_branch: z
783
- .string()
784
- .describe("create: the branch to fork from; merge: the child branch whose commits are replayed"),
785
- validate: z
786
- .boolean()
787
- .optional()
788
- .describe("merge only: refuse on SHACL violations of the would-be merged state (default true)"),
789
- delete_source: z
790
- .boolean()
791
- .optional()
792
- .describe("merge only: delete every object under the merged branch after success"),
793
- ...graphScope,
794
- },
795
- annotations: MUTATING,
796
- }, ({ action, from_branch, validate, delete_source, graph, branch }) => run(client, `lbb_branch.${action}`, "standard", () => {
797
- const target = scoped(client, graph, branch);
798
- if (action === "create")
799
- return target.createBranch({ from_branch });
800
- return target.mergeBranch({
801
- from_branch,
802
- validate: validate ?? true,
803
- delete_source: delete_source ?? false,
804
- });
805
- }));
806
- server.registerTool("lbb_observe", {
807
- description: "Remember a conversation: store the turns verbatim as an EPISODE evidence entity, then anchor + gate the supplied facts on an observe branch (LLM extraction cannot poison the main graph). Facts with both endpoints already in the graph are anchored; unanchored facts need confidence >= 0.8 to mint new entities, else they come back needs_review. auto_merge merges the branch onto the scoped branch when SHACL validation is clean (the validate-then-merge). Server flag-gated (--enable-observe). This build takes caller-extracted facts (each with a structured triplet); bare statements come back needs_review.",
808
- inputSchema: {
809
- session_id: z
810
- .string()
811
- .describe("Caller's conversation id (drives the default observe branch name)"),
812
- turns: z
813
- .array(z.object({
814
- role: z.string().describe("user | assistant | tool"),
815
- content: z.string(),
816
- name: z.string().optional(),
817
- ts: z.string().optional().describe("RFC 3339 timestamp"),
818
- }))
819
- .min(1)
820
- .describe("The conversation slice to remember (stored verbatim)"),
821
- source: z
822
- .string()
823
- .optional()
824
- .describe("Source label, e.g. support-bot"),
825
- facts: z
826
- .array(z.object({
827
- fact: z.string().describe("Natural-language statement"),
828
- confidence: z.number().optional().describe("0..1 (default 0.9)"),
829
- triplet: jsonObjectSchema
830
- .optional()
831
- .describe("Structured form {source:{type,name}, relation, target:{type,name}} — required for the fact to commit"),
832
- }))
833
- .optional()
834
- .describe("Caller-extracted candidate facts; omit with extract:false to store the episode only"),
835
- extract: z
836
- .boolean()
837
- .optional()
838
- .describe("false = store the episode only (default true)"),
839
- observe_branch: z
840
- .string()
841
- .optional()
842
- .describe("Branch for the facts (default observe-<hash12(session_id)>)"),
843
- auto_merge: z
844
- .boolean()
845
- .optional()
846
- .describe("Merge onto the scoped branch when validation is clean"),
847
- ...graphScope,
848
- },
849
- annotations: MUTATING,
850
- }, ({ session_id, turns, source, facts, extract, observe_branch, auto_merge, graph, branch, }) => run(client, "lbb_observe", "standard", () => scoped(client, graph, branch).observe({
851
- episode: { turns, session_id, source },
852
- extract: extract ?? true,
853
- extraction: { byo_completion: (facts ?? []) },
854
- branch: observe_branch,
855
- auto_merge: auto_merge ?? false,
856
- })));
857
718
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlebigbrain/mcp",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "description": "MCP server for little big brain: search, graph queries, and data import",
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.13.2",
55
+ "@littlebigbrain/client": "^0.15.0",
56
56
  "@modelcontextprotocol/sdk": "^1",
57
57
  "zod": "^3"
58
58
  },