Skip to main content

Mount Burr state-machine Applications as MCP servers.

Project description

Theodosia

PyPI tests License Docs Built on Apache Burr Built on FastMCP

Theodosia mounts a Burr Application as an MCP server. Every Burr action is reachable through a single step(action, inputs) tool; the server checks reachability against the graph before each action runs, refuses out-of-order calls with the legal next moves, and records every attempt.

A coffee-order FSM driven over MCP, with a refusal and recovery

Install

Python 3.11, 3.12, or 3.13 (Burr does not yet support 3.14). On a fresh Python 3.14 install you will see "no version that satisfies the requirement theodosia"; create a 3.11–3.13 venv first.

uv venv --python 3.13       # or: python3.13 -m venv .venv
uv pip install theodosia    # or: pip install theodosia

Optional extras: theodosia[observability], theodosia[ui], theodosia[claude], theodosia[mellea], theodosia[all].

On a slim Docker image (python:3.13-slim, Alpine) the install pulls a psutil build that needs gcc and python3-dev. Either use the full python:3.13 image, or apt-get install -y gcc python3-dev before pip install.

Try it without an API key

theodosia primer

Walks the coffee-order FSM through Theodosia's step tool in-process and prints the timeline with state diffs and one structured refusal. No LLM, no network, same output every run.

Quickstart

from burr.core import ApplicationBuilder, State, action
from burr.core.action import Condition
from theodosia import mount


@action(reads=[], writes=["item", "stage"])
def take_order(state: State, item: str) -> State:
    return state.update(item=item, stage="ordered")


@action(reads=["stage"], writes=["stage"])
def pay(state: State, amount: float) -> State:
    return state.update(stage="paid")


def build_application():
    is_ordered = Condition.expr("stage == 'ordered'")
    return (
        ApplicationBuilder()
        .with_actions(take_order=take_order, pay=pay)
        .with_transitions(("take_order", "pay", is_ordered))
        .with_state(item=None, stage="empty")
        .with_entrypoint("take_order")
        .build()
    )


if __name__ == "__main__":
    mount(build_application, name="coffee").run()

Save as coffee.py and run python coffee.py to serve over stdio. Pass a factory (a callable returning a built Application) so each MCP session gets its own isolated state. Passing an already-built Application works too but shares state across sessions.

Exercise the server in-process with FastMCP's Client:

import asyncio
from fastmcp import Client
from coffee import build_application

async def main():
    async with Client(mount(build_application, name="coffee")) as client:
        r = await client.call_tool("step", {"action": "pay", "inputs": {"amount": 5.0}})
        print(r.structured_content)
        r = await client.call_tool("step", {"action": "take_order", "inputs": {"item": "mocha"}})
        r = await client.call_tool("step", {"action": "pay", "inputs": {"amount": 5.0}})
        print(r.structured_content)

asyncio.run(main())

A client that calls pay before take_order gets a structured refusal it can recover from:

{ "error": "invalid_transition", "valid_next_actions": ["take_order"] }

Primitives

  • mount(application, *, hooks=[...], middleware=[...], upstream=..., personas=...) wraps a Burr Application (or factory) as a FastMCP server. Returns the server; call .run() or pass to FastMCP's in-memory Client. Optional kwargs forward Burr LifecycleAdapter, FastMCP Middleware, upstream MCP clients, and PERSONA.md identity layers.
  • The four MCP tools every mounted server exposes: step(action, inputs), reset_session, fork_at(sequence_id), fork_from_past(app_id, sequence_id). The action namespace lives in step's argument schema; FSM complexity changes the schema, not the tool count. FastMCP's ResourcesAsTools transform adds list_resources and read_resource for clients that lack native resources/read.
  • Structured refusals from step: invalid_transition, unknown_action, validation_failed, action_timeout, action_error. Every refusal carries valid_next_actions. fork_at / fork_from_past return their own error codes on the same wire shape.
  • theodosia:// resources for inspection: graph, state, next, history, subruns, trace, session.
  • upstream lets a Burr action body call tools on other MCP servers through call_upstream(server, tool, args). The agent driving Theodosia never sees those servers; it only sees step.

Full reference (Persona, Assembly, hooks, middleware, tracker, drive_claude) lives in the docs.

Scope

Theodosia does not include an agent, a model, or a workflow engine. It mounts an existing Burr Application and gates an MCP client's access to it. The rails are only as tight as the graph you author.

Command line

theodosia serve module:app                       # mount as MCP server (stdio, default)
theodosia serve module:app --transport http --port 8000   # serve over HTTP
theodosia render module:app                      # draw the state machine in the terminal
theodosia doctor module:app                      # statically validate the graph; exits nonzero for CI
theodosia sessions show <id>                     # full timeline: per-step state diff + timing
theodosia sessions diff <a> <b>                  # cross-session: action-path divergence + final-state diff
theodosia watch                                  # live-tail a running session
theodosia logs --refusals                        # only the steps that were refused
theodosia status                                 # tracker storage + recent activity snapshot
theodosia verify                                 # recompute the ledger hash chain
theodosia primer                                 # offline first-touch, no API key
theodosia ui                                     # open the Burr UI

A downstream package can ship its own command (my-fsm serve, my-fsm doctor, ...) with build_cli.

Documentation

Full docs at msradam.github.io/theodosia.

Page Covers
Introduction What Theodosia is and the primitives you reach for
Build your own agent End-to-end: write a Burr graph, serve it, drive it with an MCP client
Authoring a graph The Burr building blocks: @action, Condition, with_transitions
Architecture mount(), the four-tool surface, the action-selection bridge
Refusals The five refusal shapes and how the agent recovers
Sessions Per-session isolation, fork_at, fork_from_past, partition keys
Security model The trust boundary, what the ledger does and does not prove
Observability The theodosia:// resources, CLI, Burr UI, OpenTelemetry
Personas PERSONA.md identity layer mounted as MCP prompts
Upstream Calling other MCP servers from action bodies
Compatibility What works through mount() (typed state, persistence, hooks, parallelism, telemetry)
CLI serve / doctor / render / sessions / watch / logs / status / report / primer, and build_cli
Deployment recipes Copy-pasteable configs: Claude Code, Cursor, mcphost, fast-agent, HTTP, SSE, Lambda, Kubernetes
Case study Kimi K2.6 on Grafana o11y-bench: free-ranging vs gated
Research foundation The published evidence the design rests on

Examples and tests

examples/ ships self-contained FSMs covering pure-FSM, typed state, hooks, persistence, real shellouts, LLM-in-the-graph, SKILL-to-FSM, upstream, and multi-graph. Each runs with uv run python examples/<file>.py. The test suite runs with uv run pytest.

Related projects

  • theodosia-rehearsal: drop-in demos that exercise the framework end to end.
  • Philip: lifts declarative artifacts (Ansible YAML, Mermaid stateDiagram-v2, Excalidraw) into Burr Application instances that theodosia.mount() serves directly.

Acknowledgements

Theodosia is glue between two libraries that do the hard parts: Apache Burr provides the state-machine Application, the transition graph, and the tracking UI; FastMCP provides the MCP server, the transforms, and the client behind upstream. The SKILL demos under examples/skills/ are reproduced verbatim from Anthropic and Trail of Bits with attribution.

Theodosia is an independent project, not affiliated with or endorsed by the Apache Software Foundation, DAGWorks, the Apache Burr project, or FastMCP.

License

Apache 2.0. Theodosia is independent open-source work by Adam Munawar Rahman and does not represent the views of IBM Corporation or any other employer. See NOTICE.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

theodosia-0.5.0.tar.gz (5.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

theodosia-0.5.0-py3-none-any.whl (141.0 kB view details)

Uploaded Python 3

File details

Details for the file theodosia-0.5.0.tar.gz.

File metadata

  • Download URL: theodosia-0.5.0.tar.gz
  • Upload date:
  • Size: 5.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for theodosia-0.5.0.tar.gz
Algorithm Hash digest
SHA256 a0d428d3e2900b9aef8236c38865bbbf84b173207da6a0eaf45cf968b555f139
MD5 da2915f6f63935dfeb228ce88763163d
BLAKE2b-256 8f23613d97e61b2484a4d709ae45ab29f499c62392aa43054968edbf73cfa37d

See more details on using hashes here.

Provenance

The following attestation bundles were made for theodosia-0.5.0.tar.gz:

Publisher: release.yml on msradam/theodosia

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file theodosia-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: theodosia-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 141.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for theodosia-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ee194ebf73f24443bf89a135de4fe25513ca0a8f8b0672e69bbbe5e5aa7f5fa5
MD5 58f355ff0d1f0da7bb507c17166d02ff
BLAKE2b-256 1cb4807ad11ac636db7c82c1999985a388563914aa421e773eeee4cc5d42c394

See more details on using hashes here.

Provenance

The following attestation bundles were made for theodosia-0.5.0-py3-none-any.whl:

Publisher: release.yml on msradam/theodosia

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page