Self-contained reference for building on arest. FORML 2 readings compile to a database schema, constraint rules, state machines, and a REST API with HATEOAS, all from one β-reducer over Backus's FFP algebra.
The MCP (Model Context Protocol) server is how agents interact with arest. The v1.0 verb set has six tiers: primitive (the algebra requires them), entity sugar (ergonomic shortcuts), introspection (read-only metadata), persistence (capture and restore tenant state), evolution (governed self-modification), and LLM bridge (natural-language to formal-fact translation via client sampling).
{
"mcpServers": {
"arest": {
"command": "npx",
"args": ["-y", "arest", "mcp"],
"env": {
"AREST_MODE": "local",
"AREST_READINGS_DIR": "/absolute/path/to/readings"
}
}
}
}
{
"mcpServers": {
"arest": {
"command": "npx",
"args": ["-y", "arest", "mcp"],
"env": {
"AREST_MODE": "remote",
"AREST_URL": "https://your-domain.com",
"AREST_API_KEY": "secret"
}
}
}
}
The algebra requires these four. Every entity-level action is expressible as a sequence of them.
assertPush a single fact into P. Triggers resolve, derive, validate, and SM fold as usual.
assert({ fact_type: "Order_was_placed_by_Customer", bindings: { Order: "ord-1", Customer: "acme" } })
retractRemove a specific fact from P. This is distinct from deletion, which transitions the entity to a terminal status (see delete).
retract({ fact_type: "Order_was_placed_by_Customer", bindings: { Order: "ord-1", Customer: "acme" } })
projectCodd’s θ₁ projection. Restrict P to a fact type and optional filter.
project({ fact_type: "Order", filter: { status: "Placed" } })
compileIngest new readings. This is immediate self-modification: the new definitions merge into DEFS, and every subsequent call evaluates them. See self-modification for details.
compile({ readings: "Order(.Order Id) is an entity type.\n..." })
Ergonomic shortcuts over the primitives. Most agents will use these.
getGet an entity by ID or list all entities of a noun type. Returns the entity with HATEOAS links and navigation.
get({ id: "ord-1", noun: "Order" })
// or
get({ noun: "Order" }) // lists all
If the noun is federated, get reaches the external system transparently.
queryQuery facts across the population, with filters.
query({ fact_type: "Order_was_placed_by_Customer", filter: { Customer: "acme" } })
applyApply the full SYSTEM function with an input. For advanced users.
apply({ key: "create:Order", input: "<<Order Id, ord-1>, <Customer, acme>>" })
createCreate an entity with field facts.
create({ noun: "Order", id: "ord-1", fields: { "Order Id": "ord-1", Customer: "acme" } })
Runs the full pipeline: resolve → derive → validate → emit. Returns the entity with HATEOAS links.
readSame as get but name-aligned with CRUD expectations. Returns the full RMAP row.
updateAssert new field facts. Old facts are superseded by new assertions via derivation rules, so there is no implicit delete.
transitionAdvance a state machine. Takes the entity ID and the event name; the SM fold checks whether the transition is legal from the current status.
transition({ noun: "Order", id: "ord-1", event: "place" })
deleteTransition the entity to a terminal status. No hard delete by default (see Corollary: Deletion). For fact-level removal, use retract.
Read-only calls that describe the running system.
explainShow the derivation chain for a fact. Returns which rules fired, which antecedents they consumed, and whether each antecedent was asserted or derived.
explain({ fact_type: "User_accesses_Domain", bindings: { User: "alice", Domain: "core" } })
actionsList the transitions available from the current status of an entity. Equivalent to _links in the HATEOAS response.
actions({ noun: "Order", id: "ord-1" })
schemaReturn the schema of a noun or fact type: roles, reference scheme, related fact types, constraints.
schema({ noun: "Order" })
schema({ fact_type: "Order_was_placed_by_Customer" })
verifyRun constraint evaluation against proposed facts without asserting. Useful for dry-run validation.
verify({ fact_type: "Order_was_placed_by_Customer", bindings: { Order: "ord-1", Customer: "acme" } })
Capture the current tenant state and restore it later. Useful before a risky migration, before applying a propose that turned out badly, or between tutorial exercises where you want a clean slate.
Snapshots are per-tenant (the MCP handle): one client’s captures are invisible to another. They live in memory alongside the cells map. Each snapshot is cheap because cell contents share Arc storage — capture is one map insert plus an Arc ref bump per cell, not a deep clone.
snapshotCapture the full D — every cell, including P and DEFS — under an ID you can pass back to rollback. Pass an empty input to let the engine auto-assign an ID (snap-N), or pass a label and the engine uses it verbatim. Re-using a label overwrites the prior capture, so "before-migration" is a stable anchor across retries.
snapshot({ label: "" }) // → "snap-0"
snapshot({ label: "before-migration" }) // → "before-migration"
rollbackRestore a previously captured snapshot. The cells map is rebuilt from the snapshot contents; subsequent calls see the restored state. Rollback does not drain the snapshot — you can roll back to the same anchor many times. Passing an unknown ID returns ⊥ (bottom) and leaves state untouched.
rollback({ id: "before-migration" }) // → "before-migration"
rollback({ id: "never-captured" }) // → ⊥
snapshotsList every capture held by this tenant. Sorted alphabetically, returned as an FFP sequence so it parses with the same tooling as any other projection result.
snapshots({}) // → <a, b, c>
Governed self-modification.
proposeCreate a Domain Change entity with proposed readings, nouns, constraints, or verbs. Enters the review workflow at status Proposed.
propose({
rationale: "Add loyalty tier tracking",
target_domain: "orders",
readings: ["Customer has Loyalty Tier.\n Each Customer has exactly one Loyalty Tier."],
nouns: ["Loyalty Tier"]
})
Returns the change ID and next actions: transition to review, approve, apply. See self-modification.
compile (revisited)Immediate self-modification path. Bypasses the review workflow. Use in trusted contexts (migrations, bootstrap) where proposal review is not needed.
Three verbs that use MCP client sampling (via server.server.createMessage) to translate between natural language and formal facts. The engine composes the prompt with schema context; the LLM does the translation; the engine executes the formal operation.
askNatural-language question to executed projection.
ask({ question: "Which orders did acme place in the last week?", noun: "Order" })
synthesizeFacts to prose. Runs the full pipeline (including derive-to-LFP) so derived facts are included, then asks the LLM to verbalize.
synthesize({ noun: "Order", id: "ord-1" })
The engine guarantees content correctness; the LLM shapes the prose.
validateText to constraint check. Useful for document review and content moderation.
validate({ text: "Customer Bob placed 3 orders in 5 minutes.", constraint: "rate-limit-orders" })
verify on each extracted fact.All three verbs degrade gracefully when the client does not support sampling. In that case they return a prompt that the caller can execute itself.
OpenAI’s ChatGPT apps, deep research, and company knowledge modes require two specifically-named tools, search and fetch, with a fixed JSON-in-text-content shape documented at https://developers.openai.com/apps-sdk/build/mcp-server. The remote AREST worker registers both as thin adapters over the entity model. Self-host the worker with wrangler deploy and point ChatGPT custom connectors at https://<your-worker>/mcp or the /sse alias.
searchScans every noun’s entity list, filters by substring match across all field values, and returns at most fifty matches.
search({ query: "acme" })
// → { results: [{ id: "Order:o-1", title: "Order o-1", url: "/api/entities/Order/o-1" }, ...] }
The id round-trips into fetch as "Noun:entityId", so the server stays stateless between calls.
fetchResolves a "Noun:entityId" id, reads the entity through get:{Noun}, and returns the OpenAI-compatible document shape including the entity’s current SM status as metadata.
fetch({ id: "Order:o-1" })
// → { id, title, text: <entity JSON>, url, metadata: { noun, entityId, status } }
Both tools are registered alongside the rest of the AREST verbs. ChatGPT in deep-research mode ignores everything else; ChatGPT-as-app sees the whole surface.
Runtime function registration (adding new Platform names to the engine’s dispatch table) is not an MCP verb. Runtime functions are registered server-side at build time. If you need a new named operation, register it as a Platform function in the engine and redeploy; do not expose arbitrary code execution to agents.
The final chapter, Self-modification, explains how the system can evolve itself without losing the theorems. It covers compile for immediate changes and propose for reviewed ones.