MCP server
webhooks
A knowledge backend other applications can actually call.
Everything the workspace does — retrieval, grounded chat with memory, document reads, proposals, generation, long-running jobs — is reachable over HTTP and over the Model Context Protocol. Access control, the retrieval barriers and the credit meter apply identically, because there is no path around them.
The MCP server holds zero references to the application.
It talks to Liminalis over HTTP with an API key, exactly like any third party would. That isn't a guideline someone might quietly break under deadline pressure — the project simply has no reference to the server assemblies, so the compiler enforces it.
It cannot bypass anything
Project access checks, the GM-only wall, the lens barrier and the credit meter all sit on the HTTP path. A tool server that can only speak HTTP inherits every one of them, permanently, without anyone having to remember to apply them.
It doubles as the API's acceptance test
If the server can only speak HTTP, then every gap in the public API surfaces as “I can't implement this tool” during development — instead of being quietly papered over with a direct internal service call that no external caller could ever make.
No repository, no cache, no vector service, no background work. Any persistence appearing inside the tool server is treated as a design bug rather than an optimisation.
Eleven tools over MCP, and the HTTP API behind them.
| Tool | What it does |
|---|---|
| describe | Self-description: the project's lenses, document types, sections, stages and commands. An agent discovers the domain rather than having it hardcoded. |
| outline | The shape of the project — structure without pulling every document. |
| search | Grounded retrieval over the corpus, through the same stack the workspace uses. |
| read_document | A document, with truncation declared rather than silent. |
| lookup | Resolve a name — title, slug or alias — to the thing it means. |
| propose_document | Create a new document as a reviewable draft. |
| propose_edits | Suggest anchored changes to an existing document. |
| apply_edits | Apply them, given the scope to do so. |
| run_command | Invoke a lens command — the same ones a user types in chat. |
| generate | Run a generation stage from the lens's build plan. |
| job_status | Poll long-running work. |
The HTTP API additionally exposes chat and chat history, job submission and cancellation, and webhook
subscriptions. Streaming chat is deliberately excluded from the tool surface.
An agent gets the memory loop for free
An agent holding one chat id inherits the entire rolling-summary memory system without implementing any part of it — retrieval, decay, summarization, the cached prefix, all of it. Verified with a deliberately anaphoric second turn that could only be answered by remembering the first, with cost reported per call.
New domains need no client changes
Because one lens descriptor feeds the workspace, the agent API's describe, and the tool registration, a
lens authored at runtime shows up to agents automatically. The domain is data on every consumer of it.
Agents propose. People decide.
Propose, don't write
Agent output lands in the draft queue marked as generated provenance. Direct write requires an explicit scope that is off by default.
Scopes only narrow
Effective permission is the minimum of the key's scope and the key owner's role on the project. A key can never grant more than the person who created it has.
Barriers before assembly
Access filtering happens before context is built. A prompt-injected agent cannot extract material that never entered its context in the first place.
No silent clipping
Matched spans are expanded in place; truncation is declared both in the text and as a structured field, with a token estimate. Retrieval that doesn't lie to you about what it showed you.
Webhooks with the boring parts done
Per-key subscriptions, an echo handshake, HMAC signatures the listener independently recomputes, per-key rate limiting, and an SSRF guard whose rejection classes — including the cloud metadata address — are covered by tests.
Thin payloads
A webhook says that something happened and what to fetch. It never carries a result body, so an event delivery is not an exfiltration channel.
Liminalis already runs headless, behind an application that isn't the workspace.
The Familiar is a consumer product with its own WebGL client, its own interface and its own audience — a small creature that keeps you company while you write, built around journalling and collaborative fiction. It is not a Liminalis feature and it does not look remotely like a document workspace. Underneath, it is Liminalis: the document engine, the knowledge layer and retrieval are the backend, and nothing else is.
Structurally, one of its worlds is one Liminalis project. The corpus, chunking, extraction, the association graph, recall and generation all arrive with it. What the product added was domain vocabulary: two new document types for journal entries and vignettes, and a generation stage plan for writing them that delegates to the same runner the Novel lens uses for chapter outlines. That is the whole of the integration at the document layer.
Because it registers as a lens, the Liminalis workspace can already render its documents, sections and type pickers with no frontend work at all — the SPA builds those from the served lens descriptor. That view is used purely as an inspection and debugging surface onto a product whose real interface is something else entirely. It is the clearest demonstration available that the lens is a real abstraction rather than a naming convention.
| Layer | What the second product did |
|---|---|
| Corpus & retrieval | Reused whole. A world is a project; documents, embeddings, the knowledge layer and recall came with it. |
| Domain vocabulary | Added: two document types and a generation stage plan, declared on a new lens descriptor. |
| Accounts & access | Reused whole — cookie sessions, Google and AT Protocol sign-in, project access control. |
| Metering | Reused whole. Model spend for a creature is metered by the same ledger that meters a manuscript. |
| Background work | Reused the worker shape, including the out-of-band steering pattern first built for the Pacing Watcher. |
| Inspection UI | Came free, from lens registration alone. |
| Ephemeral state | Deliberately not documents. The creature's short-lived state expires; the corpus is permanent. It got its own tables rather than being forced into a document model that would have had to learn how to forget. |
The Familiar is a first-party client. It calls the ordinary controllers, exactly as the workspace SPA does — not the agent API described above. That was a deliberate choice rather than a shortcut: the agent surface is propose-scoped and draft-mediated, which is the right shape for something reviewing a corpus on your behalf and the wrong shape for a live interactive loop where a creature has to respond now.
So treat it as proof that the platform boundary holds — one engine, two products, one of which never shows a document to a user — rather than as a reference implementation of the agent API. Both claims are worth making; only one of them is this one.
What this is for
- An application that needs grounded answers over a private corpus, without building a retrieval stack.
- An agent that should read and propose changes to a body of documents under review rather than write to it directly.
- A domain tool that wants the knowledge layer — entities, relations, facts, motifs, a reverse index — without deriving one.
- A team wanting one metered, audited place where model spend against their corpus happens.
What isn't there yet
- Search over the API runs at the deterministic assembly level; the higher fan-out thinking levels aren't exposed to it.
- Idempotency exists for jobs, not beyond them.
- Lens authoring over the agent API is deliberately out of scope.
-
Tool descriptions are compile-time, so they can't enumerate a project's lenses — callers are pointed at
describeinstead.