# Zustand agent ops Digitalisierungsplanung.de has one process truth: `kind: state-blueprint-definition`, `schemaVersion: 2`. The canvas is a projection. Do not create or maintain a second graph. The account service keeps the synchronized control graph revisioned; a browser copy is a working copy, not a second authority. Writes require the expected server revision and stale writes fail with `REVISION_CONFLICT`. ## Free MCP for AI agents No account or API key is required to create a workflow: ``` POST https://accounts.digitalisierungsplanung.de/mcp/free ``` Discovery: ``` https://digitalisierungsplanung.de/.well-known/mcp.json https://accounts.digitalisierungsplanung.de/.well-known/mcp.json ``` Flow: `initialize` → `zustand_contract` → `zustand_snapshot` → one or more atomic `zustand_apply` batches → verify with `zustand_snapshot` → `zustand_share`. The free endpoint uses the same canonical state-machine ops as the authenticated product, but each MCP session is isolated and temporary. It cannot read accounts, organizations, projects, browser recordings, or private data. Sessions expire after inactivity and are bounded by server-side limits. ### What to return to the user After `zustand_share`, return **`result.url`** to the user. It is the normal handoff link: ``` https://accounts.digitalisierungsplanung.de/mcp/free/view/ ``` This URL is a **public read-only preview**. Opening it requires no account and allows no changes. `zustand_share` also returns **`result.editorUrl`**: ``` https://accounts.digitalisierungsplanung.de/state.html?share= ``` Use `editorUrl` only when the user wants to adopt the workflow. The user signs in, sees the generated workflow in the real editor, and explicitly chooses **In meine Projekte übernehmen**. Only that action creates a persistent project in the user's account. Share links expire automatically. In short: **build → verify → share → give `url` to the user**. Do not send users to the editor first. ## Free tools - zustand_contract — live catalogs: ops, decisions, triggers, patch allowlists. - zustand_snapshot — current isolated control model (name, initial, states, transitions) with all canonical control fields. No camera. - zustand_apply — atomic semantic ops. Requires `expectedRevision` (from snapshot) and `ops`. Returns `{ ok, model, changed }` or `{ ok:false, code, opIndex, message }`. Stale writes fail with `REVISION_CONFLICT`; snapshot again, never retry blindly. - zustand_share — create the temporary public read-only handoff link plus the optional authenticated editor/import link. Browser: ``` window.ZustandCanvas.snapshot() window.ZustandCanvas.contract() window.ZustandCanvas.apply(ops) ``` HTTP (eingeloggter Account, API-Schlüssel auf /login.html): ``` Authorization: Bearer dpk_… GET https://accounts.digitalisierungsplanung.de/v1/me GET https://accounts.digitalisierungsplanung.de/v1/zustand/snapshot GET https://accounts.digitalisierungsplanung.de/v1/zustand/contract POST https://accounts.digitalisierungsplanung.de/v1/zustand/apply ``` Authenticated MCP (ChatGPT / Grok / Claude Connector): ``` POST https://accounts.digitalisierungsplanung.de/mcp ``` ChatGPT/Remote-MCP verwendet OAuth 2.1 Authorization Code + PKCE S256, DCR und die Scopes `zustand:read`, `zustand:write`, optional `offline_access`. Discovery: `/.well-known/oauth-protected-resource` und `/.well-known/oauth-authorization-server`. Fehlende Authentifizierung liefert `WWW-Authenticate` mit Resource-Metadata. Die Tools spiegeln ihre OAuth-`securitySchemes`. Direkte Clients dürfen `Authorization: Bearer dpk_…` verwenden. `X-Api-Key` funktioniert nicht. Methoden: `initialize`, `ping`, `tools/list`, `tools/call`; Notifications ohne `id` → HTTP `202`. Protokoll: `2025-03-26` (Default), `2025-11-25`. Reihenfolge: `zustand_contract` → `zustand_snapshot` → `zustand_apply`. MCP liegt auf **accounts** (Control-Graph), nicht auf markt/aktuell/realtime. Beispiele: `$DPK_KEY`, niemals echte Schlüssel. OAuth ist nur eine Auth-Schicht vor demselben revisionierten Steuergraphen. `snapshot`, `contract` und `apply` bleiben unverändert. Recorder-/Komponenten-Inhalte außerhalb der Control-Projektion werden nicht durch partielle API-Modelle überschrieben; sie sind aber kein zweiter Steuergraph. Vollständige menschliche Anleitung: `README.md` und `docs/free-mcp-curl.md`. ## V1 ops setName, setInitial, addState, patchState, removeState, addTransition, patchTransition, removeTransition, layout No reset, replaceModel, or setCamera. Every add* op requires an id. addState may omit x/y. Grid 24px, lane 336px from origin 120,168. Canonical transition control fields are: `id`, `from`, `to`, `label`, `triggerType`, optional `triggerEvent`, optional `timerMs`, `condition`, `set`, optional `decision`. `triggerEvent` and `timerMs` must survive snapshot/apply/sync roundtrips. For `patchTransition`, `null` clears either optional field; persisted transitions do not use `null` as a value. apply prüft den Agent-Vertrag (Ops, IDs, decision/trigger, Graph) am bestehenden Model. Erst danach schreibt es in-place. Schlägt der Editor-Commit danach fehl, stellt saveModel den letzten gültigen Stand wieder her und apply liefert COMMIT_REJECTED. Removing a state whose transitions still exist is rejected. ## Hard rules - decision is `routine | human | stop` or omitted. If omitted or unknown, the transition is unclassified and must not execute automatically. Set decision explicitly before execution. - triggerType is `button | change | event | api | timer | auto`. Default for new edges is button. Never send click. - triggerEvent, when present, is a string. - timerMs, when present, is a finite number >= 0. - Patch only listed fields. Unspecified canonical fields stay. - Do not invent actions, workflow layers, approval objects, or a second FSM. ## Example ```json [ { "op": "setName", "name": "Dokumentprozess" }, { "op": "patchState", "id": "start", "title": "Dokument eingegangen" }, { "op": "addState", "id": "normalized", "title": "Daten aufbereitet" }, { "op": "addState", "id": "done", "title": "Fertig" }, { "op": "addTransition", "id": "start_normalized", "from": "start", "to": "normalized", "label": "Aufbereiten", "triggerType": "event", "triggerEvent": "document.received" }, { "op": "addTransition", "id": "normalized_done", "from": "normalized", "to": "done", "label": "Weiter", "decision": "human" }, { "op": "layout", "mode": "row" } ] ```