@littlebigbrain/mcp 0.5.2 → 0.6.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 +4 -6
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +0 -1
- package/dist/metadata-pages.d.ts +0 -2
- package/dist/metadata-pages.js +2 -12
- package/dist/rdf-tool.js +2 -3
- package/dist/stdio.js +1 -2
- package/dist/tool-contracts.js +1 -6
- package/dist/tool-runtime.js +23 -50
- package/dist/tools.js +25 -124
- package/package.json +2 -2
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`
|
|
68
|
-
|
|
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
|
|
|
@@ -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
|
|
134
|
-
|
|
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.
|
package/dist/http-server.d.ts
CHANGED
package/dist/http-server.js
CHANGED
|
@@ -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)
|
package/dist/metadata-pages.d.ts
CHANGED
|
@@ -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;
|
package/dist/metadata-pages.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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);
|
package/dist/tool-contracts.js
CHANGED
|
@@ -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);
|
|
@@ -576,7 +572,6 @@ export const configureInputSchema = z.discriminatedUnion("action", [
|
|
|
576
572
|
.optional()
|
|
577
573
|
.describe("Preview the exact definition without creating a graph or writing metadata."),
|
|
578
574
|
graph: z.string().describe("Graph to create or redefine"),
|
|
579
|
-
branch: graphScope.branch,
|
|
580
575
|
entity_types: z.array(z.union([z.string(), jsonObjectSchema])).optional(),
|
|
581
576
|
relations: z.array(z.union([z.string(), jsonObjectSchema])).optional(),
|
|
582
577
|
source: z.string().optional(),
|
|
@@ -627,7 +622,7 @@ export const configureInputSchema = z.discriminatedUnion("action", [
|
|
|
627
622
|
* rejects as `Expected object, received string`.
|
|
628
623
|
*
|
|
629
624
|
* Flatten the union into a single ZodObject purely for advertisement and
|
|
630
|
-
* transport: the discriminant becomes an enum, every
|
|
625
|
+
* transport: the discriminant becomes an enum, every variant field is merged in
|
|
631
626
|
* as optional, and unknown keys pass through. Each handler still `safeParse`s the
|
|
632
627
|
* raw args against the original strict union before dispatching, so per-variant
|
|
633
628
|
* required/forbidden fields are enforced exactly as before.
|
package/dist/tool-runtime.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
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\/([
|
|
449
|
+
const match = /graphs\/([^/\s]+)\/(?:[^/\s]+\/)*heads\/current\.json/.exec(error.message);
|
|
455
450
|
if (!match)
|
|
456
451
|
return error;
|
|
457
|
-
const [, graph
|
|
458
|
-
let
|
|
452
|
+
const [, graph] = match;
|
|
453
|
+
let names = [];
|
|
459
454
|
try {
|
|
460
455
|
const listed = (await client.listGraphs());
|
|
461
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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,
|
|
@@ -684,7 +657,7 @@ export async function guide(scopedClient) {
|
|
|
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
659
|
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
|
|
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
|
@@ -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
|
|
24
|
+
const target = scoped(client, args.graph);
|
|
25
25
|
switch (args.action) {
|
|
26
26
|
case "guide":
|
|
27
27
|
return guide(target);
|
|
@@ -110,7 +110,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
110
110
|
if (cursor && cursor.mode !== args.mode) {
|
|
111
111
|
throw new Error(`cursor is for ${cursor.mode}, not ${args.mode}`);
|
|
112
112
|
}
|
|
113
|
-
assertCursorScope({ graph: args.graph
|
|
113
|
+
assertCursorScope({ graph: args.graph }, cursor);
|
|
114
114
|
if (cursor &&
|
|
115
115
|
args.row_limit !== undefined &&
|
|
116
116
|
args.row_limit !== cursor.row_limit) {
|
|
@@ -119,9 +119,8 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
119
119
|
const detail = normalizeDetail(args.detail ?? cursor?.detail);
|
|
120
120
|
const rowLimit = effectiveRowLimit(detail, args.row_limit ?? cursor?.row_limit);
|
|
121
121
|
const graph = cursor?.graph ?? args.graph;
|
|
122
|
-
const branch = cursor?.branch ?? args.branch;
|
|
123
122
|
const offset = cursor?.offset ?? 0;
|
|
124
|
-
const target = scoped(client, graph
|
|
123
|
+
const target = scoped(client, graph);
|
|
125
124
|
const timing = queryTiming(options, {
|
|
126
125
|
mode: args.mode,
|
|
127
126
|
continuation: cursor !== undefined,
|
|
@@ -230,7 +229,6 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
230
229
|
v: 1,
|
|
231
230
|
mode: "structured",
|
|
232
231
|
graph,
|
|
233
|
-
branch,
|
|
234
232
|
detail,
|
|
235
233
|
row_limit: rowLimit,
|
|
236
234
|
body,
|
|
@@ -284,7 +282,6 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
284
282
|
v: 1,
|
|
285
283
|
mode: "sparql",
|
|
286
284
|
graph,
|
|
287
|
-
branch,
|
|
288
285
|
detail,
|
|
289
286
|
row_limit: rowLimit,
|
|
290
287
|
query,
|
|
@@ -305,7 +302,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
305
302
|
if (args.mode === "search") {
|
|
306
303
|
const searchArgs = args;
|
|
307
304
|
return run(client, "lbb_query.search", undefined, async () => {
|
|
308
|
-
const target = scoped(client, searchArgs.graph
|
|
305
|
+
const target = scoped(client, searchArgs.graph);
|
|
309
306
|
return target.embeddings.search({
|
|
310
307
|
embedding: searchArgs.embedding,
|
|
311
308
|
text: searchArgs.text,
|
|
@@ -319,7 +316,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
319
316
|
});
|
|
320
317
|
}
|
|
321
318
|
return run(client, `lbb_query.${args.mode}`, args.detail, async () => {
|
|
322
|
-
const target = scoped(client, args.graph
|
|
319
|
+
const target = scoped(client, args.graph);
|
|
323
320
|
return analyze(target, {
|
|
324
321
|
metric: args.metric,
|
|
325
322
|
chart: args.chart,
|
|
@@ -333,13 +330,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
333
330
|
server.registerTool("lbb_models", {
|
|
334
331
|
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
332
|
inputSchema: {
|
|
336
|
-
action: z.enum([
|
|
337
|
-
"shadow_eval",
|
|
338
|
-
"planner_dataset",
|
|
339
|
-
"planner_preference_dataset",
|
|
340
|
-
"suggest_dataset",
|
|
341
|
-
"extractor_dataset",
|
|
342
|
-
]),
|
|
333
|
+
action: z.enum(["shadow_eval", "suggest_dataset", "extractor_dataset"]),
|
|
343
334
|
body: jsonObjectSchema.optional(),
|
|
344
335
|
limit: z.number().int().positive().optional(),
|
|
345
336
|
split_seq: z.number().int().nonnegative().optional(),
|
|
@@ -347,20 +338,13 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
347
338
|
...graphScope,
|
|
348
339
|
},
|
|
349
340
|
annotations: READ_ONLY,
|
|
350
|
-
}, ({ action, body, limit, split_seq, detail, graph
|
|
351
|
-
const target = scoped(client, graph
|
|
341
|
+
}, ({ action, body, limit, split_seq, detail, graph }) => run(client, `lbb_models.${action}`, detail, () => {
|
|
342
|
+
const target = scoped(client, graph);
|
|
352
343
|
switch (action) {
|
|
353
344
|
case "shadow_eval":
|
|
354
345
|
if (!body)
|
|
355
346
|
throw new Error("shadow_eval requires body");
|
|
356
347
|
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
348
|
case "suggest_dataset":
|
|
365
349
|
return target.suggestDataset({ limit, splitSeq: split_seq });
|
|
366
350
|
case "extractor_dataset":
|
|
@@ -416,7 +400,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
416
400
|
},
|
|
417
401
|
annotations: READ_ONLY,
|
|
418
402
|
}, (args) => run(client, `lbb_embeddings.${args.action}`, args.detail, () => {
|
|
419
|
-
const target = scoped(client, args.graph
|
|
403
|
+
const target = scoped(client, args.graph);
|
|
420
404
|
switch (args.action) {
|
|
421
405
|
case "list":
|
|
422
406
|
return target.embeddings.list();
|
|
@@ -446,7 +430,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
446
430
|
},
|
|
447
431
|
annotations: MUTATING,
|
|
448
432
|
}, (args) => run(client, `lbb_embeddings_manage.${args.action}`, args.detail, () => {
|
|
449
|
-
const target = scoped(client, args.graph
|
|
433
|
+
const target = scoped(client, args.graph);
|
|
450
434
|
switch (args.action) {
|
|
451
435
|
case "declare":
|
|
452
436
|
return target.embeddings.declare(recipeOf(args));
|
|
@@ -472,11 +456,11 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
472
456
|
...graphScope,
|
|
473
457
|
},
|
|
474
458
|
annotations: DESTRUCTIVE,
|
|
475
|
-
}, ({ name, confirm, detail, graph
|
|
459
|
+
}, ({ name, confirm, detail, graph }) => run(client, "lbb_embeddings_delete", detail, () => {
|
|
476
460
|
if (confirm !== name) {
|
|
477
461
|
throw new Error("confirm must repeat the embedding name");
|
|
478
462
|
}
|
|
479
|
-
return scoped(client, graph
|
|
463
|
+
return scoped(client, graph).embeddings.delete(name);
|
|
480
464
|
}));
|
|
481
465
|
server.registerTool("lbb_evals", {
|
|
482
466
|
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 +546,8 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
562
546
|
...graphScope,
|
|
563
547
|
},
|
|
564
548
|
annotations: MUTATING,
|
|
565
|
-
}, ({ action, trace_id, item, valid, items, by, note, sparql, surface, embedding, top_k, request, golden_id, limit, unlabeled, consistency, detail, graph,
|
|
566
|
-
const target = scoped(client, graph
|
|
549
|
+
}, ({ 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, () => {
|
|
550
|
+
const target = scoped(client, graph);
|
|
567
551
|
switch (action) {
|
|
568
552
|
case "summary":
|
|
569
553
|
return target.evals.summary();
|
|
@@ -684,7 +668,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
684
668
|
...graphScope,
|
|
685
669
|
},
|
|
686
670
|
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,
|
|
671
|
+
}, ({ 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
672
|
const commitMode = mode ??
|
|
689
673
|
(search_feedback
|
|
690
674
|
? "search_feedback"
|
|
@@ -700,8 +684,8 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
700
684
|
throw new Error("lbb_commit mode=retract requires retract_edges or retract_entities");
|
|
701
685
|
}
|
|
702
686
|
const key = idempotency_key ??
|
|
703
|
-
contentHashKey({ graph
|
|
704
|
-
return scoped(client, graph
|
|
687
|
+
contentHashKey({ graph }, { mode: "retract", edges, entities });
|
|
688
|
+
return scoped(client, graph).retract({ edges, entities }, {
|
|
705
689
|
idempotencyKey: key,
|
|
706
690
|
});
|
|
707
691
|
}
|
|
@@ -709,8 +693,8 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
709
693
|
if (!search_feedback)
|
|
710
694
|
throw new Error("lbb_commit mode=search_feedback requires search_feedback");
|
|
711
695
|
const key = idempotency_key ??
|
|
712
|
-
contentHashKey({ graph
|
|
713
|
-
return scoped(client, graph
|
|
696
|
+
contentHashKey({ graph }, { mode: "search_feedback", search_feedback });
|
|
697
|
+
return scoped(client, graph).searchFeedback(search_feedback, { idempotencyKey: key });
|
|
714
698
|
}
|
|
715
699
|
if (search_feedback) {
|
|
716
700
|
throw new Error("lbb_commit facts mode cannot include search_feedback");
|
|
@@ -727,10 +711,10 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
727
711
|
payload.entity_properties.length === 0) {
|
|
728
712
|
throw new Error("lbb_commit requires at least one triplet, entity embedding, or entity property");
|
|
729
713
|
}
|
|
730
|
-
const key = idempotency_key ?? contentHashKey({ graph
|
|
714
|
+
const key = idempotency_key ?? contentHashKey({ graph }, payload);
|
|
731
715
|
if (dry_run)
|
|
732
|
-
return scoped(client, graph
|
|
733
|
-
return scoped(client, graph
|
|
716
|
+
return scoped(client, graph).commitDryRun(payload);
|
|
717
|
+
return scoped(client, graph).commit(payload, {
|
|
734
718
|
idempotencyKey: key,
|
|
735
719
|
});
|
|
736
720
|
}));
|
|
@@ -745,9 +729,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
745
729
|
const args = parsed.data;
|
|
746
730
|
return run(client, `lbb_configure.${args.action}`, "standard", () => {
|
|
747
731
|
if (args.action === "define_ontology") {
|
|
748
|
-
return client
|
|
749
|
-
.withScope({ graph: args.graph, branch: args.branch })
|
|
750
|
-
.ontologyDefine(ontologyDefineBody({
|
|
732
|
+
return client.withScope({ graph: args.graph }).ontologyDefine(ontologyDefineBody({
|
|
751
733
|
entity_types: args.entity_types,
|
|
752
734
|
relations: args.relations,
|
|
753
735
|
source: args.source,
|
|
@@ -757,7 +739,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
757
739
|
}));
|
|
758
740
|
}
|
|
759
741
|
if (args.action === "evolve_ontology") {
|
|
760
|
-
return scoped(client, args.graph
|
|
742
|
+
return scoped(client, args.graph).ontology.evolve({
|
|
761
743
|
ops: args.ops,
|
|
762
744
|
allow_data_conflicts: args.allow_data_conflicts ?? false,
|
|
763
745
|
}, { dryRun: args.dry_run });
|
|
@@ -765,7 +747,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
765
747
|
if (args.shapes === undefined) {
|
|
766
748
|
throw new Error("publish_schema requires a SHACL shapes source; use define_ontology or evolve_ontology for native metadata changes");
|
|
767
749
|
}
|
|
768
|
-
return scoped(client, args.graph
|
|
750
|
+
return scoped(client, args.graph).schema.publish({
|
|
769
751
|
ontology: args.ontology,
|
|
770
752
|
shapes: args.shapes,
|
|
771
753
|
desired_mode: args.desired_mode,
|
|
@@ -773,85 +755,4 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
773
755
|
}, { dryRun: args.dry_run });
|
|
774
756
|
});
|
|
775
757
|
});
|
|
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
758
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@littlebigbrain/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.
|
|
55
|
+
"@littlebigbrain/client": "^0.14.0",
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1",
|
|
57
57
|
"zod": "^3"
|
|
58
58
|
},
|