@littlebigbrain/mcp 0.9.0 → 0.11.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 +30 -7
- package/dist/query-tools.d.ts +89 -0
- package/dist/query-tools.js +208 -0
- package/dist/question.d.ts +33 -5
- package/dist/question.js +190 -24
- package/dist/tool-contracts.js +168 -10
- package/dist/tool-runtime.js +1 -1
- package/dist/tools.js +311 -11
- package/package.json +2 -2
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { metadataPage } from "./metadata-pages.js";
|
|
3
|
-
import { answerQuestion } from "./question.js";
|
|
3
|
+
import { answerQuestion, } from "./question.js";
|
|
4
|
+
import { commitAt, compareQuery, describeGraph, findNames, } from "./query-tools.js";
|
|
4
5
|
import { registerRdfTool } from "./rdf-tool.js";
|
|
5
6
|
import { queryTiming } from "./query-observer.js";
|
|
6
7
|
import { DESTRUCTIVE, IDEMPOTENT_WRITE, MUTATING, READ_ONLY, configureInputSchema, configureWireSchema, detailSchema, graphScope, inspectInputSchema, inspectWireSchema, jsonObjectSchema, queryInputSchema, queryWireSchema, searchFeedbackSchema, yearMonthSchema, } from "./tool-contracts.js";
|
|
@@ -63,10 +64,10 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
63
64
|
});
|
|
64
65
|
});
|
|
65
66
|
server.registerTool("lbb_query", {
|
|
66
|
-
description: "Analytical and expert reads. Modes: question (a question in plain words), structured (SPARQL-subset JSON body), sparql (SPARQL text), search (instances by meaning over every searchable class; filter narrows by class and relationship; every hit checked against the graph), analyze. Use mode=question when you have a question in plain words and no SPARQL query: the server
|
|
67
|
+
description: "Analytical and expert reads. Modes: question (a question in plain words), structured (SPARQL-subset JSON body), sparql (SPARQL text), search (instances by meaning over every searchable class; filter narrows by class and relationship; every hit checked against the graph), analyze, and four tools for writing your own queries: names, describe, commit_at, compare. names: before a query that names an entity (a person, a company), find its IRI with text=<the question or the names>; use the first candidate, not a CONTAINS match on the name. describe: before a query on classes or properties you have not seen, read them with question=<the question> (or classes/properties as IRIs): how many sampled instances hold each property (a filter on a rare one returns few rows), the values of small classes such as stages, examples and schema statements. commit_at: for a question about a date, find the commit of date=YYYY-MM-DD (or moment=RFC 3339), then run mode=sparql as_of_commit_seq=<it>. compare: for what changed between two points, run one SELECT at before and after (default the latest) with key=[the entity variable]; added, removed and changed come with totals and pages (next). Use mode=question when you have a question in plain words and no SPARQL query: the server picks the kind of question (route), runs queries in a bounded loop, reads their rows, and answers in plain words (about 8 s). The result holds the answer with citations and a chart hint (kind, x, y: columns of the rows), the steps, the route, and the query and rows the answer stands on. Continue or correct that query with mode=sparql. Each question uses model tokens and counts toward a daily limit of the stack. SPARQL is the query language; search finds what the words describe. SPARQL text can also search by meaning inside the query with ?x <https://littlebigbrain.com/search#similarTo> \"words\", so the other patterns filter and join the hits in one query; the result's search field reports the plan. To plan a search: lbb_embeddings action=list, then SPARQL for a class's relationships on a sample, then mode=search with explain=true to check the resolved filter before the real search (lbb_inspect action=guide has the queries). 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.",
|
|
67
68
|
inputSchema: queryWireSchema,
|
|
68
69
|
annotations: READ_ONLY,
|
|
69
|
-
}, (rawArgs) => {
|
|
70
|
+
}, (rawArgs, extra) => {
|
|
70
71
|
const parsed = queryInputSchema.safeParse(rawArgs);
|
|
71
72
|
if (!parsed.success)
|
|
72
73
|
return errorResult(parsed.error);
|
|
@@ -250,6 +251,10 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
250
251
|
if (search && !search.complete) {
|
|
251
252
|
notes.push(`search bound ${search.hits} of the ${search.top} hits asked for (complete: false): fewer entities satisfy the rest of the query among the candidates the search may score. When at most 20,000 entities match the other patterns, the search scores every match.`);
|
|
252
253
|
}
|
|
254
|
+
const rerank = search?.rerank ?? undefined;
|
|
255
|
+
if (rerank && rerank.status !== "applied") {
|
|
256
|
+
notes.push(`search:rerank did not apply (${rerank.status}${rerank.error ? `: ${rerank.error}` : ""}): the rows hold the most similar hits, and the relevance variable is unbound.`);
|
|
257
|
+
}
|
|
253
258
|
const data = timing.measure("query_results_parse", () => JSON.parse(response.results));
|
|
254
259
|
const rowPage = rowPageFrom(response);
|
|
255
260
|
const cursorBase = {
|
|
@@ -275,9 +280,44 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
275
280
|
}
|
|
276
281
|
if (args.mode === "question") {
|
|
277
282
|
const questionArgs = args;
|
|
283
|
+
// A call with a progress token gets one notification per step of
|
|
284
|
+
// the server's work; the result is the same.
|
|
285
|
+
const progressToken = extra._meta?.progressToken;
|
|
286
|
+
const progress = progressToken === undefined
|
|
287
|
+
? undefined
|
|
288
|
+
: {
|
|
289
|
+
signal: extra.signal,
|
|
290
|
+
report: (progress, message) => extra.sendNotification({
|
|
291
|
+
method: "notifications/progress",
|
|
292
|
+
params: { progressToken, progress, message },
|
|
293
|
+
}),
|
|
294
|
+
};
|
|
295
|
+
return (async () => {
|
|
296
|
+
try {
|
|
297
|
+
return await answerQuestion(client, questionArgs, options.queryTextFormat, progress);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
return errorResult(await enrichError(client, error));
|
|
301
|
+
}
|
|
302
|
+
})();
|
|
303
|
+
}
|
|
304
|
+
if (args.mode === "names" ||
|
|
305
|
+
args.mode === "describe" ||
|
|
306
|
+
args.mode === "commit_at" ||
|
|
307
|
+
args.mode === "compare") {
|
|
308
|
+
const toolArgs = args;
|
|
278
309
|
return (async () => {
|
|
279
310
|
try {
|
|
280
|
-
|
|
311
|
+
switch (args.mode) {
|
|
312
|
+
case "names":
|
|
313
|
+
return await findNames(client, toolArgs);
|
|
314
|
+
case "describe":
|
|
315
|
+
return await describeGraph(client, toolArgs);
|
|
316
|
+
case "commit_at":
|
|
317
|
+
return await commitAt(client, toolArgs);
|
|
318
|
+
default:
|
|
319
|
+
return await compareQuery(client, toolArgs);
|
|
320
|
+
}
|
|
281
321
|
}
|
|
282
322
|
catch (error) {
|
|
283
323
|
return errorResult(await enrichError(client, error));
|
|
@@ -460,8 +500,242 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
460
500
|
}
|
|
461
501
|
return scoped(client, graph).embeddings.delete(name);
|
|
462
502
|
}));
|
|
503
|
+
// Fit from text: fit sources declared on classes, like embeddings.
|
|
504
|
+
const fitSetup = {
|
|
505
|
+
class: z
|
|
506
|
+
.string()
|
|
507
|
+
.optional()
|
|
508
|
+
.describe("preview/declare/dry_run: the class IRI whose instances hold the text."),
|
|
509
|
+
from: z
|
|
510
|
+
.array(z.string())
|
|
511
|
+
.optional()
|
|
512
|
+
.describe("preview/declare/dry_run: the fields that hold the text, as embedding paths (transcript, label, about/label, <https://…>). Omit for the long text facts of the class."),
|
|
513
|
+
exclude: z
|
|
514
|
+
.array(z.string())
|
|
515
|
+
.optional()
|
|
516
|
+
.describe("preview/declare/dry_run: fields to drop from the automatic choice."),
|
|
517
|
+
context: z
|
|
518
|
+
.string()
|
|
519
|
+
.optional()
|
|
520
|
+
.describe('preview/declare/dry_run: one sentence about the text, e.g. "interviews with employees about their work processes" (at most 500 characters).'),
|
|
521
|
+
};
|
|
522
|
+
const fitDeclaration = (args) => {
|
|
523
|
+
if (!args.class)
|
|
524
|
+
throw new Error("this action requires class");
|
|
525
|
+
return {
|
|
526
|
+
class: args.class,
|
|
527
|
+
...(args.name ? { name: args.name } : {}),
|
|
528
|
+
...(args.from ? { from: args.from } : {}),
|
|
529
|
+
...(args.exclude ? { exclude: args.exclude } : {}),
|
|
530
|
+
...(args.context ? { context: args.context } : {}),
|
|
531
|
+
};
|
|
532
|
+
};
|
|
533
|
+
server.registerTool("lbb_fit_sources", {
|
|
534
|
+
description: "Fit from text, read only: fit sources declared on classes. A fit source names the fields of a class that hold text (transcripts, documents); the server reads every instance, proposes ontology changes (classes, properties, relations) with verbatim quotes, checks them, and files them as ontology suggestions with origin id fit:<name>. list shows each source's status (progress, lag, instances read, proposals kept and dropped, suggestions per status, this month's spend, last error) and the stack's monthly budget; get shows one; preview shows the fields, every candidate field of the class and the text of sample instances, calls no model and stores nothing. Declare, refresh or dry-run with lbb_fit_sources_manage; list the suggestions with lbb_inspect action=ontology_suggestions.",
|
|
535
|
+
inputSchema: {
|
|
536
|
+
action: z.enum(["list", "get", "preview"]),
|
|
537
|
+
name: z.string().optional().describe("get: the fit source name."),
|
|
538
|
+
...fitSetup,
|
|
539
|
+
sample: z.number().int().positive().max(20).optional(),
|
|
540
|
+
iris: z
|
|
541
|
+
.array(z.string())
|
|
542
|
+
.optional()
|
|
543
|
+
.describe("preview: the instances to show instead of a sample."),
|
|
544
|
+
detail: detailSchema,
|
|
545
|
+
...graphScope,
|
|
546
|
+
},
|
|
547
|
+
annotations: READ_ONLY,
|
|
548
|
+
}, (args) => run(client, `lbb_fit_sources.${args.action}`, args.detail, () => {
|
|
549
|
+
const fit = scoped(client, args.graph).ontology.fitSources;
|
|
550
|
+
switch (args.action) {
|
|
551
|
+
case "list":
|
|
552
|
+
return fit.list();
|
|
553
|
+
case "get":
|
|
554
|
+
if (!args.name)
|
|
555
|
+
throw new Error("get requires name");
|
|
556
|
+
return fit.get(args.name);
|
|
557
|
+
case "preview":
|
|
558
|
+
return fit.preview({
|
|
559
|
+
...fitDeclaration(args),
|
|
560
|
+
...(args.sample ? { sample: args.sample } : {}),
|
|
561
|
+
...(args.iris ? { iris: args.iris } : {}),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}));
|
|
565
|
+
server.registerTool("lbb_fit_sources_manage", {
|
|
566
|
+
description: "Declare, dry-run or refresh a fit source (fit from text). Run lbb_fit_sources action=preview first and show the user the text the fit reads. dry_run runs the models on the first part of up to 3 instances (at most 60 s; truncated when one did not finish) and returns every proposal with its quote, its support score and what the checks made of it; it files nothing but spends model budget. declare sets the class, the fields and the context; the job then reads every instance and files suggestions (an existing source changes only what you name). refresh asks the job to run now and returns the status at once; the job also runs by itself after each published commit, so poll lbb_fit_sources action=get for progress. Nothing changes the ontology until a person accepts a suggestion.",
|
|
567
|
+
inputSchema: {
|
|
568
|
+
action: z.enum(["declare", "dry_run", "refresh"]),
|
|
569
|
+
name: z
|
|
570
|
+
.string()
|
|
571
|
+
.optional()
|
|
572
|
+
.describe("The fit source name (refresh: required)."),
|
|
573
|
+
...fitSetup,
|
|
574
|
+
detail: detailSchema,
|
|
575
|
+
...graphScope,
|
|
576
|
+
},
|
|
577
|
+
annotations: MUTATING,
|
|
578
|
+
}, (args) => run(client, `lbb_fit_sources_manage.${args.action}`, args.detail, () => {
|
|
579
|
+
const fit = scoped(client, args.graph).ontology.fitSources;
|
|
580
|
+
switch (args.action) {
|
|
581
|
+
case "declare":
|
|
582
|
+
return fit.declare(fitDeclaration(args));
|
|
583
|
+
case "dry_run":
|
|
584
|
+
return fit.preview({ ...fitDeclaration(args), propose: true });
|
|
585
|
+
case "refresh":
|
|
586
|
+
if (!args.name)
|
|
587
|
+
throw new Error("refresh requires name");
|
|
588
|
+
return fit.refresh(args.name);
|
|
589
|
+
}
|
|
590
|
+
}));
|
|
591
|
+
server.registerTool("lbb_fit_sources_delete", {
|
|
592
|
+
description: "Delete a fit source: the fit stops reading its class. The suggestions it filed stay. Ask the user first; confirm must repeat the name.",
|
|
593
|
+
inputSchema: {
|
|
594
|
+
name: z.string().describe("The fit source name."),
|
|
595
|
+
confirm: z.string().describe("The same name again, to confirm."),
|
|
596
|
+
detail: detailSchema,
|
|
597
|
+
...graphScope,
|
|
598
|
+
},
|
|
599
|
+
annotations: DESTRUCTIVE,
|
|
600
|
+
}, ({ name, confirm, detail, graph }) => run(client, "lbb_fit_sources_delete", detail, () => {
|
|
601
|
+
if (confirm !== name) {
|
|
602
|
+
throw new Error("confirm must repeat the fit source name");
|
|
603
|
+
}
|
|
604
|
+
return scoped(client, graph).ontology.fitSources.delete(name);
|
|
605
|
+
}));
|
|
606
|
+
const modelUse = z
|
|
607
|
+
.enum(["ask", "route", "rerank", "fit", "label"])
|
|
608
|
+
.describe("A use of a model: ask = question answers, route = query routing, rerank = search rerank, fit = ontology fit, label = eval labels.");
|
|
609
|
+
server.registerTool("lbb_model_choice", {
|
|
610
|
+
description: "Model choice, read only: which model each use of a model on the graph runs, and trials that test other models against the graph's ground truth (the checked calls: a person's review, else the judge's verdict). options shows per use the model it runs now (current), LBB's model (default), a switch (switched), its checked calls, and the models a trial can test with their efforts, prices and whether the server holds their key; available=false with a reason when trials cannot run. trials lists the trials, newest first, each with its report: calls compared, both models' right share, mean score (0 to 1), cost per call and median time, the difference with its 95% interval (delta, ci_low, ci_high), outcome (too_few, better, same, worse) and qualifies (meets the bar: at least 20 calls, at most 5 points worse at the low end, no more failures, cheaper or better). trial reads one trial with its compared calls; trial_call reads one call with both answers and the ground truth. switches lists the switched uses. Start, stop, switch or revert with lbb_model_choice_manage.",
|
|
611
|
+
inputSchema: {
|
|
612
|
+
action: z.enum([
|
|
613
|
+
"options",
|
|
614
|
+
"trials",
|
|
615
|
+
"trial",
|
|
616
|
+
"trial_call",
|
|
617
|
+
"switches",
|
|
618
|
+
]),
|
|
619
|
+
trial_id: z
|
|
620
|
+
.string()
|
|
621
|
+
.optional()
|
|
622
|
+
.describe("trial / trial_call: the trial id."),
|
|
623
|
+
call_id: z
|
|
624
|
+
.string()
|
|
625
|
+
.optional()
|
|
626
|
+
.describe("trial_call: a call id from the trial's calls."),
|
|
627
|
+
job: modelUse
|
|
628
|
+
.optional()
|
|
629
|
+
.describe("trials: only the trials of this use."),
|
|
630
|
+
limit: z
|
|
631
|
+
.number()
|
|
632
|
+
.int()
|
|
633
|
+
.positive()
|
|
634
|
+
.max(50)
|
|
635
|
+
.optional()
|
|
636
|
+
.describe("trials: at most this many, newest first (default 20)."),
|
|
637
|
+
detail: detailSchema,
|
|
638
|
+
...graphScope,
|
|
639
|
+
},
|
|
640
|
+
annotations: READ_ONLY,
|
|
641
|
+
}, (args) => run(client, `lbb_model_choice.${args.action}`, args.detail, () => {
|
|
642
|
+
const models = scoped(client, args.graph).models;
|
|
643
|
+
switch (args.action) {
|
|
644
|
+
case "options":
|
|
645
|
+
return models.trials.options();
|
|
646
|
+
case "trials":
|
|
647
|
+
return models.trials.list({ job: args.job, limit: args.limit });
|
|
648
|
+
case "trial":
|
|
649
|
+
if (!args.trial_id)
|
|
650
|
+
throw new Error("trial requires trial_id");
|
|
651
|
+
return models.trials.get(args.trial_id);
|
|
652
|
+
case "trial_call":
|
|
653
|
+
if (!args.trial_id || !args.call_id) {
|
|
654
|
+
throw new Error("trial_call requires trial_id and call_id");
|
|
655
|
+
}
|
|
656
|
+
return models.trials.call(args.trial_id, args.call_id);
|
|
657
|
+
case "switches":
|
|
658
|
+
return models.switches.list();
|
|
659
|
+
}
|
|
660
|
+
}));
|
|
661
|
+
server.registerTool("lbb_model_choice_manage", {
|
|
662
|
+
description: "Model choice, writes. start tests a candidate model on the uses in jobs (without jobs, every use it can do that has checked calls): the server answers those checked calls again with the candidate and scores it and the model in use against the same ground truth. It changes no model and spends at most the trial budget ($3.00 by default) of model and judge cost; an open trial of the same use and candidate comes back as it is. Pick candidates from lbb_model_choice action=options (provider anthropic with an effort, or typesafe jev-latest). A trial runs by itself: poll lbb_model_choice action=trial until its status is no longer running. stop ends a trial; it keeps what it compared. switch makes the trial's use run its candidate on this graph from the next call on; only a trial that qualifies and compared with the model in use switches (ask to a Claude model, route to a Jev model). Show the user the trial's report (score, difference and interval, cost per call, time) and switch only what the user confirmed. revert puts the use back on LBB's model.",
|
|
663
|
+
inputSchema: {
|
|
664
|
+
action: z.enum(["start", "stop", "switch", "revert"]),
|
|
665
|
+
provider: z
|
|
666
|
+
.enum(["anthropic", "typesafe"])
|
|
667
|
+
.optional()
|
|
668
|
+
.describe("start: the candidate's provider."),
|
|
669
|
+
model: z
|
|
670
|
+
.string()
|
|
671
|
+
.optional()
|
|
672
|
+
.describe("start: the candidate model id, e.g. claude-haiku-5-5 or jev-latest."),
|
|
673
|
+
effort: z
|
|
674
|
+
.enum(["low", "medium", "high", "xhigh", "max"])
|
|
675
|
+
.optional()
|
|
676
|
+
.describe("start: the effort of a Claude candidate."),
|
|
677
|
+
jobs: z
|
|
678
|
+
.array(modelUse)
|
|
679
|
+
.optional()
|
|
680
|
+
.describe("start: the uses to test it on."),
|
|
681
|
+
target: z
|
|
682
|
+
.number()
|
|
683
|
+
.int()
|
|
684
|
+
.positive()
|
|
685
|
+
.max(100)
|
|
686
|
+
.optional()
|
|
687
|
+
.describe("start: the calls to compare (default 40)."),
|
|
688
|
+
days: z
|
|
689
|
+
.number()
|
|
690
|
+
.int()
|
|
691
|
+
.positive()
|
|
692
|
+
.max(30)
|
|
693
|
+
.optional()
|
|
694
|
+
.describe("start: the days the trial takes new checks (default 14)."),
|
|
695
|
+
trial_id: z
|
|
696
|
+
.string()
|
|
697
|
+
.optional()
|
|
698
|
+
.describe("stop / switch: the trial id."),
|
|
699
|
+
job: modelUse
|
|
700
|
+
.optional()
|
|
701
|
+
.describe("revert: the use to put back on LBB's model."),
|
|
702
|
+
detail: detailSchema,
|
|
703
|
+
...graphScope,
|
|
704
|
+
},
|
|
705
|
+
annotations: MUTATING,
|
|
706
|
+
}, (args) => run(client, `lbb_model_choice_manage.${args.action}`, args.detail, () => {
|
|
707
|
+
const models = scoped(client, args.graph).models;
|
|
708
|
+
switch (args.action) {
|
|
709
|
+
case "start":
|
|
710
|
+
if (!args.provider || !args.model) {
|
|
711
|
+
throw new Error("start requires provider and model");
|
|
712
|
+
}
|
|
713
|
+
return models.trials.create({
|
|
714
|
+
candidate: {
|
|
715
|
+
provider: args.provider,
|
|
716
|
+
model: args.model,
|
|
717
|
+
...(args.effort ? { effort: args.effort } : {}),
|
|
718
|
+
},
|
|
719
|
+
...(args.jobs ? { jobs: args.jobs } : {}),
|
|
720
|
+
...(args.target ? { target: args.target } : {}),
|
|
721
|
+
...(args.days ? { days: args.days } : {}),
|
|
722
|
+
});
|
|
723
|
+
case "stop":
|
|
724
|
+
if (!args.trial_id)
|
|
725
|
+
throw new Error("stop requires trial_id");
|
|
726
|
+
return models.trials.stop(args.trial_id);
|
|
727
|
+
case "switch":
|
|
728
|
+
if (!args.trial_id)
|
|
729
|
+
throw new Error("switch requires trial_id");
|
|
730
|
+
return models.switches.create({ trial: args.trial_id });
|
|
731
|
+
case "revert":
|
|
732
|
+
if (!args.job)
|
|
733
|
+
throw new Error("revert requires job");
|
|
734
|
+
return models.switches.revert(args.job);
|
|
735
|
+
}
|
|
736
|
+
}));
|
|
463
737
|
server.registerTool("lbb_evals", {
|
|
464
|
-
description: "Managed evals: the
|
|
738
|
+
description: "Managed evals: ground truth for the questions an app asks and the searches it runs. A query run with `request` (lbb_query, also mode=question) records a trace: the question, the query that answered it, the type of that query (`query_type`: sparql, hybrid = search by meaning with conditions, search = search by meaning alone) and one item per result (hit or row); trace reads it back with the item ids. label has two forms. On a question's trace, valid alone judges the whole answer: valid=true makes the trace's query the golden query of the question; valid=false with sparql gives the right query, which becomes the golden query; valid=false alone marks the answer wrong. item + valid (or items) judges one result, a citation of the answer: the ground truth of the hits of a search by meaning, and a result marked wrong must not come back for any type. golden freezes a stored query or a search (every result it returns now is relevant). run checks every golden at the current commit: a question is asked again through the query rewriter, then the type of the query it wrote is compared with the expected type, its rows with the rows of the golden query at the same commit (`query_check`), and its results with the judged results; a search is done again; 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. Model checks: a judge model checks a sample of the model calls LBB makes for the graph (rerank, route, ask = the answer of a question, rewrite, fit, propose, label). checks_summary reads a month per job and model (checks, score, right, partly, wrong, reviews) and the judge's agreement with people; checks lists the month's checks, newest first, with the judge's verdict, score and reason and the ground truth (`truth`). review_check records a person's review of one check (call_id): agree=true keeps the judge's verdict; agree=false with verdict (and an optional score, reference and note) corrects it. The review becomes the call's ground truth, so show the check to the user and review only what the user confirmed.",
|
|
465
739
|
inputSchema: {
|
|
466
740
|
action: z.enum([
|
|
467
741
|
"summary",
|
|
@@ -491,7 +765,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
491
765
|
valid: z
|
|
492
766
|
.boolean()
|
|
493
767
|
.optional()
|
|
494
|
-
.describe("label: true = the result answers the request (thumbs up), false = it does not."),
|
|
768
|
+
.describe("label: true = the result answers the request (thumbs up), false = it does not. Without item or items, on a question's trace: the whole answer is right (true) or wrong (false)."),
|
|
495
769
|
items: z
|
|
496
770
|
.array(z
|
|
497
771
|
.object({
|
|
@@ -513,7 +787,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
513
787
|
sparql: z
|
|
514
788
|
.string()
|
|
515
789
|
.optional()
|
|
516
|
-
.describe("golden: the query to freeze (the search text for surface=search)."),
|
|
790
|
+
.describe("golden: the query to freeze (the search text for surface=search). label: with valid=false and no item, the right query for the question; the server checks and runs it once."),
|
|
517
791
|
surface: z
|
|
518
792
|
.enum(["sparql", "search"])
|
|
519
793
|
.optional()
|
|
@@ -550,7 +824,15 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
550
824
|
.optional()
|
|
551
825
|
.describe("checks_summary / checks: the month, yyyy-mm (UTC). Defaults to the current month."),
|
|
552
826
|
job: z
|
|
553
|
-
.enum([
|
|
827
|
+
.enum([
|
|
828
|
+
"rerank",
|
|
829
|
+
"route",
|
|
830
|
+
"ask",
|
|
831
|
+
"rewrite",
|
|
832
|
+
"fit",
|
|
833
|
+
"propose",
|
|
834
|
+
"label",
|
|
835
|
+
])
|
|
554
836
|
.optional()
|
|
555
837
|
.describe("checks: only the checks of one job."),
|
|
556
838
|
verdict: z
|
|
@@ -600,13 +882,19 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
600
882
|
case "label":
|
|
601
883
|
if (!trace_id)
|
|
602
884
|
throw new Error("label requires trace_id");
|
|
603
|
-
if (!item && !items?.length)
|
|
604
|
-
throw new Error("label requires item (with valid) or items");
|
|
605
885
|
if (item && valid === undefined)
|
|
606
886
|
throw new Error("label requires valid with item");
|
|
887
|
+
if (!item && !items?.length && valid === undefined)
|
|
888
|
+
throw new Error("label requires item (with valid), items, or valid alone for the answer of a question");
|
|
889
|
+
if (sparql !== undefined &&
|
|
890
|
+
(item || items?.length || valid !== false))
|
|
891
|
+
throw new Error("label takes sparql (the right query) only with valid=false and no item");
|
|
607
892
|
return target.evals.label(trace_id, {
|
|
608
893
|
...(item ? { item, valid } : {}),
|
|
609
894
|
...(items?.length ? { items } : {}),
|
|
895
|
+
// The answer of a question as a whole.
|
|
896
|
+
...(!item && !items?.length ? { valid } : {}),
|
|
897
|
+
...(sparql !== undefined ? { sparql } : {}),
|
|
610
898
|
by,
|
|
611
899
|
note,
|
|
612
900
|
});
|
|
@@ -789,7 +1077,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
789
1077
|
});
|
|
790
1078
|
}));
|
|
791
1079
|
server.registerTool("lbb_configure", {
|
|
792
|
-
description: "Manage native schema metadata. Actions: define_ontology (friendly spec with super_types), evolve_ontology (ordered edits including add_super_types), list_starters (the base ontologies crm, documents and work, each with its status on the graph: absent, partial or applied, what applying adds, and conflicts), apply_starter (add what the graph lacks of a starter in one ontology version; a relation the graph has is widened; refused with starter_conflict when the graph holds a term differently; dry_run previews), publish_schema (SHACL activation), suggest_ontology_change (file a change for a person to review instead of applying it; prefer it when the graph's owner reviews ontology changes, and list the result with lbb_inspect action=ontology_suggestions). define, evolve and
|
|
1080
|
+
description: "Manage native schema metadata. Actions: define_ontology (friendly spec with super_types), evolve_ontology (ordered edits including add_super_types), list_starters (the base ontologies crm, documents and work, each with its status on the graph: absent, partial or applied, what applying adds, and conflicts), apply_starter (add what the graph lacks of a starter in one ontology version; a relation the graph has is widened; refused with starter_conflict when the graph holds a term differently; dry_run previews), publish_schema (SHACL activation), suggest_ontology_change (file a change for a person to review instead of applying it; prefer it when the graph's owner reviews ontology changes, and list the result with lbb_inspect action=ontology_suggestions), get_rewrite_profile and set_rewrite_profile (the graph's notes and up to 20 worked question-to-SPARQL examples that lbb_query mode=question reads for every question; pass the version you read as expected_version). define, evolve, publish and set_rewrite_profile support dry_run previews. Definition/import here extracts native metadata; it does NOT store the complete RDF/OWL document as queryable graph facts. Use lbb_rdf import for full OWL and lbb_rdf update for additive INSERT DATA revisions; RDF deletions are unsupported. Publish_schema accepts unchanged ontology plus shapes; use define/evolve for native ontology changes. Publication enqueues durable conformance; a preview does not validate the whole graph.",
|
|
793
1081
|
inputSchema: configureWireSchema,
|
|
794
1082
|
annotations: MUTATING,
|
|
795
1083
|
}, (rawArgs) => {
|
|
@@ -823,6 +1111,18 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
823
1111
|
if (args.action === "list_starters") {
|
|
824
1112
|
return scoped(client, args.graph).ontology.starters.list();
|
|
825
1113
|
}
|
|
1114
|
+
if (args.action === "get_rewrite_profile") {
|
|
1115
|
+
return scoped(client, args.graph).query.rewriteProfile();
|
|
1116
|
+
}
|
|
1117
|
+
if (args.action === "set_rewrite_profile") {
|
|
1118
|
+
return scoped(client, args.graph).query.setRewriteProfile({
|
|
1119
|
+
notes: args.notes ?? "",
|
|
1120
|
+
examples: args.examples ?? [],
|
|
1121
|
+
...(args.expected_version !== undefined
|
|
1122
|
+
? { expected_version: args.expected_version }
|
|
1123
|
+
: {}),
|
|
1124
|
+
}, { dryRun: args.dry_run });
|
|
1125
|
+
}
|
|
826
1126
|
if (args.action === "apply_starter") {
|
|
827
1127
|
return scoped(client, args.graph).ontology.starters.apply(args.starter, {
|
|
828
1128
|
dryRun: args.dry_run,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@littlebigbrain/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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.22.0",
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1",
|
|
57
57
|
"zod": "^3"
|
|
58
58
|
},
|