Skip to content

MCP server for AI agents

View Markdown

@littlebigbrain/mcp is an MCP server. It gives an agent a small set of tools over your graph. With them, an agent can:

  • search by meaning,
  • follow links with SPARQL,
  • cite the commit it read,
  • write records that your schema checks.

A typical agent turn has three calls. Search finds a starting record. A SPARQL query follows its links to the answer. The answer cites the rows and the commit.

The quickstart shows these steps in code. Your first search through MCP shows them as tool calls.

Run the server locally over stdio with npx @littlebigbrain/mcp. Or connect to the hosted endpoint with OAuth sign-in. The server wraps the @littlebigbrain/client SDK. This page describes version 0.16.0, which uses client 0.31.0.

Source and issues: github.com/littlebigbrains/lbb-mcp (Apache-2.0).

The agent skills index provides focused instructions for connecting to MCP and querying a graph with its real vocabulary and commit evidence. It follows Agent Skills Discovery v0.2.0; each entry points to a Markdown skill and includes a SHA-256 digest. Verify the downloaded file against that digest before loading its instructions.

The AI catalog lists the MCP server, both OpenAPI schemas and two agent skills, with stable identifiers and example queries for semantic discovery. The same manifest is available at the current ARD discovery path. Follow its MCP server card to connect. The card describes the hosted Streamable HTTP endpoint and requires your existing stack slug in X-LBB-Stack. Follow the OAuth protected-resource metadata for sign-in, then connect to https://mcp.littlebigbrain.com with that header. You can also use the stack-specific URL described below.

The current SEP-2127 format describes identity and connection details. The server reports capabilities and the available tools when your MCP client connects. A compatibility card also provides the earlier serverInfo and capabilities fields for clients that use the original well-known discovery path.

The marketing site also exposes browser tools through WebMCP where the browser supports it. Agents can find and open public pages, read the current page and estimate Cloud or Pro pricing. The contact form lets an agent prepare an enquiry for the user to review and send. These browser tools operate on the marketing site; graph queries use the authenticated MCP server described here.

Discover the service documentation through DNS

Section titled “Discover the service documentation through DNS”

Query the SVCB record at _index._agents.littlebigbrain.com to find the public service documentation index. It advertises docs.littlebigbrain.com, HTTPS on port 443 and the path /llms.txt through the experimental DNS-AID well-known parameter, encoded as key65409 pending registration:

_index._agents.littlebigbrain.com. 3600 IN SVCB 1 docs.littlebigbrain.com. (
mandatory=alpn,port,key65409 alpn="h2,http/1.1" port=443 key65409="/llms.txt"
)

Fetch the index to find the MCP, HTTP API and authentication documentation. A DNS-AID client needs to understand the mandatory path parameter to use this record. The hosted MCP connection still uses your stack’s URL and OAuth sign-in, as described below.

The set has one tool per task. It does not mirror the HTTP routes.

Tool Purpose
lbb_inspect graph context and exact reads: guide, graphs, paginated ontology/schema, publication status, conformance, metadata, entity, ontology change suggestions
lbb_rdf import full RDF/OWL or add axioms using INSERT DATA (an update that removes asks the person to approve)
lbb_query analytical reads: structured query, SPARQL text, search by meaning, questions in plain words, canned analysis, and four tools for writing queries (names, describe, commit_at, compare)
lbb_embeddings search setup, read only: list and inspect embeddings, preview the text and cost of embedding a class
lbb_embeddings_manage declare or refresh an embedding (spends model tokens)
lbb_embeddings_delete delete an embedding and its vectors (asks the person to approve)
lbb_fit_sources fit from text, read only: list the fit sources, preview the text the fit reads
lbb_fit_sources_manage declare, dry-run or refresh a fit source (spends model tokens)
lbb_fit_sources_delete delete a fit source (asks the person to approve)
lbb_workflows starter workflows, read only: the starter workflows this server offers, the graph’s triggered workflows with their status, the preview of creating one, the workflows with triggers, what a watch has not sent yet, the history of a workflow’s settings, workflow tests, and what the runs did with each input
lbb_workflows_manage create a workflow from a starter workflow on a class or change one, ask it to run now, pause and resume, define a workflow with triggers of your worker, use earlier settings of a workflow again, or start, answer and cancel a workflow test
lbb_workflows_delete delete a triggered workflow or a workflow with triggers (asks the person to approve)
lbb_reviews review items, read only: the questions for a person, one page at a time, one item, and the items grouped by pattern
lbb_reviews_manage file a review item, answer one or a batch, comment, or withdraw an item (an answer that commits asks the person to approve)
lbb_files files, read only: the graph’s files with their parse status, filtered by status or name, and the parsed lines of a page range, each with its box on the page
lbb_commit preview/write statements or entity properties, and retract them (a retract asks the person to approve)
lbb_model_choice the model each use runs, trials of other models with their reports, and the model choices of the workflow steps
lbb_model_choice_manage start or stop a trial, switch a use to a tested model, revert (a trial spends model and judge tokens); set or remove the model of a workflow step
lbb_models evaluation and training inputs, and the managed models’ activity
lbb_evals label results as relevant or not, save evaluation queries, and check them at the current commit; read the model checks and correct one; keep datasets of examples
lbb_evals_delete delete a dataset of examples (asks the person to approve)
lbb_configure preview/define/evolve native metadata, list and apply ontology starters, suggest an ontology change, activate SHACL shapes, or keep the graph’s notes for questions in plain words (an evolve that removes and a reject-mode publish ask the person to approve)

Call lbb_inspect with action: "ontology" before the agent writes a query or a statement. The agent then uses only the types, relations and properties of the pinned commit.

Read tools accept detail: "compact" | "standard" | "full". The default is compact, which returns this envelope: {summary, data, counts?, truncated?, rows_shown?, next?}.

The client starts the server as a local process. They talk over standard input and output (stdio). This needs Node.js 18 or later.

Copy the complete endpoint and a stack API key from Connect in the console. Add them to your client’s MCP configuration, such as .mcp.json or mcp.json:

{
"mcpServers": {
"lbb": {
"command": "npx",
"args": ["-y", "@littlebigbrain/mcp"],
"env": {
"LBB_BASE_URL": "https://<tenant-short-id>--<stack-slug>.db.eu.littlebigbrain.com",
"LBB_API_KEY": "<your-stack-api-key>"
}
}
}
}
Variable Meaning
LBB_BASE_URL Required. The complete stack endpoint from Connect. Without it, the server starts and lists its tools, and each tool call fails with a message that asks for it.
LBB_API_KEY The stack API key. Keep it in your local configuration.
LBB_GRAPH Optional. The graph for calls that pass no graph. The default is main.
LBB_MCP_DESTRUCTIVE Optional. allow runs a call that deletes or removes data without a question when the client cannot show one. Leave it unset to keep the question. See Approve destructive writes.

The hosted endpoint is https://mcp.littlebigbrain.com. It uses the MCP OAuth sign-in. You sign in to your little big brain account. No stack API key is stored on your machine. Claude-style clients use the URL of one stack:

{
"mcpServers": {
"lbb": {
"url": "https://mcp.littlebigbrain.com/mcp/<your-stack-slug>"
}
}
}

A client that supports remote MCP auth runs the sign-in by itself:

  1. It receives an authorization challenge from the endpoint.
  2. It opens the browser sign-in.
  3. It sends the resulting access token with each call.

The endpoint checks the token and confirms that your account owns the stack. It then creates a short-lived session and runs the tools on that stack. Add ?graph=<name> to the URL to change the default graph from main.

Response Cause
401 with an authorization challenge The access token is missing or not valid. The client signs in again.
402 subscription_required The account’s subscription setup is not complete. Finish it in the console.
403 insufficient_scope A claimed agent token lacks the trusted mcp:access permission.
404 unknown stack Your account does not own a stack with that slug.
405 The request was not a POST.
503 The stack’s endpoint is not ready yet. Retry from the console.

For agent identity registration, follow auth.md. The guide uses WorkOS’s generated instructions once the issuer enables Agent Registration, and the existing human OAuth flow until then. A claimed agent’s access token must carry mcp:access, granting read and write access to its organization’s stacks. Each request checks revocation, the completed service-auth claim and the delegated user’s active membership before opening a database session. See agent authentication.

You can also serve the tools over streamable HTTP with a stack API key. Run the HTTP server from your own Node process, as in Embed in a Node process.

Run the quickstart first. It creates the support graph with tickets, bugs, releases and a ticket embedding. Then connect one of the transports above to the same stack. Ask your agent: which release fixed the login bug?

The agent reads the schema first, with lbb_inspect:

{ "action": "ontology", "graph": "support" }

It lists the types Ticket, Bug and Release and the links REPORTED_AS and FIXED_IN. Next, the agent searches by meaning with lbb_query:

{
"mode": "search",
"graph": "support",
"text": "users cannot sign in after the upgrade",
"top_k": 3
}

The top hit is the ticket login fails after 3.1. The agent takes its iri and the response’s served_at_seq into the next call. It follows the links with lbb_query at that commit:

{
"mode": "sparql",
"graph": "support",
"as_of_commit_seq": 1,
"query": "PREFIX dr: <https://littlebigbrain.com/r/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?bug ?release WHERE { <iri-of-the-top-hit> dr:reported_as ?b . ?b rdfs:label ?bug ; dr:fixed_in ?r . ?r rdfs:label ?release . }"
}

The result has one row:

bug release
session token expiry Release 3.2

The answer comes from two stored links. The agent does not infer the connection from similar text. It can cite the rows and commit 1. Replay an answer explains how long a commit stays readable.

An agent can also build a graph with tools alone. The calls below write the quickstart records into a new graph, support-agent. The search and query above then work on it with "graph": "support-agent".

Use a new graph for this. lbb_commit writes records by type and name, and the quickstart writes them with a key. On one graph, that gives two records for each ticket.

1. Preview the schema. Call lbb_configure. With dry_run: true, the call shows the result and writes nothing:

{
"action": "define_ontology",
"graph": "support-agent",
"dry_run": true,
"entity_types": ["Ticket", "Bug", "Release"],
"relations": [
{ "name": "REPORTED_AS", "source": "Ticket", "target": "Bug" },
{ "name": "FIXED_IN", "source": "Bug", "target": "Release" }
]
}

Send the same call without dry_run to create the graph. A repeat of the same call returns changed: false.

2. Add a field. Call lbb_configure again:

{
"action": "evolve_ontology",
"graph": "support-agent",
"ops": [{ "op": "add_property", "name": "description", "value_type": "text" }]
}

3. Write the records. Call lbb_commit with links (triplets) and field values (entity_properties):

{
"graph": "support-agent",
"triplets": [
{ "source": { "type": "Ticket", "name": "login fails after 3.1" }, "relation": "REPORTED_AS", "target": { "type": "Bug", "name": "session token expiry" }, "evidence": "Support triage, 2 Sep" },
{ "source": { "type": "Ticket", "name": "invoice PDF is blank" }, "relation": "REPORTED_AS", "target": { "type": "Bug", "name": "PDF fonts missing" }, "evidence": "Support triage, 3 Sep" },
{ "source": { "type": "Ticket", "name": "CSV export times out" }, "relation": "REPORTED_AS", "target": { "type": "Bug", "name": "export runs on one thread" }, "evidence": "Support triage, 4 Sep" },
{ "source": { "type": "Bug", "name": "session token expiry" }, "relation": "FIXED_IN", "target": { "type": "Release", "name": "Release 3.2" }, "evidence": "Release notes 3.2" },
{ "source": { "type": "Bug", "name": "PDF fonts missing" }, "relation": "FIXED_IN", "target": { "type": "Release", "name": "Release 3.1.1" }, "evidence": "Release notes 3.1.1" }
],
"entity_properties": [
{ "type": "Ticket", "name": "login fails after 3.1", "properties": { "description": "Since 3.1, people are sent back to the login screen right after they type their password." } },
{ "type": "Ticket", "name": "invoice PDF is blank", "properties": { "description": "Downloaded invoices open as empty pages." } },
{ "type": "Ticket", "name": "CSV export times out", "properties": { "description": "Exports with more than 10,000 rows stop after 30 seconds." } }
]
}

The response reports commit_seq and echoes written_properties, the fields that were stored.

4. Wait for publication. Call lbb_inspect until state is current:

{ "action": "publication", "graph": "support-agent" }

5. Turn on search. Call lbb_embeddings_manage to declare an embedding on the Ticket type:

{
"action": "declare",
"graph": "support-agent",
"class": "https://littlebigbrain.com/class/ticket",
"from": ["label", "description"]
}

A declaration spends model tokens. Preview the text first with lbb_embeddings and action: "preview". Then call lbb_embeddings with action: "get" and name: "ticket" until the embedding reports serving. Search by meaning covers each option.

lbb_configure has these schema actions. Each one that writes takes dry_run: true to preview without writing.

Action Arguments Behavior
define_ontology graph (required), entity_types and relations, or source with format; merge_default Creates the graph with its types and links. On an existing graph, it adds what is new.
evolve_ontology ops (required, in order), graph Adds, renames, widens, narrows or removes types, links and fields.
list_starters graph Lists the ontology starters (crm, documents, work) with each one’s status on the graph.
apply_starter starter (required), expected_ontology_version, graph Adds what the graph lacks of a starter in one ontology version. Refused with starter_conflict when the graph holds a term differently.
suggest_ontology_change title (required), change, facts, rationale, anchor, key, evidence, agent, graph Files the change as a suggestion for a person to review. change holds up to 128 evolve_ontology operations. facts holds graph facts (triplets, entity_properties, replace_owned, summary) that accepting commits; change may be empty when facts is set. Nothing changes until a person accepts it.
publish_schema shapes (required), desired_mode, confirm_restrictive, ontology, graph Activates SHACL rules. In reject mode it asks the person to approve first. See Validate writes.
get_rewrite_profile graph Reads the graph’s profile: the notes and worked examples the model reads for every question in plain words, and its version.
set_rewrite_profile notes, examples (up to 20 {question, sparql, note?}), expected_version, graph Stores the profile. Pass the version you read as expected_version; another write first answers 409 conflict.

For define_ontology, give both entity_types and relations. Each type is a name or { "name": … }. A type object accepts name, description, resolvable and super_types. Another field fails with HTTP 400, which lists the accepted fields.

A link object has name, source and target, as in the example. It also accepts description, inverse, symmetric, transitive, reducer, cardinality and temporal_semantics.

merge_default: true adds the built-in AI-context vocabulary under your types. The tool’s default is false.

On an existing graph, define_ontology refuses a change that narrows a link’s source or target types. The error is ontology_restrictive_change. Use evolve_ontology with narrow_relation for that change.

super_types and add_super_types store each parent as an rdfs:subClassOf statement. A query with entailment: "subclass" then matches the records of each subtype. See Reasoning.

define_ontology also takes a source document instead of names. Its format is auto (the default), turtle, json_ld, rdf_xml, csv, tsv, lbb_json or spec. lbb_json expects a serialized ontology. spec is the JSON form above.

From an RDF or OWL document, define_ontology keeps only types and links. To store the whole document as queryable facts, use lbb_rdf.

evolve_ontology takes these op values:

op Arguments
add_entity_type name
add_super_types entity_type, super_types
add_relation name, domain, range, cardinality, temporal_semantics, reducer, inverse_name, transitive, symmetric
add_property name, value_type (bool, i64, f64, date_time, keyword, text or bytes; default text), required, unit (of an i64 or f64 field, such as mm, kg or h), allowed_values
set_property_unit property, unit (leave it out to clear the unit)
set_property_constraint property, allowed_values (an empty list clears the constraint)
widen_relation relation, add_domain, add_range
narrow_relation relation, remove_domain, remove_range
rename_entity_type, rename_relation from, to
set_relation_inverse relation, inverse_name
set_relation_cardinality relation, cardinality
remove_entity_type, remove_relation name

allowed_values is enforced: a commit with another value for the field fails. It works on keyword, text, i64 and bool fields. Values stored before a constraint or a unit change stay as they are: a unit change converts nothing, and a constraint checks only later commits.

A removed type or link stays readable in old records. New commits that use it fail. Preview a narrowing or removing change with dry_run: true. The preview reports conflicts with current data. Repair them, then apply. allow_data_conflicts does not skip that check. A call that removes asks the person to approve first. See Approve destructive writes.

lbb_commit has three modes. It picks retract when you pass retract_edges or retract_entities, and search_feedback when you pass search_feedback. Otherwise it writes facts.

Argument Meaning
triplets Links. Each has source and target as { "type", "name" }, a relation, and optional evidence, confidence (0 to 1) and valid_time (start, end, granularity, source_text).
entity_properties Field values. Each item is { "type", "name", "properties" }.
entity_embeddings Vectors you computed yourself. Each item has type, name, optional key or entity_id, model_id, vector, and optional metric.
dry_run Checks a facts commit, returns its SHACL report, and writes nothing.
observed_at An RFC 3339 time. The commit is recorded as of that time, for backfills.
edge_idempotency append (the MCP default) or skip_unchanged.
idempotency_key Optional. See Idempotent writes.
retract_edges Links to remove, each { source, relation, target }.
retract_entities Records whose current links are all removed. Their history stays readable.
search_feedback Relevance labels for search results.

Values are converted to each field’s declared type. The string "52" lands in an i64 field, and "2026-06-26" in a date_time field. An undeclared field fails the commit with HTTP 400. Declare the field with add_property first.

The source and target of a triplet carry only type and name. The tool drops a key there. entity_properties items pass a key through.

retract_edges and retract_entities also match records by type and name. A record written with a key does not match, and the call returns no_op: true. A retract asks the person to approve first. See Approve destructive writes.

A graph whose first write is RDF refuses lbb_commit with HTTP 400 rdf_native_graph. See Add RDF and OWL.

For search_feedback, send the query, an optional search_id, and labels. Each label has a target, a grade, and optional rank, score, reason and split. Grades are 3 (good), 1 (partly relevant) and 0 (bad).

A target is { "kind": "entity", "entity": { "entity_type", "name" } }. The kinds assertion, observation and concept name other targets.

lbb_query has nine modes. Each read answers from one commit.

mode: "sparql" runs SPARQL 1.1 SELECT and ASK queries. CONSTRUCT and DESCRIBE return HTTP 400. Use the /sparql endpoint for them.

Argument Meaning
query The query text. Required unless cursor is set.
entailment none (default), subclass, rdfs or owl. See Reasoning.
consistency eventual reads the latest published commit. strong also applies the newest writes.
min_indexed_seq Read at this commit or a later one.
as_of_commit_seq Read the graph as it was at this commit.
row_limit Rows per page. See Pages and large results.
cursor The next.cursor of the previous page.
request The user’s words behind the query. The server records an eval trace, and the result names its trace_id.
detail, graph As for every tool.

Without as_of_commit_seq, the tool reads the graph’s latest commit number first. Every page of that query then reads that commit.

A valid-time as_of is refused before any HTTP call, also inside an older cursor. Use as_of_commit_seq for an earlier commit.

A SPARQL query can also search by meaning. The pattern ?x <https://littlebigbrain.com/search#similarTo> "words" binds the records nearest to the words, and the other patterns of the query filter and join them. The result then carries search: the plan (nearest, filter_first or search_first), the hits asked for and bound, and complete. When complete is false, notes says so. With search:rerank true and search:relevance ?r, the rerank model keeps the hits that answer the words and binds its answer; search.rerank reports how it went. Search inside a SPARQL query lists the patterns.

mode: "structured" takes a JSON body instead of query text. A predicate is a link name, in any case. This body counts the tickets fixed in each release:

{
"mode": "structured",
"graph": "support",
"body": {
"patterns": [
{ "subject": { "var": "t" }, "predicate": "REPORTED_AS", "object": { "var": "b" } },
{ "subject": { "var": "b" }, "predicate": "FIXED_IN", "object": { "var": "r" } }
],
"group_by": ["r"],
"aggregates": [{ "func": "count", "as": "tickets" }]
}
}

Each release comes back in groups, with its record under keys.r and the count under aggregates.tickets.

Body field Shape
patterns [{ subject, predicate, object }]. A term is { "var": "x" } or a fixed { "entity": { "entity_type", "name" } }.
filters { "compare": { "op", "left", "right" } }, or and, or, not of filters. op is eq, ne, lt, le, gt or ge.
Filter operand { "var" }, { "property": { "var", "field" } }, or { "value": … } with one of str, i64, f64, bool, date_time, entity.
group_by Variables to group by record.
group_keys { "property": { var, field, as } }, or { "date_bucket": { var, field, granularity, as } } with year, month, week, day or hour. Values come back under value_keys.
aggregates { func, as } with count, sum, avg, min or max, and optional operand and distinct.
having Filters over the groups.
order_by [{ var, descending }]
select, distinct, as_of_commit_seq Projection, duplicate removal, and a commit pin.

To count groups without reading them, add an equality having and set row_limit: 1. Then read row_page.total. A body with combinators fails: write OPTIONAL, UNION, MINUS or EXISTS as SPARQL text.

mode: "search" takes text. It searches every searchable type, or one embedding. Other arguments are top_k (at most 200), probe, include: ["text"], filter, explain and request.

A filter item names a class or a link. A class item is { "class": … }. A link item is { "via": …, "to": … }. Its direction is out (the default) or in.

With explain: true, the call returns the plan without calling the model. Search by meaning explains filters and planning.

mode: "question" takes a question in plain words and calls POST /v1/query/ask. The server describes the graph and picks the kind of question. Its model runs queries in a short loop, reads the rows, and answers. The result has the answer with the records it cites, the steps, the route, the query whose rows hold the answer, the rows and the eval trace_id. It takes about 6 to 12 seconds.

Argument Meaning
question The question, 1 to 4,000 characters. Required.
context Notes on what the data means: units, names to prefer. Send the same text with each question.
route Fixes the kind of question: lookup, aggregate, search, history, schema or unanswerable.
limit The rows each query returns, 1 to 1,000.
anchor The IRIs of records the question is about, at most 10. The queries use them directly.
timeline Dates that stand for the graph’s commits, at most 200: [{date, as_of_commit_seq, label?}]. A question about a date reads the commit of the latest point on or before it.
detail, graph As for every tool.

The result lists in linked the names of the question that the server found in the graph, and in anchors what it read about each IRI of anchor. A history question returns history: the commit the server read for its date, and for an answer from a comparison the rows added, removed and changed with totals. Each list shows at most 20, 100 or 500 entries by detail; history.shown and a note say when a list is cut. The rows are those of the query the answer stands on. The answer and run arguments of earlier versions are gone: the mode always answers.

When the client asks for progress (a progressToken in the call’s _meta), the tool sends each step as a progress notification: the route, each tool call of the loop, and the answer. The result stays the same. Cancelling the call stops the server’s work.

Use this mode when the agent has not read the schema. When it has, a query it writes itself in mode: "sparql" needs no model call. An unanswerable route means the graph does not hold the data. Answer questions in plain words covers the routes and the limits. It also lists the data that goes to the model providers.

Four modes give an agent that writes its own SPARQL what only the server knows. None calls a model.

Mode Arguments Use it when
names text, limit (1 to 10) The question names a record. The result groups the candidates by name, the one to prefer first; put its IRI in the query.
describe question, or classes and properties as IRIs The agent needs classes or properties it has not seen. The result is the server’s text: how many sampled instances hold each property, and the values of small classes. detail: "full" adds the JSON.
commit_at date (YYYY-MM-DD) or moment (RFC 3339) The question names a date. Run the query with mode: "sparql" and the commit it returns as as_of_commit_seq.
compare query, before, after, key, max_rows, limit The question asks what changed. before and after are each {as_of_commit_seq}, {date} or {moment}. The result has added, removed and changed as lexical rows, with totals; pass next back for the next page.

mode: "analyze" returns counts with a Vega-Lite chart. Set metric to entity_types, relations, overview, or sparql with a structured body in sparql. chart is bar (default) or pie, and top_k keeps the largest values.

A query page holds at most row_limit rows. The default depends on detail: 20 for compact, 100 for standard, 1,000 for full. The maximum is 5,000.

Each page reports row_page: {returned, total, offset, limit, has_more, next_offset?}. Its summary says returned X of Y rows. When more rows exist, next holds the arguments for the next page:

{ "mode": "sparql", "cursor": "eyJhc19vZl9jb21taXRfc2Vx…", "row_limit": 2, "detail": "compact" }

Pass next back to lbb_query until it is absent. You do not write LIMIT and OFFSET. The cursor holds the query, the graph and the commit of page 1. New writes between pages do not change the rows of later pages.

A cursor must match the arguments sent with it. A different query, graph, row_limit, entailment, consistency or min_indexed_seq fails.

A page keeps whole values within 80,000 bytes of UTF-8. So a page can be shorter than row_limit. Follow next even then. A cursor moves on only by the rows you received.

If one row alone is too large, project fewer fields. Or read that row through the SPARQL HTTP endpoint.

Other read tools shorten large values by detail. compact keeps 5 items per list and 300 characters per string. standard keeps 20 and 1,000. full keeps 100 and 5,000. A shortened result sets truncated and names the next detail level in next.

A list page (data with has_more and next_cursor) asks the server for at most the items its detail level keeps, so detail never shortens the page. A page over 80,000 characters is asked for again with fewer items. Its next_cursor then continues after the last item shown. next_cursor is never shortened.

lbb_inspect reads graph context and exact records:

Action Returns
guide Counts, types, links and suggested next calls. For a missing graph, the list of graphs and how to create one.
graphs The graphs of the stack. Works before any graph exists.
publication Whether writes are published: state, head_seq, published_seq, lag_commits.
ontology Types, links and fields, with counts, in complete pages.
schema The active schema and SHACL metadata, in complete pages. It runs no validation.
ontology_conformance The published SHACL report: conforms, result_count, validated_at_seq.
ontology_search Types, terms and links that match query, up to top_k.
metadata Graph status, including the latest commit.
entity One record by entity_id, or by entity_type and name: its fields and its links. Accepts as_of_commit_seq. A valid-time as_of is refused before any HTTP call.
ontology_suggestions The ontology change suggestions and their state, with identity_count and fact_count (the links and field rows accepting commits). status keeps one state: open, accepted, dismissed or superseded. limit is 50 by default.

ontology and schema return complete entries. page_size sets entries per page: 50 by default, at most 500. section limits the pages to one list, such as entity_type_defs, relation_defs, property_defs, classes or relations.

Pass the returned next arguments until they are absent. Nested values are never shortened, also with detail: "compact".

A single entry can exceed the output budget. The result then carries entry_fragment. Join its serialized_json parts in char_offset order, then parse the JSON.

A schema change between pages fails the cursor, so pages never mix versions. Start again without cursor after a change.

lbb_rdf stores RDF statements as graph facts. It has two actions:

Action Arguments Behavior
import source (required), format (turtle default, ntriples, nquads, trig), base_iri, blank_node_scope, idempotency_key, graph Stores a complete document: lists, labels, comments and OWL statements.
update update (required), idempotency_key, graph Runs SPARQL Update INSERT DATA.

import accepts the default RDF graph only. An N-Quads or TriG document must contain only default-graph statements.

update accepts only INSERT DATA. DELETE, WHERE, CLEAR and the other forms return HTTP 400 and change nothing. Named graphs are refused too.

A graph whose first write is RDF is RDF-native. It refuses later lbb_commit writes with rdf_native_graph. Decide the write path before the first write. A graph first written with lbb_commit accepts lbb_rdf as well.

This call adds a type hierarchy to the support graph. It states that every bug is an issue:

{
"action": "update",
"graph": "support",
"update": "INSERT DATA { <https://littlebigbrain.com/class/bug> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <https://example.org/support/Issue> }"
}

The response has accepted: true, which means the statement is stored. Queries use it once its commit is published.

Wait for publication with lbb_inspect action: "publication". Then check the answer with entailment:

{
"mode": "sparql",
"graph": "support",
"entailment": "subclass",
"query": "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?issue WHERE { ?b a <https://example.org/support/Issue> ; rdfs:label ?issue } ORDER BY ?issue"
}

The query returns the three bugs. Without entailment, it returns no rows. Reasoning & inference lists the OWL statements that entailment: "owl" applies.

To read the stored statements, query with entailment: "none" and follow each next cursor:

SELECT ?s ?p ?o WHERE {
?s ?p ?o
} ORDER BY ?s ?p ?o

RDF statements cannot be removed or replaced. An import of a changed document adds its new statements and keeps the old ones.

To revise a document, import it into a new graph and check it. Then move your clients to that graph. Keep the original graph until you choose to delete it.

A retry with the same content writes nothing new. To write the same content again on purpose, pass a new idempotency_key.

SHACL is the W3C standard for validation rules. lbb_configure with action: "publish_schema" activates a set of rules:

{
"action": "publish_schema",
"graph": "support",
"dry_run": true,
"desired_mode": "warn",
"shapes": {
"format": "turtle",
"source": "@prefix sh: <http://www.w3.org/ns/shacl#> . @prefix dclass: <https://littlebigbrain.com/class/> . @prefix dr: <https://littlebigbrain.com/r/> . <https://example.org/shapes/Ticket> a sh:NodeShape ; sh:targetClass dclass:ticket ; sh:property [ sh:path dr:reported_as ; sh:maxCount 1 ; sh:message \"A ticket is reported as at most one bug.\" ] ."
}
}
  • shapes.format is auto, turtle, n_triples, n_quads or trig.
  • desired_mode is off, warn or reject.
  • confirm_restrictive is accepted. The server does not read it.
  • ontology must match the current schema. Change the schema with define_ontology or evolve_ontology.

The preview parses the rules and checks them against the schema. It writes nothing and starts no validation job. It does not check your data.

Tighten rules in this order:

  1. Publish in warn mode.
  2. Read the report with lbb_inspect action: "ontology_conformance".
  3. Repair the records it lists.
  4. Publish the same rules in reject mode.

In reject mode, a write that breaks a rule fails, and the error lists the violations. A publish in reject mode asks the person to approve first, with the target classes of the rules. See Approve destructive writes. lbb_commit with dry_run: true returns the same report without writing. See Query and validate.

A failed call sets isError. Its structuredContent.error keeps every field of the server error:

{
"error": {
"type": "not_found_error",
"code": "graph_not_found",
"message": "graph 'nope' does not exist for this tenant",
"param": "graph",
"request_id": "8661a030-…",
"doc_url": "https://docs.littlebigbrain.com/sdks/errors/#graph_not_found",
"status": 404
}
}

An error the tool finds before any HTTP call has type and code tool_error. Examples are an unknown argument and a cursor that does not match its query.

A graph error can name only a storage key. The tool then rewrites the message. The new message lists the stack’s graphs and the graph value to pass.

Without an idempotency_key, lbb_commit and lbb_rdf derive one from the graph and the content. A retry with the same content writes nothing new. lbb_commit then returns the first result with idempotent_replay: true.

To repeat an identical write on purpose, pass a new idempotency_key.

lbb_commit sets edge_idempotency to append by default. New evidence on an existing link is then recorded. Pass skip_unchanged for backfills that you run again. It skips exact copies of current links, and repeats that differ only in evidence.

A call that deletes or removes data asks the person first. The server sends the question through the client with MCP form elicitation. The question names what the call removes first, with the counts, for example Delete the embedding "ticket" of graph "support" and its vectors?. It has one confirm box.

  • When the person approves, the call runs.
  • When the person declines or cancels, nothing is sent. The tool returns an error with the code approval_declined.
  • When the approval request fails, nothing is sent either. The code is approval_failed.

These calls ask:

Call What the question names
lbb_embeddings_delete the embedding and its graph
lbb_fit_sources_delete the fit source and its graph
lbb_workflows_delete the workflow, its starter workflow and its graph
lbb_evals_delete the dataset, its id, its item count and its graph
lbb_commit with mode: "retract" the counts, then each link and record it removes (the first 20 of each, then a count)
lbb_rdf with an update that holds DELETE, CLEAR, DROP, MOVE or COPY each operation that removes, quoted on lines of its own, then the forms of the other operations
lbb_configure with action: "evolve_ontology" and remove_entity_type, remove_relation, or narrow_relation with remove_domain or remove_range the counts, then each type and link it removes and each type it takes out of a link’s source or target types (the first 20, then a count)
lbb_configure with action: "publish_schema" and desired_mode: "reject" the target classes of the rules and the size of the source
lbb_reviews_manage answer or answer_batch that accepts or corrects a value item, or accepts a change item the count and the effect: how many items commit a value and how many apply an ontology change, then each item with its value, or with what its change removes

The question escapes newlines and other control characters in names and values, so a name cannot add lines of its own.

lbb_model_choice_manage put_choice and delete_choice ask nothing. They change only the model of a workflow step’s future calls, remove no data, and a choice is set again in one step.

Some calls read first, so the question can name what they change. lbb_evals_delete reads the dataset. When it does not exist, that read fails with 404, and nobody is asked. A review answer reads its items, and the suggestion of each change item. An answer that commits nothing asks nothing: a reject, a cant_tell or a choose, and an answer to an item that is answered, withdrawn or in test scope. The answer to an item that Evals filed (a check, a calibration question, a golden) is a grade or a choice, so it asks nothing either. A preview (dry_run: true) of an evolve_ontology or a publish_schema writes nothing, so it does not ask.

Some clients cannot show the question: a client without elicitation, or with URL elicitation only. The HTTP transport of this server, which the hosted endpoint uses, handles each call as a request of its own, so it cannot ask either. Then the call fails with approval_required, and nothing is sent. No tool argument changes that, since the agent sets the arguments. The person who runs the MCP server can set LBB_MCP_DESTRUCTIVE=allow in its environment. The server then runs these calls without a question when the client cannot show one.

The hosted endpoint does not set it. There, every call that deletes or removes data answers approval_required. Make that change in the console or with the TypeScript or Python SDK instead.

The server writes one JSON line per tool call to standard error:

{"event":"mcp_tool_call","tool":"lbb_embeddings_delete","outcome":"error","code":"approval_declined","approval":"declined","request_id":7}

action names the call’s action, approval what the approval step decided (approved, declined, cancelled, failed, unavailable or allowed_by_setting), and request_id the JSON-RPC request. A line holds no arguments, names or texts.

Tool Actions
lbb_embeddings list: each embedding’s status, progress and lag, and the graph’s model. get: one embedding by name. preview: the text of sample records and the facts of a class, with no model call.
lbb_embeddings_manage declare: a new embedding or a changed recipe (class, from, exclude, title, name). refresh: one embed step now, by name. model: move every embedding of the graph to model (and dim).
lbb_embeddings_delete Deletes the embedding name and its vectors, once the person approves.
lbb_fit_sources list: each fit source’s status, the suggestions it filed and this month’s spend. get: one by name. preview: the text the fit reads for a few records of a class, with no model call.
lbb_fit_sources_manage declare: a fit source on a class (class, from, exclude, context, name); the server reads every record and files ontology suggestions. dry_run: the proposals for up to 3 records, nothing filed (spends model tokens). refresh: ask the job to run now, by name (returns at once).
lbb_fit_sources_delete Deletes the fit source name, once the person approves; its suggestions stay.
lbb_workflows starters: the starter workflows this server offers, with their group, their settings and whether each can run here. list: the graph’s triggered workflows with their state, lag, counters, spend and last error. get: one, by starter and name, with its own status in details. preview: what creating a workflow would do for starter, class, fields, exclude and params (and workflow, owns and batch for a developer workflow, whose preview shows its first trigger messages): the resolved watch, the instance count and the starter workflow’s own preview. It writes nothing. workflows: the workflows with triggers, or one by name, with each one’s target type, shards, routing field, window, trigger and shard inboxes. queue: what a workflow’s watch has not sent yet, by name, each record with its shard and origin, one page in data (cursor takes the answer’s next_cursor; limit, trigger). run_results: what a workflow’s runs did with each input, by name, newest run first: the input, its outcome and its values with their quotes and review items, one page in data (run for one run with run_counts, outcome, iri, cursor, limit). revisions: the history of a workflow’s settings by name, newest change first (cursor, limit 1 to 100): each item a revision (its settings, its id wrev_…, the hash of those settings) with who changed it, when, how, and bound (the one in use). revision: one revision by name and revision; compare two by their settings. tests: one page of the graph’s workflow tests, newest first (name, limit, cursor). tests_get: one test by id, with each input’s text and, per value, its quote, its support and what the live workflow does with it.
lbb_workflows_manage use: create a workflow from a starter workflow on a class, or change one (starter, class, fields, exclude, params, name, gate, budget_usd_per_month); the workflow reads every instance, then follows each published commit. A developer workflow (starter: "workflow") also takes workflow (workflow_type, version), owns (classes, relations, properties) and batch (1 to 64). refresh: ask the workflow to run now, by starter and name (returns at once). pause and resume: stop a workflow from reading and sending, or let it catch up. Every workflow pauses but search.embed and ontology.fit, which answer workflow_pause_unsupported. Without starter they name a workflow with triggers. define: create or change a workflow with triggers of your worker (name, workflow, one trigger, owns, batch, shards, route_by, window, trigger). The trigger is class and fields (a watch), media_types (a content trigger on uploads), schedule ({cron, timezone} or {every, offset}, and jitter) or stream ({max_wait}: the events a client sends, in batches). run: run one now, by name. activate: make a kept revision the one in use, by name and revision, with expected_revision (the revision you read as bound) so a change by someone else since fails with workflow_revision_conflict. An activation changes what the workflow does from then on and deletes no data, so it is not a destructive write and asks for no confirmation. Show the user what changes first. tests_create: test a values.extract workflow on chosen inputs before it writes. Name a workflow of the graph (name), or pass install, the use request of a workflow you have not created. Add iris (at most 20) or sample (1 to 20, default 6). tests_answer: the person’s answer to one value (id, iri, property, answer); answer: null removes it. tests_cancel: stop a test by id. A test writes nothing to the graph.
lbb_workflows_delete Deletes the triggered workflow of starter named name, or without starter the workflow with triggers named name, once the person approves; what it wrote stays.
lbb_reviews list: one page of review items, the latest update first, as the server sends it (object, data, has_more, next_cursor). Filters: status, kind (one or a list), scope, origin_id, workflow, run, key, eval_id (the items Evals filed for one record: a calibration session, a model call’s check, a golden); page with limit and cursor. get: one item by id. patterns: the live items grouped by the decision they ask (status, origin_id, type, eval_id).
lbb_reviews_manage create: file an item of kind value, choice, grade or answer (kind, title, question, and subject, evidence, rationale, key, scope, facts, origin). answer: answer one item (id, answer, revision). answer_batch: one answer for up to 500 ids. comment (id, text, author). withdraw (id, reason).
lbb_files list: 50 files a call, newest upload first, with their SHA-256, name, size, Document IRI and parse status; when more follow, next names the after of the next call. parse_status keeps the files of one status: parsed, waiting, unsupported or failed. name keeps the files whose name holds a text, in any case. A filtered list reads the published generation and returns its commit (as_of_commit_seq). A new upload can be missing from it until publication. Its next repeats the filters. pages: the parsed pages of one file, by sha256 or by its exact name (a file of any age), from from to to (0-based, to excluded, one page by default). Each line comes as <line id> [x0,y0,x1,y1] <text>, the box normalized to the page. contains keeps the lines that hold a text, max_lines caps the lines (200 by default, at most 1,000), and next names the from and line_offset of the next call.
lbb_evals summary, traces, trace, label, judge, goldens, golden, accept, delete, run, results, settings. Model checks: checks_summary, checks, review_check. Datasets: datasets, datasets_get, datasets_export, datasets_create, datasets_items.
lbb_evals_delete Deletes the dataset id with its items, once the person approves. A later datasets_get or datasets_export of it fails with 404, and the next save of its workflow test makes a new dataset.
lbb_model_choice options: per use (ask, route, rerank, fit, label) the model it runs now, LBB’s model, a switch, its checked calls and the models a trial can test. trials (job, limit): the trials with their reports. trial (trial_id): one trial with its compared calls. trial_call (trial_id, call_id): both answers and the ground truth. switches: the switched uses. choices (workflow, use, cursor, limit): the model choices of the workflow steps (extract, decide, embed): the environment’s rows, and with workflow the row in force for each step of that workflow; one page in data, with has_more and next_cursor.
lbb_model_choice_manage start: test a candidate (provider, model, effort) on the uses in jobs, with an optional target and days; it changes no model. stop (trial_id). switch (trial_id): the use runs the candidate from the next call on; only a trial that qualifies and compared with the model in use. revert (job): back to LBB’s model. Agents show the report and switch only what the user confirmed. put_choice (use, provider, model, effort, workflow): the step runs that model from its next call, for the environment or one workflow. delete_choice (use, workflow): the step runs the next level again; it answers {id, object, deleted: true}.
lbb_models shadow_eval (with a body), suggest_dataset, extractor_dataset (with limit, split_seq). activity: one month of the stack’s managed-model use, by feature, day and graph (month as yyyy-mm; the current month by default).

With lbb_evals, an agent records what the user confirmed about an answer:

  1. Run a query or search with request, the user’s words. The result names a trace_id.
  2. For a question, label the whole answer with action: "label", trace_id and valid: true when the query is right for the question. With valid: false, sparql gives the right query.
  3. For the results, read their ids with action: "trace" and label them with item and valid, or items. A search by meaning needs these labels: no query says which hits are right.

golden saves a query and treats its current results as relevant. run checks every golden at the current commit: a question is asked again through Ask (POST /v1/query/ask, its answer loop), and a saved query or search runs again. The verdict of a question shows the loop’s final query and its answer with the records it cites. judge lets the platform’s judge model label unlabeled results.

lbb_evals also reads the model checks. A judge model checks a sample of the model calls LBB makes for the graph:

  • checks_summary sums a month per job and model, with the judge’s agreement with people. month is yyyy-mm; the default is the current month.
  • checks lists the month’s checks, newest first. Each has the judge’s verdict, score and reason, and the ground truth. job, verdict and reviewed filter the list, and after reads the next page.
  • review_check records a review of one check by its call_id. agree: true keeps the judge’s verdict. agree: false with verdict corrects it, with an optional score, reference and note.

A review becomes the call’s ground truth. Show the check to the user first, and review only what the user confirmed:

lbb_evals action=checks verdict=wrong reviewed=false
lbb_evals action=review_check call_id=<call> agree=false verdict=right note="The user confirmed the order."

A review item is a question for a person. Workflows, integrations, agents and people file them, and the console’s Review inbox shows them. Each kind takes its own decisions:

Kind Asks Decisions
change accept an ontology change, identities or facts accept, reject, cant_tell
value confirm one value of a property accept, correct (with value), reject, cant_tell
choice pick one of question.options choose (with option), cant_tell
grade grade question.output on the scale question.options choose (with option), cant_tell
answer answer question.prompt in words accept (the text in note), cant_tell

An accepted or corrected value item commits the value to the graph. Show the item to the user, and answer only what the user decided:

lbb_reviews action=list status=open kind=value
lbb_reviews action=get id=rev_…
lbb_reviews_manage action=answer id=rev_… answer={"decision": "correct", "value": 45} revision=3

An answer that accepts or corrects a value item, or accepts a change item, commits facts or applies an ontology change. It asks the person to approve first. See Approve destructive writes.

An item has one answer. The same answer again returns the item. Another answer fails with 409 review_answered. revision is the revision the person saw; a later one fails with 409 review_stale.

A list page holds at most the items the detail level shows: 5 (compact), 20 (standard) or 100 (full). The page then comes back whole, and next_cursor continues after the last item you read. Each write sends an idempotency key. A retry after a network failure or a 5xx sends the same key, so the write happens once. Ask a person shows how workflows file items and use the answers.

A workflow test runs a values.extract workflow on a few inputs and writes nothing to the graph. The answers to its values become a dataset of examples:

lbb_workflows_manage action=tests_create name=sinks sample=6
lbb_workflows action=tests_get id=wtest_…
lbb_workflows_manage action=tests_answer id=wtest_… iri=<input> property=width_cm answer={"decision": "correct", "value": 45}
lbb_evals action=datasets_create name=sinks source={"kind": "test", "id": "wtest_…"}
lbb_evals action=datasets_export id=ds_…

datasets_create also takes the examples themselves: name, items, install (each property’s value type, the units, the context) and about. Each item has a name, its text, the values a correct run writes and the ones it must leave_out. datasets_items adds items to a dataset; an item with the name of one it holds replaces it. lbb_evals_delete deletes a dataset by id, once the person approves. A dataset ends with its graph.

Storage inspection and other operator actions are not in the tool set. Publication and cleanup run automatically.

The package exports three functions:

Export What it does
createMcpHttpServer(options) Returns a Node http.Server that serves the tools over streamable HTTP. Each caller sends a stack API key as a bearer token.
buildLbbServer(client, options?) Returns an MCP server bound to one LbbClient.
registerLbbTools(server, client, options?) Adds the tools to an MCP server you already have.

This process serves the tools on http://127.0.0.1:8080/mcp:

import { createMcpHttpServer } from "@littlebigbrain/mcp";
const server = createMcpHttpServer({
baseUrl: process.env.LBB_URL!,
mcpPath: "/mcp",
allowedHosts: ["127.0.0.1", "localhost"],
onError: (error) => console.error("MCP request failed", error),
});
server.listen(8080, "127.0.0.1");

A client sends POST /mcp with a JSON body and Authorization: Bearer <stack API key>. Add ?graph= to change the default graph. GET /healthz needs no key.

Option Default Meaning
baseUrl Required The complete stack endpoint.
mcpPath /mcp The path that serves MCP.
maxBodyBytes 1,048,576 Larger bodies get 413.
requestTimeoutMs 30,000 The request timeout, in milliseconds.
allowedHosts None Host names that the Host header must match. A present Origin must match too. Others get 403. Set it when the server is reachable from a browser.
allowedOrigins None Extra exact Origin values to accept.
onError None Called with each failed request’s error.
clientFactory new LbbClient(…) Builds the LbbClient for each request from baseUrl, apiKey and graph.
Response Cause
401 No bearer token.
415 The Content-Type is not application/json.
400 The body is not valid JSON.
413 The body is larger than maxBodyBytes.
403 The Host or Origin is not allowed.
404 Another path, or a method other than POST on mcpPath.
500 An unexpected error. The body is internal server error, and onError receives the details.

This server has no OAuth. Put your own authentication in front of it before you expose it to a network. The hosted OAuth endpoint is a separate service.

To add the tools to your own MCP server, next to your own tools:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { LbbClient } from "@littlebigbrain/client";
import { registerLbbTools } from "@littlebigbrain/mcp";
const lbb = new LbbClient({
baseUrl: process.env.LBB_URL!,
apiKey: process.env.LBB_API_KEY!,
graph: "support",
});
const server = new McpServer({ name: "support-agent", version: "1.0.0" });
registerLbbTools(server, lbb);
await server.connect(new StdioServerTransport());

buildLbbServer(lbb) does the same with a new server named lbb. Both accept these options:

Option Default Meaning
queryTextFormat compact compact or pretty: the format of query results.
destructiveWithoutApproval allow when LBB_MCP_DESTRUCTIVE=allow, else refuse What a call that deletes or removes data does when the client cannot ask the person. See Approve destructive writes.
log A JSON line on standard error Receives one event per tool call.