@littlebigbrain/mcp
This server exposes eight MCP tools for searching, querying, writing, and evolving a Little Big Brain knowledge graph, with hosted OAuth and local stdio options.
lbb_inspect — explore graphs, ontology/schema metadata, entity details, states, history, provenance, and publication status.
lbb_rdf — import complete RDF/OWL documents (Turtle, N-Triples, N-Quads, TriG) or add axioms via SPARQL INSERT DATA.
lbb_query — run SPARQL text or structured queries, including filters, grouping, aggregation, entailment, snapshot pinning, and paged analysis.
lbb_commit — write facts, typed entity properties, and embeddings; retract mistaken edges/entities; store search relevance feedback.
lbb_observe — store conversation episodes verbatim and commit reviewed/extracted facts on an isolated branch, with optional auto-merge.
lbb_branch — create isolation branches and validate-then-merge child branches back onto their fork parent.
lbb_models — read model-training datasets or run shadow evaluations over a pinned published snapshot.
lbb_configure — define/evolve native ontology metadata, add relations/properties/supertypes, and preview or publish SHACL schema validation.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@littlebigbrain/mcpsearch for entities related to 'Project Apollo'"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@littlebigbrain/mcp
Eight task-shaped MCP tools that let Claude, Cursor, Codex, or any MCP client search, query, and write a Little Big Brain graph. Ships two ways: a hosted endpoint with OAuth sign-in, and a local stdio server.
Hosted (OAuth) — recommended
The client opens WorkOS sign-in; your machine never stores a Little Big Brain key. Point it at your stack:
{
"mcpServers": {
"lbb": {
"url": "https://mcp.littlebigbrain.com/mcp/<stack-slug>"
}
}
}Codex sends the URL as an OAuth resource, so use the origin plus a stack header instead:
{
"mcpServers": {
"lbb": {
"type": "http",
"url": "https://mcp.littlebigbrain.com",
"headers": { "X-LBB-Stack": "<stack-slug>" }
}
}
}Related MCP server: RDF4J MCP Server
Local (stdio)
Run against any data-plane endpoint with a stack API key:
{
"mcpServers": {
"lbb": {
"command": "npx",
"args": ["-y", "@littlebigbrain/mcp"],
"env": {
"LBB_BASE_URL": "https://0abc1def--production.db.eu.littlebigbrain.com",
"LBB_API_KEY": "lbb_sk_live_..."
}
}
}
}Set LBB_GRAPH or LBB_BRANCH to target a scope other than main.
LBB_BASE_URL has no hosted default: copy endpoint_url from the stack's
Connect page. The MCP process exits with a configuration error when it is
missing.
Tools
Tool | Use it for |
| graph discovery, complete paginated ontology/schema, publication status, entity, state, history, and provenance |
| import full RDF/OWL or add axioms using INSERT DATA |
| SPARQL text, structured SPARQL bodies, and canned analysis |
| facts, properties, and embeddings |
| conversation episodes plus reviewed extraction |
| isolation branches and validated merge |
| shadow evaluation and training datasets |
| native ontology definition/evolution and SHACL preview/publication |
Read tools return compact structured envelopes by default — use detail, row_limit, and returned cursors to page without silently truncating. Write tools derive an idempotency key unless you provide one.
Query pages preserve complete RDF values and may contain fewer than row_limit
rows to fit the 80 KB UTF-8 text budget. Query text uses compact JSON, with the
same complete data in structuredContent. The full transport response includes
both representations and can exceed 80 KB. Follow the returned next arguments
until absent; the cursor advances by rows actually delivered. A single row that
exceeds the budget fails explicitly: project fewer fields or use the direct
SPARQL HTTP endpoint for that row.
Both lbb_query SPARQL modes (sparql and structured) support retained commit reads
through as_of_commit_seq. When omitted, the connector pins the current head
commit and reuses it for cursor pages. Valid-time as_of is unsupported and is
rejected before an API call, including when carried in an old cursor. Start a
new query without that selector or choose a retained commit sequence.
Create and evolve an ontology through MCP
Start with lbb_inspect action=guide; action=graphs helps select an existing
scope, and a missing graph returns bootstrap guidance. Decide what questions
the graph must answer before choosing classes and relations. Distinguish
source-backed facts from hypotheses, and preserve evidence and dates.
Native metadata and stored RDF axioms are separate:
lbb_configure action=define_ontologyaccepts a friendlyspec, including classsuper_types. Unknown spec fields fail explicitly.lbb_jsonexpects an internal serialized ontology, not a friendly spec. Raw OWL supplied to configure is reduced to native metadata; it is not stored as a full document.lbb_rdf action=importstores the complete Turtle, N-Triples, N-Quads, or TriG document as queryable graph facts, including RDF lists, annotations, and OWL axioms. Passsource; the published RDF tier supports only the default RDF graph. Dataset formats must contain only default-graph quads. The first RDF data write selects RDF-native storage, which refuses later property-graph commits; choose the write workflow before bootstrap.lbb_configure action=evolve_ontologysupports explicit native changes, includingadd_super_types.dry_run: truepreviews define/evolve/publish; the same flag previewslbb_commit mode=factswithout writing.lbb_rdf action=updatesubmits SPARQL Update unchanged; currently onlyINSERT DATAis supported. DELETE, WHERE, and graph replacement are refused. Re-importing is additive and does not remove obsolete axioms. Content-based retry keys are automatic; use a new explicit key for an intentional repeat after other edits.
For example, add a superclass without a browser or RDF conversion:
{
"action": "update",
"update": "INSERT DATA { <urn:Person> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <urn:Contact> }"
}Removing or replacing RDF axioms requires native bounded update support in the engine. Until then, import a revised document into a new versioned LBB graph, verify it, and explicitly switch consumers. Do not implicitly delete the original.
publish_schema activates SHACL shapes against unchanged native metadata.
Its preview checks parsing and compatibility without writing objects or
scheduling jobs; it does not audit the entire graph. Preview restrictive
native edits with evolve, resolve conflicts, then apply. For restrictive
SHACL, use warn → inspect conformance → repair → reject.
After applying, inspect action=publication, then verify both asserted axioms
and expected inferred answers. lbb_query mode=sparql accepts explicit
entailment: "none" | "subclass" | "rdfs" | "owl" (default none),
consistency: "eventual" | "strong", and min_indexed_seq. Cursors retain
these controls. OWL is the server's supported inference profile, not arbitrary
OWL DL. An upload acknowledgement is not proof of successful reasoning.
To read asserted axioms in the default graph, query with entailment: "none"
and follow all returned row cursors:
SELECT ?s ?p ?o WHERE {
?s ?p ?o
} ORDER BY ?s ?p ?olbb_inspect action=ontology and action=schema page complete native metadata
with page_size (default 50, maximum 500), optional section, and cursor.
Pass the returned next arguments until absent. These pages preserve nested
values even with detail=compact; they never replace the remainder with a
suggestion to repeat detail=full. If a single entry is too large, the result
contains entry_fragment: concatenate serialized_json by char_offset, then
JSON-parse the completed entry. Changed metadata invalidates the cursor rather
than mixing versions. Restart inspection after applying edits.
Embed the server
For self-hosting behind your own auth, the package also serves the tools over HTTP:
import { createMcpHttpServer } from "@littlebigbrain/mcp";
createMcpHttpServer({
baseUrl: "https://0abc1def--production.db.eu.littlebigbrain.com",
mcpPath: "/mcp",
allowedHosts: ["127.0.0.1", "localhost", "::1"],
}).listen(8080, "127.0.0.1");The embedded server passes a key bearer to the data plane; the hosted endpoint's OAuth and ownership layer is served separately by the Little Big Brain API.
When using buildLbbServer(client, options) directly, queryTextFormat: "pretty"
restores indented query text; the default is "compact". Both formats use the
same formatter for page sizing and rendering. Optional timing.observe receives
bounded query-stage durations and counts, with no query text or RDF values.
No logger is installed by default, including on stdio. Timing observer failures
do not affect tool results.
Full tool schemas and examples: docs.littlebigbrain.com/sdks/mcp.
Local end-to-end ontology check
From the repository root, build the server and SDKs, then opt into the isolated real-server MCP test (it creates and removes its own temporary data root):
cargo build -p lbb-server
npm run build -w @littlebigbrain/client
LBB_TEST_SERVER_BIN="$PWD/target/debug/lbb-server" npm test -w @littlebigbrain/mcpThe test verifies native hierarchy evolution, preservation of RDF annotations, subclass/inverse inference, additive edits, and refusal of unsupported deletion.
Available Tools
8 toolslbb_branchA
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).
| Name | Required | Description | Default |
|---|---|---|---|
| graph | No | Graph to target; defaults to the connection's graph | |
| action | Yes | create = fork a new branch; merge = replay a child branch onto its fork parent | |
| branch | No | Branch to target; defaults to the connection's branch | |
| validate | No | merge only: refuse on SHACL violations of the would-be merged state (default true) | |
| from_branch | Yes | create: the branch to fork from; merge: the child branch whose commits are replayed | |
| delete_source | No | merge only: delete every object under the merged branch after success |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description reveals critical behavioral traits: merge replays as one commit, SHACL validation is performed, conflicts are reported, and delete_source is destructive. This goes beyond the annotations which only indicate readOnlyHint=false, destructiveHint=false. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, with one dense paragraph covering all aspects. It front-loads with 'Branch lifecycle' and then details actions. While packed with information, it remains clear and avoids redundancy, though slightly more structure could improve readability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (6 parameters, 2 required, no output schema), the description provides comprehensive coverage: actions, parameter roles, validation behavior, conflict handling, and destructiveness. It equips an agent to use the tool correctly without missing critical context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
All 6 parameters have schema descriptions (100% coverage). The tool description adds extra context for parameters like 'from_branch' (explains role in create vs. merge) and 'delete_source' (explains it consumes the branch), enhancing understanding beyond the schema definitions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly specifies the tool handles the branch lifecycle with two distinct actions ('create' and 'merge'), each explained with sufficient detail. It differentiates from sibling tools by focusing on branching operations, while siblings like lbb_query or lbb_commit serve different purposes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains when to use create vs. merge, including specifics like 'merge: replay a child branch onto its fork parent' and 'delete_source consumes the merged branch.' It does not explicitly state when not to use it or mention alternatives, but the context of branching is well-defined.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lbb_commitAIdempotent
Write graph facts, retract them, or label ranked results. mode=facts writes triplets/embeddings/properties; mode=retract removes a wrongly-added fact (by edge or by entity) without a full reset; mode=search_feedback stores query/result relevance labels (Feedback grades: 3=ideal/good, 1=partial, 0=bad; include query, search_id when available, target, rank, score). Explicit idempotency_key wins; when omitted, MCP derives a stable content hash so content-identical retries dedupe. Facts mode defaults edge_idempotency to append; pass skip_unchanged for re-runnable backfills.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | ||
| graph | No | Graph to target; defaults to the connection's graph | |
| branch | No | Branch to target; defaults to the connection's branch | |
| dry_run | No | Validate a facts commit and return its structured SHACL report without writing. Only supported for mode=facts. | |
| triplets | No | ||
| observed_at | No | Backfill timestamp (RFC3339). Records this commit AS OF that instant: stamps transaction time and defaults each triplet's valid_time.start. Replay history in order with observed_at per commit so as-of reads by date work. Omit for live writes. | |
| retract_edges | No | mode=retract: specific edges to remove, matched by (source, relation, target). | |
| idempotency_key | No | ||
| search_feedback | No | ||
| edge_idempotency | No | Defaults to append in MCP. Use skip_unchanged for backfills; it skips exact current-edge duplicates and drops evidence-only repeats. | |
| retract_entities | No | mode=retract: entities whose every current edge is removed (a current-state tombstone; the record and its history are kept for as_of reads). | |
| entity_embeddings | No | ||
| entity_properties | No | Typed scalar attributes per entity. Each item is { type, name, properties }. `properties` is a flat map of field -> value, e.g. { "type": "PERSON", "name": "Ada Lovelace", "properties": { "h_index": 52, "title": "VP", "last_contact": "2026-06-26" } }. Values are coerced to each field's declared type, so a string like "2026-06-26" lands in a date_time field and "52" in an i64 field. (The verbose form [{ field, value: { i64: 52 } }] is also accepted.) Register a field first with lbb_configure evolve_ontology add_property; the commit response echoes written_properties so you can confirm what landed. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses meaningful behavior: explicit idempotency_key wins, otherwise MCP derives a stable content hash for dedupe, facts mode defaults edge_idempotency to append, and retract removes by edge or entity while avoiding a full reset. This gives an agent a realistic model of side effects and idempotency guarantees before invoking the tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but efficient: four sentences front-load the mode taxonomy and then add idempotency/backfill behavior without fluff. This is appropriately concise for a tool with 13 parameters and three distinct operations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex multi-mode mutation tool with no output schema, the definition covers the mode contract, idempotency rules, feedback grading semantics, and backfill defaults needed to call it correctly. The schema adds detailed parameter descriptions for observed_at, edge_idempotency, retract_entities, and entity_properties, so no critical invocation detail appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With schema description coverage at 62%, the description compensates by summarizing mode-specific parameter groups and adding hard-to-infer semantics like idempotency_key behavior, edge_idempotency defaults, and the feedback grade scale. It also tells the agent which fields to include for search_feedback (query, search_id, target, rank, score). It does not re-explain every parameter, but the schema fills most of the remaining gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb-plus-resource summary: 'Write graph facts, retract them, or label ranked results,' and immediately enumerates the three modes with concrete objects. It clearly identifies lbb_commit as a write/mutation tool, distinguishing it from the read-oriented siblings like lbb_query and lbb_inspect.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit mode-selection guidance: mode=facts writes triplets/embeddings/properties, mode=retract removes wrongly-added facts without a full reset, and mode=search_feedback stores graded query/result labels. It also provides operational context such as passing skip_unchanged for re-runnable backfills. It does not name sibling tools explicitly, but the mode-level instructions make the intended usage clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lbb_configureA
Manage native schema metadata. Actions: define_ontology (friendly spec with super_types), evolve_ontology (ordered edits including add_super_types), publish_schema (SHACL activation). All 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.
| Name | Required | Description | Default |
|---|---|---|---|
| ops | No | Ontology changes to apply in order (additive, in-place edits, or subtractive) | |
| graph | No | Graph to create or redefine | |
| action | Yes | Selects the variant (one of: define_ontology, publish_schema, evolve_ontology). | |
| branch | No | Branch to target; defaults to the connection's branch | |
| format | No | ||
| shapes | No | ||
| source | No | ||
| dry_run | No | Preview the exact definition without creating a graph or writing metadata. | |
| ontology | No | ||
| relations | No | ||
| desired_mode | No | ||
| entity_types | No | ||
| merge_default | No | ||
| confirm_restrictive | No | ||
| allow_data_conflicts | No | Deprecated compatibility flag; does not bypass conflicts. Preview subtractive changes with dry_run=true, repair the reported conflicts, then apply. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only provide readOnlyHint=false and destructiveHint=false, so the description carries most of the behavioral burden. It discloses important traits: dry_run previews exist, the tool does NOT store the full RDF/OWL document, RDF deletions are unsupported, and a preview does not validate the whole graph. It could add more about side effects of tombstoning operations, but the key behavioral caveats are present.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but well-structured: purpose first, then actions, then critical caveats and sibling routing. Every sentence earns its place, and there is no filler or repetition of schema content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a high-complexity tool with 15 parameters, nested objects, and no output schema, yet the description covers the core invocation decisions and major pitfalls: not storing RDF facts, unsupported RDF deletions, preview limitations, and publication semantics. It does not describe return values, which is a small gap given the absence of an output schema, but the rest is sufficient for safe use.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 40%, below the 50% threshold, so the description must compensate. It does add action-level meaning: define_ontology uses a friendly spec with super_types, evolve_ontology applies ordered edits, and publish_schema activates SHACL shapes. However, many parameters such as branch, format, source, entity_types, desired_mode, merge_default, and confirm_restrictive are left mostly to the schema, so compensation is only partial.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource ('Manage native schema metadata') and names the three actions. It distinctly differentiates from the closest sibling by stating that definition/import here extracts native metadata and does NOT store the complete RDF/OWL document as queryable graph facts. This is a clear, non-tautological statement of tool ownership.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly routes to alternatives: use lbb_rdf import for full OWL and lbb_rdf update for additive INSERT DATA revisions, with RDF deletions unsupported. It also gives intra-tool guidance, stating publish_schema is for unchanged ontology plus shapes while define/evolve are for native ontology changes. This is concrete when/when-not usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lbb_inspectARead-only
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.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | ||
| as_of | No | Valid-time snapshot pin (RFC3339): reproduce the node as of this instant. | |
| graph | No | Graph to target; defaults to the connection's graph | |
| query | No | Ontology concept, term, or relation to search | |
| top_k | No | ||
| action | Yes | Selects the variant (one of: guide, ontology, ontology_conformance, schema, graphs, publication, ontology_search, metadata, entity, state, history, why, transitions). | |
| branch | No | Branch to target; defaults to the connection's branch | |
| cursor | No | Opaque lbb_inspect continuation. Repeat action and pass the returned next arguments; rejects changed metadata. | |
| detail | No | Response detail level. Defaults to compact. | |
| section | No | Optional top-level array to inspect, e.g. entity_type_defs, relation_defs, property_defs, classes, or relations. Omit to page through all sections. | |
| relation | No | ||
| entity_id | No | Entity id (hex); alternative to entity_type+name | |
| page_size | No | Maximum complete metadata entries per page; defaults to 50. Nested fields are never truncated; an oversized single entry returns serialized_json fragments to concatenate and parse. | |
| entity_type | No | ||
| source_name | No | ||
| source_type | No | ||
| target_name | No | ||
| target_type | No | ||
| as_of_commit_seq | No | Snapshot pin: reproduce the node (state, edges, history) as of this commit_seq. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint=true already covering the read-only profile, the description productively adds behavior: pagination mechanics ('follow next until absent'), queryability reporting, and 'schema reads active native ontology/SHACL metadata without running validation'. It also describes entity's bounded edge neighborhood. It stops short of outlining error cases or exact response shapes, but is not contradicted by annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
At roughly 130 words, the description is dense but front-loads purpose, then gives action-specific behavior in compact clauses. Each sentence adds either routing, pagination, or variant semantics; the action enumeration is a necessary structural element rather than filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 19-parameter, 13-action tool with no output schema, the description covers the major variants, pagination, and the key sibling alternative. However, several actions (guide, state, history, transitions, why, metadata) are only named and not described, and some parameter interactions are left implicit, so it is not fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 58%, so the schema still carries much of the parameter burden. The description adds semantics for action, page_size, section, and cursor, but leaves several parameters (name, relation, entity_type, source_name, target_name, etc.) with no explanation beyond the schema, some of which have no schema description either.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Read graph context and exact graph facts', a specific verb + resource, then enumerates the 13 action variants. It distinguishes itself from lbb_query by directing asserted RDF/OWL axiom queries to that sibling, though it does not explicitly differentiate against lbb_rdf, so it falls just short of full clarity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides actionable routing signals: 'Query asserted RDF/OWL axioms separately with lbb_query', 'Use lbb_query with SPARQL property paths for precise path selection', and lifecycle context such as 'graphs works before bootstrap' and 'publication reports whether writes are queryable'. It does not give exclusions for other siblings like lbb_rdf or lbb_models, so usage guidance is clear but incomplete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lbb_modelsBRead-only
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.
| Name | Required | Description | Default |
|---|---|---|---|
| body | No | ||
| graph | No | Graph to target; defaults to the connection's graph | |
| limit | No | ||
| action | Yes | ||
| branch | No | Branch to target; defaults to the connection's branch | |
| detail | No | Response detail level. Defaults to compact. | |
| split_seq | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark readOnlyHint=true, so no contradiction. Description adds context about operating 'over one pinned published snapshot' and explains action-specific behavior (shadow_eval body, dataset returns bounded examples). This adds value beyond annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no wasted words, but the information could be better structured (e.g., separating action types into a list). Still efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so description should clarify return values, but it doesn't. It also omits explanation of the 'limit', 'graph', 'branch', and 'detail' parameters. While it covers the two action groups, it is incomplete for a 7-parameter tool with nested objects.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 43%, so the schema leaves gaps. The description partially compensates by explaining the 'shadow_eval' action requires the body parameter and that dataset actions use an optional signal split, but does not cover graph, branch, limit, or detail parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool reads model-training inputs or compares retrieval configurations, using verbs like 'Read' and 'compare'. It distinguishes from sibling tools by focusing on model-training and snapshots, but could be more specific about the scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance on when to use this tool versus siblings like lbb_query or lbb_inspect. The description lacks explicit context for selection criteria.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lbb_observeA
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.
| Name | Required | Description | Default |
|---|---|---|---|
| facts | No | Caller-extracted candidate facts; omit with extract:false to store the episode only | |
| graph | No | Graph to target; defaults to the connection's graph | |
| turns | Yes | The conversation slice to remember (stored verbatim) | |
| branch | No | Branch to target; defaults to the connection's branch | |
| source | No | Source label, e.g. support-bot | |
| extract | No | false = store the episode only (default true) | |
| auto_merge | No | Merge onto the scoped branch when validation is clean | |
| session_id | Yes | Caller's conversation id (drives the default observe branch name) | |
| observe_branch | No | Branch for the facts (default observe-<hash12(session_id)>) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds significant behavioral context beyond the minimal annotations (readOnlyHint=false, etc.). It explains the branching model, the anchoring vs. needs_review outcomes, the auto-merge with SHACL validation, and the server flag dependency. No contradictions with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured and front-loaded with the main purpose. All sentences add value, though some technical details (e.g., auto_merge behavior) could be slightly more streamlined. It is appropriately sized for the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has 9 parameters and no output schema, the description covers the core logic (anchoring, confidence, auto-merge) and mentions the server flag. However, it does not describe the return format or error conditions, leaving agents to infer the response structure.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, providing baseline parameter documentation. The description adds interpretive context (e.g., 'omit with extract:false to store the episode only', 'confidence >= 0.8 to mint new entities'), but does not fully explain all parameters beyond what the schema already states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: storing conversation turns verbatim as an EPISODE evidence entity and anchoring/gating facts on an observe branch. It uses specific verbs ('remember', 'store', 'anchor', 'gate') and distinguishes it from siblings by highlighting the observe branch and the fact anchoring behavior.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides context on when to use the tool (for adding facts without poisoning the main graph) and explains the anchoring logic and confidence threshold. However, it lacks explicit 'when not to use' or direct comparisons to sibling tools like lbb_commit or lbb_ground.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lbb_queryARead-only
Analytical and expert reads. Modes: structured (SPARQL-subset JSON body), sparql (SPARQL text), analyze. SPARQL is the only query surface. 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.
| Name | Required | Description | Default |
|---|---|---|---|
| body | No | Structured SPARQL-subset request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: "x" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { "compare": { "op": <op>, "left": <term>, "right": <term> } } (or { "and": [<filter>…] }, { "or": [<filter>…] }, { "not": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { "var": "x" }, { "property": { "var": "x", "field": "amount" } } (a typed scalar attribute), or { "value": <typed> } — and <typed> is exactly one wrapper: { "str": "…" }, { "i64": 5 }, { "f64": 0.9 }, { "bool": true }, { "date_time": "2026-01-01" } (RFC3339), or { "entity": { "entity_type": "T", "name": "N" } }. Complete runnable example — deals whose amount ≥ 1000000: { "patterns": [{ "subject": { "var": "d" }, "predicate": "for_client", "object": { "var": "c" } }], "filters": [{ "compare": { "op": "ge", "left": { "property": { "var": "d", "field": "amount" } }, "right": { "value": { "f64": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: ["s"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { "patterns": [{ "subject": { "var": "c" }, "predicate": "committed_to", "object": { "var": "repo" } }], "group_keys": [{ "date_bucket": { "var": "c", "field": "committed_at", "granularity": "month", "as": "m" } }, { "property": { "var": "c", "field": "area", "as": "area" } }], "aggregates": [{ "func": "count", "as": "n" }], "order_by": [{ "var": "m" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { "compare": { "op": "gt", "left": { "var": "n" }, "right": { "value": { "i64": 10 } } } }). A `combinators` key (UNION/OPTIONAL/MINUS/EXISTS) is rejected here; express those with SPARQL text under mode=sparql. Cheap aggregate count: pair an equality having (e.g. { "compare": { "op": "eq", "left": { "var": "n" }, "right": { "value": { "i64": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning use the top-level `as_of_commit_seq` argument or the same body field. Valid-time `as_of` and `as_of_valid_time` selectors are unsupported and rejected before HTTP. | |
| mode | Yes | Selects the variant (one of: structured, sparql, analyze). | |
| as_of | No | Unsupported in structured and SPARQL text modes; use as_of_commit_seq for a retained commit snapshot. | |
| chart | No | ||
| field | No | ||
| graph | No | Graph to target; defaults to the connection's graph | |
| query | No | SPARQL 1.1 query text (SELECT or ASK). Valid-time as_of is unsupported; use as_of_commit_seq for a retained commit snapshot. IRI scheme: relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased, e.g. writes_to; reverse a relation with the ^ path operator, no stored inverse triple). Types are <https://littlebigbrain.com/class/NAME> (lowercased), matched as `?x a <…/class/NAME>` with explicit entailment=subclass, rdfs, or owl for inference (default none). Property fields are <https://littlebigbrain.com/p/NAME> (lowercased). The local name is ALWAYS lowercase — an uppercase one (e.g. <…/r/FOR_CLIENT>) is a different, non-existent IRI that silently matches nothing; this tool auto-lowercases the local name of /r/, /class/, and /p/ IRIs for you and adds a `notes` entry when it does, so a stray uppercase still resolves. (Structured mode's `predicate` is case-insensitive on its own.) Entities are content-addressed <https://littlebigbrain.com/e/HASH> — never build an entity IRI from a name; anchor a named entity by its label instead: `?e <http://www.w3.org/2000/01/rdf-schema#label> "Acme"`. Discover the exact relation and type names with lbb_inspect action=ontology. SELECT and ASK only (CONSTRUCT/DESCRIBE are rejected). Example: SELECT ?service ?db WHERE { ?service <https://littlebigbrain.com/r/writes_to> ?db } LIMIT 10 | |
| top_k | No | ||
| branch | No | Branch to target; defaults to the connection's branch | |
| cursor | No | Opaque cursor from a previous lbb_query row page; reruns the original query at the next offset. | |
| detail | No | Response detail level. Defaults to compact. | |
| metric | No | ||
| sparql | No | ||
| row_limit | No | Maximum query rows to return in this page. Defaults by detail: compact=20, standard=100, full=1000. | |
| entailment | No | Reasoning over the pinned RDF generation. Defaults to none. owl includes RDFS, inverse relationships and the supported OWL profile. | |
| consistency | No | Read consistency. strong requires publication through head; a pending response is retryable. | |
| min_indexed_seq | No | Read-after-write publication floor. Preserved across cursor pages. | |
| as_of_commit_seq | No | Snapshot pin: evaluate the body as of this commit_seq, hiding later commits. Errors if past head. Top-level alias for the body's `as_of_commit_seq` (either works for this one). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes well beyond the readOnlyHint annotation by disclosing auto-lowercasing of IRIs with a notes entry, the watermark pinning behavior, rejection of valid-time selectors, and the case-insensitivity of structured predicates. These are behavioral traits an agent needs to know but are not in the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long and dense, but each sentence carries technical necessity (IRI schemes, filter shapes, examples, rejection notes). It is not front-loaded in a scannable way; the core purpose and modes appear first, but the body parameter explanation is a wall of text. It could be tightened without losing critical detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 18 parameters, 6 enums, and no output schema, the description covers the essential query functionality thoroughly: modes, IRI conventions, entity anchoring, unsupported features, and references to lbb_inspect for ontology discovery. The schema descriptions handle remaining parameters, and the tool's return format is implied but not detailed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 72% and the description adds substantial meaning for the two main parameters: `body` (detailed shape, filter grammar, group_by/aggregates examples, count optimization) and `query` (SPARQL text, IRI scheme, entailment, auto-lowercase). This goes beyond schema descriptions, especially with runnable examples.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it performs 'Analytical and expert reads' and is the only query surface, with three modes. It distinguishes itself from sibling tools like lbb_inspect (ontology discovery) by referencing it for name discovery, and implies read-only operation. However, it does not explicitly name alternatives or contrast with lbb_rdf, so it stops short of a perfect score.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives concrete guidance on when to use lbb_inspect to discover relation/type names before querying, and notes that structured mode rejects combinators (use sparql mode instead). It does not explicitly state when to prefer lbb_query over lbb_rdf or other siblings, but the core usage context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lbb_rdfADestructiveIdempotent
Store and extend complete RDF/OWL documents through MCP. import accepts Turtle/N-Triples/N-Quads/TriG without conversion; update executes INSERT DATA for additive edits. Replacing/removing axioms is unsupported; use a new versioned LBB graph for a revised document. Named RDF graphs are unsupported; Turtle/N-Triples use the default graph, and dataset formats must contain only default-graph quads. These write graph facts, distinct from lbb_configure's native schema metadata. A first RDF write selects RDF-native storage, which refuses later property-graph commits; choose the write workflow before bootstrap. Retries deduplicate by content unless idempotency_key is supplied. A completed write schedules publication; inspect action=publication and verify using lbb_query entailment=owl.
| Name | Required | Description | Default |
|---|---|---|---|
| graph | No | Graph to target; defaults to the connection's graph | |
| action | Yes | Selects the variant (one of: import, update). | |
| branch | No | Branch to target; defaults to the connection's branch | |
| detail | No | Response detail level. Defaults to compact. | |
| format | No | ||
| source | No | Complete RDF document. Preserves OWL axioms, RDF lists, labels, comments, and external IRIs as graph facts. | |
| update | No | SPARQL INSERT DATA text to add axioms. Submitted unchanged. DELETE/WHERE/CLEAR and named graphs are currently unsupported and fail without mutation. | |
| base_iri | No | ||
| idempotency_key | No | ||
| blank_node_scope | No | Stable document scope for blank labels across import chunks. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as non-read-only, non-open-world, idempotent, and destructive. The description goes well beyond these by explaining the permanent storage-mode selection on first write, refusal of later property-graph commits, retry deduplication behavior, and scheduled publication after a completed write. No contradiction with annotations exists; the destructiveHint is consistent with irreversible storage-mode selection.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but every sentence carries unique information. It is front-loaded with the core purpose and action variants, then systematically covers constraints, storage implications, retries, and publication. No filler or repetition of schema details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex tool with 10 parameters and no output schema, this description is unusually complete. It explains the two execution modes, supported formats, unsupported operations, named-graph restrictions, storage-mode implications, retry semantics, and the publication workflow. The only minor omission is detailed return-value structure, but the description directs users to inspect action=publication and verify with lbb_query.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 70%, and the description substantially compensates: it explains that source preserves OWL axioms and external IRIs as graph facts, that update is submitted unchanged and fails without mutation for unsupported SPARQL operations, that dataset formats must contain only default-graph quads, and that blank_node_scope provides stable labels across import chunks. This adds significant meaning beyond the schema's terse field descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool stores and extends complete RDF/OWL documents, and explicitly distinguishes the two actions: import accepts RDF serializations without conversion, while update executes SPARQL INSERT DATA for additive edits. This clearly separates it from sibling tools like lbb_configure, which handles native schema metadata.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use and when-not-to-use guidance: replacing/removing axioms is unsupported and users should use a new versioned LBB graph instead. It also warns about named-graph limitations, storage mode selection before bootstrap, and retries deduplication, and distinguishes these graph facts from lbb_configure's metadata.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.5.1- Changed
lbb_commit1 field changed- added
Input schema / properties / dry_runAdded value: +{ + "description": "Validate a facts commit and return its structured SHACL report without writing. Only supported for mode=facts.", + "type": "boolean" +}
- Changed
lbb_configure9 fields changed- changed
Input schema / properties / allow_data_conflicts / descriptionPrevious value: -"Apply subtractive ops (narrow/remove) even when current data conflicts; affected records are kept and begin to warn. Default false rejects a conflicting subtractive request and reports the conflicts."New value: +"Deprecated compatibility flag; does not bypass conflicts. Preview subtractive changes with dry_run=true, repair the reported conflicts, then apply." - added
Input schema / properties / dry_runAdded value: +{ + "description": "Preview the exact definition without creating a graph or writing metadata.", + "type": "boolean" +} - removed
Input schema / properties / entity_types / items / additionalPropertiesRemoved value: -{} - added
Input schema / properties / entity_types / items / anyOfAdded value: +[ + { + "type": "string" + }, + { + "additionalProperties": {}, + "type": "object" + } +] - removed
Input schema / properties / entity_types / items / typeRemoved value: -"object" - changed
Input schema / properties / ops / items / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "add_domain": { - "description": "Entity-type names to add to the relation's domain (source types)", - "items": { - "type": "string" - }, - "type": "array" - }, - "add_range": { - "description": "Entity-type names to add to the relation's range (target types)", - "items": { - "type": "string" - }, - "type": "array" - }, - "op": { - "const": "widen_relation", - "type": "string" - }, - "relation": { - "description": "Relation to widen, by name (case-insensitive)", - "type": "string" - } - }, - "required": [ - "op", - "relation" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "name": { - "description": "Display name of the new entity type (idempotent if it exists)", - "type": "string" - }, - "op": { - "const": "add_entity_type", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "cardinality": { - "description": "Defaults to many_to_many", - "enum": [ - "one_to_one", - "one_to_many", - "many_to_one", - "many_to_many" - ], - "type": "string" - }, - "domain": { - "description": "Entity-type names allowed as the source (domain); must already exist", - "items": { - "type": "string" - }, - "type": "array" - }, - "inverse_name": { - "description": "Optional inverse-relation display name, e.g. PHASE_OF (enables one-hop reverse traversal)", - "type": "string" - }, - "name": { - "description": "Display name of the new relation, e.g. HAS_PHASE", - "type": "string" - }, - "op": { - "const": "add_relation", - "type": "string" - }, - "range": { - "description": "Entity-type names allowed as the target (range); must already exist", - "items": { - "type": "string" - }, - "type": "array" - }, - "reducer": { - "description": "State-reducer token, e.g. append_only (default), latest_wins", - "type": "string" - }, - "symmetric": { - "type": "boolean" - }, - "temporal_semantics": { - "description": "Defaults to bitemporal", - "enum": [ - "atemporal", - "valid_time", - "commit_time", - "bitemporal" - ], - "type": "string" - }, - "transitive": { - "type": "boolean" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "name": { - "description": "Display name of the new scalar property field, e.g. status (idempotent if it exists)", - "type": "string" - }, - "op": { - "const": "add_property", - "type": "string" - }, - "required": { - "description": "Advisory required flag (not enforced on commit)", - "type": "boolean" - }, - "value_type": { - "description": "Scalar type; defaults to text. Lets a later lbb_commit set entity_properties[].field", - "enum": [ - "bool", - "i64", - "f64", - "date_time", - "keyword", - "text", - "bytes" - ], - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "from": { - "description": "Current entity-type name", - "type": "string" - }, - "op": { - "const": "rename_entity_type", - "type": "string" - }, - "to": { - "description": "New display name (stable id stays frozen; records keep resolving)", - "type": "string" - } - }, - "required": [ - "op", - "from", - "to" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "from": { - "description": "Current relation name", - "type": "string" - }, - "op": { - "const": "rename_relation", - "type": "string" - }, - "to": { - "description": "New display name (stable id stays frozen; edges keep resolving)", - "type": "string" - } - }, - "required": [ - "op", - "from", - "to" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "inverse_name": { - "description": "Inverse-relation display name, e.g. PHASE_OF (enables one-hop reverse traversal)", - "type": "string" - }, - "op": { - "const": "set_relation_inverse", - "type": "string" - }, - "relation": { - "description": "Relation to set the inverse on, by name", - "type": "string" - } - }, - "required": [ - "op", - "relation", - "inverse_name" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "cardinality": { - "enum": [ - "one_to_one", - "one_to_many", - "many_to_one", - "many_to_many" - ], - "type": "string" - }, - "op": { - "const": "set_relation_cardinality", - "type": "string" - }, - "relation": { - "description": "Relation to change, by name", - "type": "string" - } - }, - "required": [ - "op", - "relation", - "cardinality" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "op": { - "const": "narrow_relation", - "type": "string" - }, - "relation": { - "description": "Relation to narrow, by name", - "type": "string" - }, - "remove_domain": { - "description": "Entity-type names to remove from the relation's domain (subtractive)", - "items": { - "type": "string" - }, - "type": "array" - }, - "remove_range": { - "description": "Entity-type names to remove from the relation's range (subtractive)", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "op", - "relation" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "name": { - "description": "Entity type to tombstone — kept readable for old records, rejected for new commits", - "type": "string" - }, - "op": { - "const": "remove_entity_type", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "name": { - "description": "Relation to tombstone — old edges stay readable, rejected for new commits", - "type": "string" - }, - "op": { - "const": "remove_relation", - "type": "string" - } - }, - "required": [ - "op", - "name" - ], - "type": "object" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "entity_type": { + "type": "string" + }, + "op": { + "const": "add_super_types", + "type": "string" + }, + "super_types": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "op", + "entity_type", + "super_types" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "add_domain": { + "description": "Entity-type names to add to the relation's domain (source types)", + "items": { + "type": "string" + }, + "type": "array" + }, + "add_range": { + "description": "Entity-type names to add to the relation's range (target types)", + "items": { + "type": "string" + }, + "type": "array" + }, + "op": { + "const": "widen_relation", + "type": "string" + }, + "relation": { + "description": "Relation to widen, by name (case-insensitive)", + "type": "string" + } + }, + "required": [ + "op", + "relation" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "name": { + "description": "Display name of the new entity type (idempotent if it exists)", + "type": "string" + }, + "op": { + "const": "add_entity_type", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "cardinality": { + "description": "Defaults to many_to_many", + "enum": [ + "one_to_one", + "one_to_many", + "many_to_one", + "many_to_many" + ], + "type": "string" + }, + "domain": { + "description": "Entity-type names allowed as the source (domain); must already exist", + "items": { + "type": "string" + }, + "type": "array" + }, + "inverse_name": { + "description": "Optional inverse-relation display name, e.g. PHASE_OF (enables one-hop reverse traversal)", + "type": "string" + }, + "name": { + "description": "Display name of the new relation, e.g. HAS_PHASE", + "type": "string" + }, + "op": { + "const": "add_relation", + "type": "string" + }, + "range": { + "description": "Entity-type names allowed as the target (range); must already exist", + "items": { + "type": "string" + }, + "type": "array" + }, + "reducer": { + "description": "State-reducer token, e.g. append_only (default), latest_wins", + "type": "string" + }, + "symmetric": { + "type": "boolean" + }, + "temporal_semantics": { + "description": "Defaults to bitemporal", + "enum": [ + "atemporal", + "valid_time", + "commit_time", + "bitemporal" + ], + "type": "string" + }, + "transitive": { + "type": "boolean" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "name": { + "description": "Display name of the new scalar property field, e.g. status (idempotent if it exists)", + "type": "string" + }, + "op": { + "const": "add_property", + "type": "string" + }, + "required": { + "description": "Advisory required flag (not enforced on commit)", + "type": "boolean" + }, + "value_type": { + "description": "Scalar type; defaults to text. Lets a later lbb_commit set entity_properties[].field", + "enum": [ + "bool", + "i64", + "f64", + "date_time", + "keyword", + "text", + "bytes" + ], + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "description": "Current entity-type name", + "type": "string" + }, + "op": { + "const": "rename_entity_type", + "type": "string" + }, + "to": { + "description": "New display name (stable id stays frozen; records keep resolving)", + "type": "string" + } + }, + "required": [ + "op", + "from", + "to" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "description": "Current relation name", + "type": "string" + }, + "op": { + "const": "rename_relation", + "type": "string" + }, + "to": { + "description": "New display name (stable id stays frozen; edges keep resolving)", + "type": "string" + } + }, + "required": [ + "op", + "from", + "to" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "inverse_name": { + "description": "Inverse-relation display name, e.g. PHASE_OF (enables one-hop reverse traversal)", + "type": "string" + }, + "op": { + "const": "set_relation_inverse", + "type": "string" + }, + "relation": { + "description": "Relation to set the inverse on, by name", + "type": "string" + } + }, + "required": [ + "op", + "relation", + "inverse_name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "cardinality": { + "enum": [ + "one_to_one", + "one_to_many", + "many_to_one", + "many_to_many" + ], + "type": "string" + }, + "op": { + "const": "set_relation_cardinality", + "type": "string" + }, + "relation": { + "description": "Relation to change, by name", + "type": "string" + } + }, + "required": [ + "op", + "relation", + "cardinality" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "op": { + "const": "narrow_relation", + "type": "string" + }, + "relation": { + "description": "Relation to narrow, by name", + "type": "string" + }, + "remove_domain": { + "description": "Entity-type names to remove from the relation's domain (subtractive)", + "items": { + "type": "string" + }, + "type": "array" + }, + "remove_range": { + "description": "Entity-type names to remove from the relation's range (subtractive)", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "op", + "relation" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "name": { + "description": "Entity type to tombstone — kept readable for old records, rejected for new commits", + "type": "string" + }, + "op": { + "const": "remove_entity_type", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "name": { + "description": "Relation to tombstone — old edges stay readable, rejected for new commits", + "type": "string" + }, + "op": { + "const": "remove_relation", + "type": "string" + } + }, + "required": [ + "op", + "name" + ], + "type": "object" + } +] - removed
Input schema / properties / relations / $refRemoved value: -"#/properties/entity_types" - added
Input schema / properties / relations / itemsAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/properties/entity_types/items/anyOf/1" + } + ] +} - added
Input schema / properties / relations / typeAdded value: +"array"
- Changed
lbb_inspect5 fields changed- changed
Input schema / properties / action / descriptionPrevious value: -"Selects the variant (one of: guide, ontology, ontology_conformance, schema, ontology_search, metadata, entity, state, history, why, transitions)."New value: +"Selects the variant (one of: guide, ontology, ontology_conformance, schema, graphs, publication, ontology_search, metadata, entity, state, history, why, transitions)." - changed
Input schema / properties / action / enumPrevious value: -[ - "guide", - "ontology", - "ontology_conformance", - "schema", - "ontology_search", - "metadata", - "entity", - "state", - "history", - "why", - "transitions" -]New value: +[ + "guide", + "ontology", + "ontology_conformance", + "schema", + "graphs", + "publication", + "ontology_search", + "metadata", + "entity", + "state", + "history", + "why", + "transitions" +] - added
Input schema / properties / cursorAdded value: +{ + "description": "Opaque lbb_inspect continuation. Repeat action and pass the returned next arguments; rejects changed metadata.", + "type": "string" +} - added
Input schema / properties / page_sizeAdded value: +{ + "description": "Maximum complete metadata entries per page; defaults to 50. Nested fields are never truncated; an oversized single entry returns serialized_json fragments to concatenate and parse.", + "maximum": 500, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / sectionAdded value: +{ + "description": "Optional top-level array to inspect, e.g. entity_type_defs, relation_defs, property_defs, classes, or relations. Omit to page through all sections.", + "type": "string" +}
- Changed
lbb_query6 fields changed- changed
Input schema / properties / as_of / descriptionPrevious value: -"Snapshot pin (valid-time, RFC3339): evaluate the body as of this instant. Folded into the request's `as_of_valid_time`. Top-level here is the supported spelling — a bare `as_of` inside the body is rejected, since the server silently ignores it."New value: +"Unsupported in structured and SPARQL text modes; use as_of_commit_seq for a retained commit snapshot." - changed
Input schema / properties / body / descriptionPrevious value: -"Structured SPARQL-subset request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: \"x\" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { \"compare\": { \"op\": <op>, \"left\": <term>, \"right\": <term> } } (or { \"and\": [<filter>…] }, { \"or\": [<filter>…] }, { \"not\": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { \"var\": \"x\" }, { \"property\": { \"var\": \"x\", \"field\": \"amount\" } } (a typed scalar attribute), or { \"value\": <typed> } — and <typed> is exactly one wrapper: { \"str\": \"…\" }, { \"i64\": 5 }, { \"f64\": 0.9 }, { \"bool\": true }, { \"date_time\": \"2026-01-01\" } (RFC3339), or { \"entity\": { \"entity_type\": \"T\", \"name\": \"N\" } }. Complete runnable example — deals whose amount ≥ 1000000: { \"patterns\": [{ \"subject\": { \"var\": \"d\" }, \"predicate\": \"for_client\", \"object\": { \"var\": \"c\" } }], \"filters\": [{ \"compare\": { \"op\": \"ge\", \"left\": { \"property\": { \"var\": \"d\", \"field\": \"amount\" } }, \"right\": { \"value\": { \"f64\": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: [\"s\"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { \"patterns\": [{ \"subject\": { \"var\": \"c\" }, \"predicate\": \"committed_to\", \"object\": { \"var\": \"repo\" } }], \"group_keys\": [{ \"date_bucket\": { \"var\": \"c\", \"field\": \"committed_at\", \"granularity\": \"month\", \"as\": \"m\" } }, { \"property\": { \"var\": \"c\", \"field\": \"area\", \"as\": \"area\" } }], \"aggregates\": [{ \"func\": \"count\", \"as\": \"n\" }], \"order_by\": [{ \"var\": \"m\" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { \"compare\": { \"op\": \"gt\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 10 } } } }). A `combinators` key (UNION/OPTIONAL/MINUS/EXISTS) is rejected here; express those with SPARQL text under mode=sparql. Cheap aggregate count: pair an equality having (e.g. { \"compare\": { \"op\": \"eq\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning prefer the top-level `as_of` / `as_of_commit_seq` arguments below; a bare `as_of` key inside the body is rejected (the body's valid-time field is `as_of_valid_time`)."New value: +"Structured SPARQL-subset request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: \"x\" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { \"compare\": { \"op\": <op>, \"left\": <term>, \"right\": <term> } } (or { \"and\": [<filter>…] }, { \"or\": [<filter>…] }, { \"not\": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { \"var\": \"x\" }, { \"property\": { \"var\": \"x\", \"field\": \"amount\" } } (a typed scalar attribute), or { \"value\": <typed> } — and <typed> is exactly one wrapper: { \"str\": \"…\" }, { \"i64\": 5 }, { \"f64\": 0.9 }, { \"bool\": true }, { \"date_time\": \"2026-01-01\" } (RFC3339), or { \"entity\": { \"entity_type\": \"T\", \"name\": \"N\" } }. Complete runnable example — deals whose amount ≥ 1000000: { \"patterns\": [{ \"subject\": { \"var\": \"d\" }, \"predicate\": \"for_client\", \"object\": { \"var\": \"c\" } }], \"filters\": [{ \"compare\": { \"op\": \"ge\", \"left\": { \"property\": { \"var\": \"d\", \"field\": \"amount\" } }, \"right\": { \"value\": { \"f64\": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: [\"s\"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { \"patterns\": [{ \"subject\": { \"var\": \"c\" }, \"predicate\": \"committed_to\", \"object\": { \"var\": \"repo\" } }], \"group_keys\": [{ \"date_bucket\": { \"var\": \"c\", \"field\": \"committed_at\", \"granularity\": \"month\", \"as\": \"m\" } }, { \"property\": { \"var\": \"c\", \"field\": \"area\", \"as\": \"area\" } }], \"aggregates\": [{ \"func\": \"count\", \"as\": \"n\" }], \"order_by\": [{ \"var\": \"m\" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { \"compare\": { \"op\": \"gt\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 10 } } } }). A `combinators` key (UNION/OPTIONAL/MINUS/EXISTS) is rejected here; express those with SPARQL text under mode=sparql. Cheap aggregate count: pair an equality having (e.g. { \"compare\": { \"op\": \"eq\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning use the top-level `as_of_commit_seq` argument or the same body field. Valid-time `as_of` and `as_of_valid_time` selectors are unsupported and rejected before HTTP." - added
Input schema / properties / consistencyAdded value: +{ + "description": "Read consistency. strong requires publication through head; a pending response is retryable.", + "enum": [ + "eventual", + "strong" + ], + "type": "string" +} - added
Input schema / properties / entailmentAdded value: +{ + "description": "Reasoning over the pinned RDF generation. Defaults to none. owl includes RDFS, inverse relationships and the supported OWL profile.", + "enum": [ + "none", + "subclass", + "rdfs", + "owl" + ], + "type": "string" +} - added
Input schema / properties / min_indexed_seqAdded value: +{ + "description": "Read-after-write publication floor. Preserved across cursor pages.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +} - changed
Input schema / properties / query / descriptionPrevious value: -"SPARQL 1.1 query text (SELECT or ASK). IRI scheme: relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased, e.g. writes_to; reverse a relation with the ^ path operator, no stored inverse triple). Types are <https://littlebigbrain.com/class/NAME> (lowercased), matched as `?x a <…/class/NAME>` with rdfs:subClassOf closure on by default. Property fields are <https://littlebigbrain.com/p/NAME> (lowercased). The local name is ALWAYS lowercase — an uppercase one (e.g. <…/r/FOR_CLIENT>) is a different, non-existent IRI that silently matches nothing; this tool auto-lowercases the local name of /r/, /class/, and /p/ IRIs for you and adds a `notes` entry when it does, so a stray uppercase still resolves. (Structured mode's `predicate` is case-insensitive on its own.) Entities are content-addressed <https://littlebigbrain.com/e/HASH> — never build an entity IRI from a name; anchor a named entity by its label instead: `?e <http://www.w3.org/2000/01/rdf-schema#label> \"Acme\"`. Discover the exact relation and type names with lbb_inspect action=ontology. SELECT and ASK only (CONSTRUCT/DESCRIBE are rejected). Example: SELECT ?service ?db WHERE { ?service <https://littlebigbrain.com/r/writes_to> ?db } LIMIT 10"New value: +"SPARQL 1.1 query text (SELECT or ASK). Valid-time as_of is unsupported; use as_of_commit_seq for a retained commit snapshot. IRI scheme: relations are <https://littlebigbrain.com/r/NAME> (NAME lowercased, e.g. writes_to; reverse a relation with the ^ path operator, no stored inverse triple). Types are <https://littlebigbrain.com/class/NAME> (lowercased), matched as `?x a <…/class/NAME>` with explicit entailment=subclass, rdfs, or owl for inference (default none). Property fields are <https://littlebigbrain.com/p/NAME> (lowercased). The local name is ALWAYS lowercase — an uppercase one (e.g. <…/r/FOR_CLIENT>) is a different, non-existent IRI that silently matches nothing; this tool auto-lowercases the local name of /r/, /class/, and /p/ IRIs for you and adds a `notes` entry when it does, so a stray uppercase still resolves. (Structured mode's `predicate` is case-insensitive on its own.) Entities are content-addressed <https://littlebigbrain.com/e/HASH> — never build an entity IRI from a name; anchor a named entity by its label instead: `?e <http://www.w3.org/2000/01/rdf-schema#label> \"Acme\"`. Discover the exact relation and type names with lbb_inspect action=ontology. SELECT and ASK only (CONSTRUCT/DESCRIBE are rejected). Example: SELECT ?service ?db WHERE { ?service <https://littlebigbrain.com/r/writes_to> ?db } LIMIT 10"
- Added
lbb_rdf
5 tool updates
v0.4.2- Removed
lbb_decode - Removed
lbb_ground - Changed
lbb_inspect5 fields changed- changed
Input schema / properties / action / descriptionPrevious value: -"Selects the variant (one of: guide, ontology, ontology_conformance, schema, ontology_search, metadata, entity, state, history, why, traverse, transitions)."New value: +"Selects the variant (one of: guide, ontology, ontology_conformance, schema, ontology_search, metadata, entity, state, history, why, transitions)." - changed
Input schema / properties / action / enumPrevious value: -[ - "guide", - "ontology", - "ontology_conformance", - "schema", - "ontology_search", - "metadata", - "entity", - "state", - "history", - "why", - "traverse", - "transitions" -]New value: +[ + "guide", + "ontology", + "ontology_conformance", + "schema", + "ontology_search", + "metadata", + "entity", + "state", + "history", + "why", + "transitions" +] - removed
Input schema / properties / directionRemoved value: -{ - "enum": [ - "out", - "in", - "both" - ], - "type": "string" -} - removed
Input schema / properties / max_hopsRemoved value: -{ - "exclusiveMinimum": 0, - "maximum": 6, - "type": "integer" -} - removed
Input schema / properties / relationsRemoved value: -{ - "items": { - "type": "string" - }, - "type": "array" -}
- Changed
lbb_query2 fields changed- changed
Input schema / properties / body / descriptionPrevious value: -"Structured SPARQL-subset or analytics request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: \"x\" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { \"compare\": { \"op\": <op>, \"left\": <term>, \"right\": <term> } } (or { \"and\": [<filter>…] }, { \"or\": [<filter>…] }, { \"not\": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { \"var\": \"x\" }, { \"property\": { \"var\": \"x\", \"field\": \"amount\" } } (a typed scalar attribute), or { \"value\": <typed> } — and <typed> is exactly one wrapper: { \"str\": \"…\" }, { \"i64\": 5 }, { \"f64\": 0.9 }, { \"bool\": true }, { \"date_time\": \"2026-01-01\" } (RFC3339), or { \"entity\": { \"entity_type\": \"T\", \"name\": \"N\" } }. Complete runnable example — deals whose amount ≥ 1000000: { \"patterns\": [{ \"subject\": { \"var\": \"d\" }, \"predicate\": \"for_client\", \"object\": { \"var\": \"c\" } }], \"filters\": [{ \"compare\": { \"op\": \"ge\", \"left\": { \"property\": { \"var\": \"d\", \"field\": \"amount\" } }, \"right\": { \"value\": { \"f64\": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: [\"s\"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { \"patterns\": [{ \"subject\": { \"var\": \"c\" }, \"predicate\": \"committed_to\", \"object\": { \"var\": \"repo\" } }], \"group_keys\": [{ \"date_bucket\": { \"var\": \"c\", \"field\": \"committed_at\", \"granularity\": \"month\", \"as\": \"m\" } }, { \"property\": { \"var\": \"c\", \"field\": \"area\", \"as\": \"area\" } }], \"aggregates\": [{ \"func\": \"count\", \"as\": \"n\" }], \"order_by\": [{ \"var\": \"m\" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { \"compare\": { \"op\": \"gt\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 10 } } } }); it is evaluated only on this grouped path, NOT alongside `combinators` (UNION/OPTIONAL/MINUS), which route to the analytics engine. Cheap aggregate count: pair an equality having (e.g. { \"compare\": { \"op\": \"eq\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning prefer the top-level `as_of` / `as_of_commit_seq` arguments below; a bare `as_of` key inside the body is rejected (the body's valid-time field is `as_of_valid_time`)."New value: +"Structured SPARQL-subset request body. Shape: { patterns: [{ subject, predicate, object }], filters?, group_by?, group_keys?, aggregates?, having?, order_by?, select?, limit?, distinct? }. Each pattern term is { var: \"x\" } or a fixed { entity: { entity_type, name } }; `predicate` is a relation name and is case-insensitive here (FOR_CLIENT and for_client both resolve — unlike SPARQL text, which needs the lowercased IRI local name). FILTER — `filters` is a list of conditions, each of exact shape { \"compare\": { \"op\": <op>, \"left\": <term>, \"right\": <term> } } (or { \"and\": [<filter>…] }, { \"or\": [<filter>…] }, { \"not\": <filter> }). `op` is one of eq | ne | lt | le | gt | ge (NOT the symbols =,<,>). Each <term> is exactly one of { \"var\": \"x\" }, { \"property\": { \"var\": \"x\", \"field\": \"amount\" } } (a typed scalar attribute), or { \"value\": <typed> } — and <typed> is exactly one wrapper: { \"str\": \"…\" }, { \"i64\": 5 }, { \"f64\": 0.9 }, { \"bool\": true }, { \"date_time\": \"2026-01-01\" } (RFC3339), or { \"entity\": { \"entity_type\": \"T\", \"name\": \"N\" } }. Complete runnable example — deals whose amount ≥ 1000000: { \"patterns\": [{ \"subject\": { \"var\": \"d\" }, \"predicate\": \"for_client\", \"object\": { \"var\": \"c\" } }], \"filters\": [{ \"compare\": { \"op\": \"ge\", \"left\": { \"property\": { \"var\": \"d\", \"field\": \"amount\" } }, \"right\": { \"value\": { \"f64\": 1000000 } } } }] }. Comparisons use the field's real declared type (numbers as numbers, datetimes as instants), so they run server-side. GROUP BY supports both entity-identity keys (group_by: [\"s\"]) and typed scalar keys via group_keys: a property value ({ property: { var, field, as } }) or a calendar bucket of a datetime property ({ date_bucket: { var, field, granularity: year|month|week|day|hour, as } }). Scalar keys come back per group under value_keys[as] — so a per-area breakdown or a commits-per-month time series is one server-side query, no client-side bucketing. Worked example -- commits per area per month in one query: { \"patterns\": [{ \"subject\": { \"var\": \"c\" }, \"predicate\": \"committed_to\", \"object\": { \"var\": \"repo\" } }], \"group_keys\": [{ \"date_bucket\": { \"var\": \"c\", \"field\": \"committed_at\", \"granularity\": \"month\", \"as\": \"m\" } }, { \"property\": { \"var\": \"c\", \"field\": \"area\", \"as\": \"area\" } }], \"aggregates\": [{ \"func\": \"count\", \"as\": \"n\" }], \"order_by\": [{ \"var\": \"m\" }] } -- area and committed_at are typed entity attributes (set via entity_properties; readable flat under attributes, never a nested metadata blob), and each group returns value_keys.m + value_keys.area + aggregates.n. `having: [...]` takes the same filter shape over the aggregated groups (e.g. { \"compare\": { \"op\": \"gt\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 10 } } } }). A `combinators` key (UNION/OPTIONAL/MINUS/EXISTS) is rejected here; express those with SPARQL text under mode=sparql. Cheap aggregate count: pair an equality having (e.g. { \"compare\": { \"op\": \"eq\", \"left\": { \"var\": \"n\" }, \"right\": { \"value\": { \"i64\": 4 } } } }) with row_limit: 1 -- the response row_page.total reports how many groups match without materializing them all, so you read the count off row_page.total instead of paging every matching row. For snapshot pinning prefer the top-level `as_of` / `as_of_commit_seq` arguments below; a bare `as_of` key inside the body is rejected (the body's valid-time field is `as_of_valid_time`)." - changed
Input schema / properties / metric / enumPrevious value: -[ - "entity_types", - "relations", - "overview", - "facets", - "sparql" -]New value: +[ + "entity_types", + "relations", + "overview", + "sparql" +]
- Removed
lbb_search
8 tool updates
v0.2.7- Removed
lbb_ask - Changed
lbb_configure6 fields changed- changed
Input schema / properties / action / descriptionPrevious value: -"Selects the variant (one of: define_ontology, define_rules, publish_schema, evolve_ontology)."New value: +"Selects the variant (one of: define_ontology, publish_schema, evolve_ontology)." - changed
Input schema / properties / action / enumPrevious value: -[ - "define_ontology", - "define_rules", - "publish_schema", - "evolve_ontology" -]New value: +[ + "define_ontology", + "publish_schema", + "evolve_ontology" +] - removed
Input schema / properties / confirm_emptyRemoved value: -{ - "type": "boolean" -} - changed
Input schema / properties / desired_mode / enumPrevious value: -[ - "warn", - "reject" -]New value: +[ + "off", + "warn", + "reject" +] - removed
Input schema / properties / preview_digestRemoved value: -{ - "type": "string" -} - removed
Input schema / properties / rulesRemoved value: -{ - "items": { - "additionalProperties": true, - "properties": { - "body": { - "description": "The condition: a basic graph pattern over current edges (asserted + already-derived), joined on shared variables. Terms may be variables or fixed entities.", - "items": { - "additionalProperties": true, - "properties": { - "object": { - "$ref": "#/properties/rules/items/properties/body/items/properties/subject", - "description": "A rule term: { \"var\": \"x\" } (a variable) or { \"entity\": { \"entity_type\": \"DeliveryStatus\", \"name\": \"Complete\" } } (a fixed entity used as a constant in the body or head)" - }, - "predicate": { - "description": "Relation name. In a rule body, the reserved \"rdf:type\" makes a type-membership constraint: the object names a class ({ entity: { entity_type: \"Contact\" } }, no name) and matches every entity of that class and its subtypes (rdfs:subClassOf closure), so one rule keyed on a supertype fires for all subtypes. Not allowed in a head or an exists/not_exists filter.", - "type": "string" - }, - "subject": { - "anyOf": [ - { - "additionalProperties": true, - "properties": { - "var": { - "description": "A variable, joined across patterns by name", - "type": "string" - } - }, - "required": [ - "var" - ], - "type": "object" - }, - { - "additionalProperties": true, - "properties": { - "entity": { - "additionalProperties": true, - "properties": { - "entity_id": { - "description": "Entity id (hex), as an alternative to type+name", - "type": "string" - }, - "entity_type": { - "description": "Entity type name (with `name`, names a fixed entity)", - "type": "string" - }, - "name": { - "description": "Entity name (paired with `entity_type`)", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "entity" - ], - "type": "object" - } - ], - "description": "A rule term: { \"var\": \"x\" } (a variable) or { \"entity\": { \"entity_type\": \"DeliveryStatus\", \"name\": \"Complete\" } } (a fixed entity used as a constant in the body or head)" - } - }, - "type": "object" - }, - "type": "array" - }, - "combinators": { - "description": "exists/not_exists filters folded over the body. not_exists is stratified negation — it lets a rule express a universal condition, e.g. derive \"phase complete\" only when not_exists an incomplete deliverable. A negation cycle is rejected.", - "items": { - "anyOf": [ - { - "additionalProperties": true, - "properties": { - "exists": { - "items": { - "$ref": "#/properties/rules/items/properties/body/items" - }, - "type": "array" - } - }, - "required": [ - "exists" - ], - "type": "object" - }, - { - "additionalProperties": true, - "properties": { - "not_exists": { - "items": { - "$ref": "#/properties/rules/items/properties/body/items" - }, - "type": "array" - } - }, - "required": [ - "not_exists" - ], - "type": "object" - } - ], - "description": "An existence filter over the body solutions: { exists: [...] } (semijoin — keep rows with a compatible match) or { not_exists: [...] } (negation/antijoin — keep rows with none)" - }, - "type": "array" - }, - "head": { - "$ref": "#/properties/rules/items/properties/body/items", - "description": "The triple derived once per body solution. Every head variable must be bound by the body; a fixed-entity object derives a constant (e.g. set the rolled-up status to the Complete entity)." - }, - "name": { - "type": "string" - }, - "order": { - "description": "Run order, low to high (a determinism hint)", - "type": "integer" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" -}
- Changed
lbb_decode5 fields changed- removed
Input schema / properties / source_name / descriptionRemoved value: -"Source entity display name" - removed
Input schema / properties / source_type / descriptionRemoved value: -"Source entity type; omit to have the DB recover it from the name" - removed
Input schema / properties / target_name / descriptionRemoved value: -"Target entity display name" - removed
Input schema / properties / target_type / descriptionRemoved value: -"Target entity type; omit to have the DB recover it from the name" - removed
Input schema / properties / use_model_when_forced / descriptionRemoved value: -"Call the model even when the type pair forces one relation (default false — a forced pair is answered by the DB alone)"
- Changed
lbb_ground5 fields changed- removed
Input schema / properties / action / descriptionRemoved value: -"complete = narrowed vocabulary autocomplete; resolve = snap free text to the nearest real vocabulary; audit = groundability report" - changed
Input schema / properties / kinds / descriptionPrevious value: -"[complete/resolve] Restrict to these vocabulary kinds (default: all)"New value: +"Restrict to these vocabulary kinds (default: all)" - changed
Input schema / properties / sample / descriptionPrevious value: -"[audit] Entities to sample for narrowing-recall"New value: +"[audit] Entities sampled for narrowing recall" - changed
Input schema / properties / text / descriptionPrevious value: -"[resolve] Free text to snap to the nearest real vocabulary item"New value: +"[resolve] Free text to resolve" - changed
Input schema / properties / top_k / descriptionPrevious value: -"[complete/resolve] Max results (default 8)"New value: +"Max results (default 8)"
- Removed
lbb_index - Changed
lbb_inspect12 fields changed- changed
Input schema / properties / action / descriptionPrevious value: -"Selects the variant (one of: guide, ontology, ontology_conformance, schema, schema_audit, rules, schema_preview, ontology_search, metadata, entity, edges, state, history, why, traverse, transitions)."New value: +"Selects the variant (one of: guide, ontology, ontology_conformance, schema, ontology_search, metadata, entity, state, history, why, traverse, transitions)." - changed
Input schema / properties / action / enumPrevious value: -[ - "guide", - "ontology", - "ontology_conformance", - "schema", - "schema_audit", - "rules", - "schema_preview", - "ontology_search", - "metadata", - "entity", - "edges", - "state", - "history", - "why", - "traverse", - "transitions" -]New value: +[ + "guide", + "ontology", + "ontology_conformance", + "schema", + "ontology_search", + "metadata", + "entity", + "state", + "history", + "why", + "traverse", + "transitions" +] - removed
Input schema / properties / base_ontology_versionRemoved value: -{ - "minimum": 0, - "type": "integer" -} - removed
Input schema / properties / base_shapes_versionRemoved value: -{ - "minimum": 0, - "type": "integer" -} - removed
Input schema / properties / cursorRemoved value: -{ - "description": "Opaque cursor from the previous page's `next_cursor`. The response is the unified list envelope { object:'list', data, has_more, next_cursor, total_count }; page a high-degree node (entity detail hard-caps its edge sample) by feeding next_cursor back here until has_more is false. Each row carries valid_time for a per-edge timeline.", - "type": "string" -} - removed
Input schema / properties / desired_modeRemoved value: -{ - "enum": [ - "warn", - "reject" - ], - "type": "string" -} - removed
Input schema / properties / direction / descriptionRemoved value: -"Edges out of / into / touching the entity (default both)." - removed
Input schema / properties / offsetRemoved value: -{ - "description": "Legacy alias for `cursor` (still accepted); prefer paging with `cursor`.", - "minimum": 0, - "type": "integer" -} - removed
Input schema / properties / ontologyRemoved value: -{ - "additionalProperties": false, - "properties": { - "format": { - "enum": [ - "auto", - "turtle", - "json_ld", - "rdf_xml", - "csv", - "tsv", - "lbb_json", - "spec" - ], - "type": "string" - }, - "source": { - "description": "Ontology source text", - "type": "string" - } - }, - "required": [ - "source" - ], - "type": "object" -} - removed
Input schema / properties / relation / descriptionRemoved value: -"Filter to a single relation name." - removed
Input schema / properties / row_limitRemoved value: -{ - "description": "Max edges returned this page (server caps at 1000; default 150).", - "exclusiveMinimum": 0, - "type": "integer" -} - removed
Input schema / properties / shapesRemoved value: -{ - "additionalProperties": false, - "properties": { - "format": { - "enum": [ - "auto", - "turtle", - "n_triples", - "n_quads", - "trig" - ], - "type": "string" - }, - "source": { - "description": "SHACL/RDF shape source text", - "type": "string" - } - }, - "required": [ - "source" - ], - "type": "object" -}
- Added
lbb_models - Changed
lbb_query16 fields changed- removed
Input schema / properties / anchor_nameRemoved value: -{ - "type": "string" -} - removed
Input schema / properties / anchor_typeRemoved value: -{ - "type": "string" -} - removed
Input schema / properties / explainRemoved value: -{ - "type": "boolean" -} - removed
Input schema / properties / include_derivedRemoved value: -{ - "type": "boolean" -} - removed
Input schema / properties / max_derivedRemoved value: -{ - "exclusiveMinimum": 0, - "type": "integer" -} - removed
Input schema / properties / max_premisesRemoved value: -{ - "exclusiveMinimum": 0, - "type": "integer" -} - removed
Input schema / properties / max_roundsRemoved value: -{ - "exclusiveMinimum": 0, - "type": "integer" -} - removed
Input schema / properties / max_solutionsRemoved value: -{ - "exclusiveMinimum": 0, - "type": "integer" -} - changed
Input schema / properties / mode / descriptionPrevious value: -"Selects the variant (one of: structured, sparql, shacl, infer, retrieval_premises, analyze)."New value: +"Selects the variant (one of: structured, sparql, analyze)." - changed
Input schema / properties / mode / enumPrevious value: -[ - "structured", - "sparql", - "shacl", - "infer", - "retrieval_premises", - "analyze" -]New value: +[ + "structured", + "sparql", + "analyze" +] - removed
Input schema / properties / query_top_kRemoved value: -{ - "exclusiveMinimum": 0, - "type": "integer" -} - removed
Input schema / properties / relationRemoved value: -{ - "type": "string" -} - removed
Input schema / properties / rulesRemoved value: -{ - "items": { - "additionalProperties": true, - "properties": { - "body": { - "description": "The condition: a basic graph pattern over current edges (asserted + already-derived), joined on shared variables. Terms may be variables or fixed entities.", - "items": { - "additionalProperties": true, - "properties": { - "object": { - "$ref": "#/properties/rules/items/properties/body/items/properties/subject", - "description": "A rule term: { \"var\": \"x\" } (a variable) or { \"entity\": { \"entity_type\": \"DeliveryStatus\", \"name\": \"Complete\" } } (a fixed entity used as a constant in the body or head)" - }, - "predicate": { - "description": "Relation name. In a rule body, the reserved \"rdf:type\" makes a type-membership constraint: the object names a class ({ entity: { entity_type: \"Contact\" } }, no name) and matches every entity of that class and its subtypes (rdfs:subClassOf closure), so one rule keyed on a supertype fires for all subtypes. Not allowed in a head or an exists/not_exists filter.", - "type": "string" - }, - "subject": { - "anyOf": [ - { - "additionalProperties": true, - "properties": { - "var": { - "description": "A variable, joined across patterns by name", - "type": "string" - } - }, - "required": [ - "var" - ], - "type": "object" - }, - { - "additionalProperties": true, - "properties": { - "entity": { - "additionalProperties": true, - "properties": { - "entity_id": { - "description": "Entity id (hex), as an alternative to type+name", - "type": "string" - }, - "entity_type": { - "description": "Entity type name (with `name`, names a fixed entity)", - "type": "string" - }, - "name": { - "description": "Entity name (paired with `entity_type`)", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "entity" - ], - "type": "object" - } - ], - "description": "A rule term: { \"var\": \"x\" } (a variable) or { \"entity\": { \"entity_type\": \"DeliveryStatus\", \"name\": \"Complete\" } } (a fixed entity used as a constant in the body or head)" - } - }, - "type": "object" - }, - "type": "array" - }, - "combinators": { - "description": "exists/not_exists filters folded over the body. not_exists is stratified negation — it lets a rule express a universal condition, e.g. derive \"phase complete\" only when not_exists an incomplete deliverable. A negation cycle is rejected.", - "items": { - "anyOf": [ - { - "additionalProperties": true, - "properties": { - "exists": { - "items": { - "$ref": "#/properties/rules/items/properties/body/items" - }, - "type": "array" - } - }, - "required": [ - "exists" - ], - "type": "object" - }, - { - "additionalProperties": true, - "properties": { - "not_exists": { - "items": { - "$ref": "#/properties/rules/items/properties/body/items" - }, - "type": "array" - } - }, - "required": [ - "not_exists" - ], - "type": "object" - } - ], - "description": "An existence filter over the body solutions: { exists: [...] } (semijoin — keep rows with a compatible match) or { not_exists: [...] } (negation/antijoin — keep rows with none)" - }, - "type": "array" - }, - "head": { - "$ref": "#/properties/rules/items/properties/body/items", - "description": "The triple derived once per body solution. Every head variable must be bound by the body; a fixed-entity object derives a constant (e.g. set the rolled-up status to the Complete entity)." - }, - "name": { - "type": "string" - }, - "order": { - "description": "Run order, low to high (a determinism hint)", - "type": "integer" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" -} - removed
Input schema / properties / shacl_modeRemoved value: -{ - "description": "select returns focus nodes; validate returns a report", - "enum": [ - "select", - "validate" - ], - "type": "string" -} - removed
Input schema / properties / shapesRemoved value: -{ - "items": { - "$ref": "#/properties/body" - }, - "type": "array" -} - removed
Input schema / properties / thresholdRemoved value: -{ - "maximum": 1, - "minimum": 0, - "type": "number" -}
11 tool updates
v0.2.1- First observed
lbb_ask - First observed
lbb_branch - First observed
lbb_commit - First observed
lbb_configure - First observed
lbb_decode - First observed
lbb_ground - First observed
lbb_index - First observed
lbb_inspect - First observed
lbb_observe - First observed
lbb_query - First observed
lbb_search
TDQS
Scored across 8 tools
Each tool has a clearly distinct purpose: lbb_rdf handles RDF/OWL document import/update, lbb_commit writes/retracts graph facts and feedback, lbb_inspect reads graph context/metadata, lbb_query executes SPARQL, lbb_configure manages schema metadata, lbb_branch handles branch lifecycle, lbb_models provides model training data, and lbb_observe captures conversations. Despite some overlap in write operations, the descriptions explicitly delineate boundaries (e.g., lbb_rdf vs. lbb_commit), eliminating ambiguity.
All tools share the 'lbb_' prefix and use lowercase single-word names, which is readable and predictable. However, the second part mixes verbs (inspect, query, commit, configure, observe) and nouns (rdf, branch, models), so it does not follow a strict verb_noun pattern. The consistency of the prefix and clarity of each name mitigate this minor inconsistency.
With 8 tools, the server is well-scoped for its domain (a knowledge graph with RDF support, schema management, branching, and observation). This falls comfortably within the ideal 3-15 range, and each tool provides a distinct capability without redundancy.
The server covers core lifecycle operations: RDF import/update, graph fact writes/retraction, SPARQL querying, schema definition/evolution, branching/merging, model data access, and observation. However, there is no direct RDF deletion (only additive updates via lbb_rdf and retraction via lbb_commit), and no tool for exporting or bulk deleting graph data, leaving minor gaps that agents can work around.
Maintenance
Related MCP Connectors
MCP server for querying BrainKB, a knowledge base for neuroscience knowledge graphs.
Repository knowledge graph MCP server for codebase understanding and debugging.
MCP server for searching Airweave collections with natural language queries.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server that enables graph database interactions with Neo4j, allowing users to access and manipulate graph data through natural language commands.-
- AlicenseAqualityDmaintenanceAn MCP server that enables AI-powered exploration of RDF data and SPARQL querying via RDF4J. It provides tools for executing queries, searching knowledge graph resources, and retrieving schema summaries.131MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that builds a knowledge graph from code and text documents, enabling Q\&A and implementation planning via tools like graph_create, graph_plan, and graph_query.7MIT
- FlicenseNot gradedqualityDmaintenanceMCP server that connects AI assistants to the Data Graphs knowledge graph platform, enabling natural language search, exploration, and querying of graph data.4 npm-