---
title: MCP reference
editUrl: https://github.com/Cratis/Screenplay/edit/main/Documentation/screenplay/mcp/reference.md
description: Every Screenplay MCP server tool, argument, limit and validation policy, plus the embedding API for hosts.
---


This page is the reference for the server's tools. To use the server through an
assistant, start with [the MCP server overview](/screenplay/mcp/), and see
[Create a model](/screenplay/mcp/create/), [Explore a model](/screenplay/mcp/explore/), [View a model](/screenplay/mcp/view/)
and [Edit a model](/screenplay/mcp/edit/) for prompts.

## Key and route admission

Typed source and AST proposals retain read-model key marks, named query/read key parts, production routes and observer filters. Composite read-model keys remain authoring-only (`PLAY0268`, #599). Production route overrides and observer filters select ESM v10. Source inventories include production route references. Declaration details retain the authored lookup parts and filters.

`PLAY0661` repairs command reads only when missing parts have unique same-named compatible inputs. Queries and trigger reads are not repaired. `PLAY0664` removes an identical production route through a typed proposal. Both require canonical formatting consent and explicit Apply. The saved-file attachment-evidence pin contract remains limited to PLAY0166 and PLAY0478. See [Authoring tools](/screenplay/mcp/authoring-tools/).

## Installation and scope

The server is included in the `cratis/screenplay` Docker image and in the
`Cratis.Screenplay.Tool` .NET tool. It runs as `screenplay mcp <root>`, or as `screenplay mcp` alone, which picks the model
folder per workspace from `open-workspace`'s `path`, the client's roots or the working
directory ([choose where the model lives](/screenplay/mcp/install/#choose-where-the-model-lives)):

```bash
docker run -i --rm -v "$PWD/specifications:/model" cratis/screenplay mcp /model
```

or, with the tool installed through `dotnet tool install --global Cratis.Screenplay.Tool`:

```bash
screenplay mcp ./specifications
```

See [installation and client configuration](/screenplay/mcp/install/). One physical root
is one application, whether it has one source file or hundreds of nested files.
Symbolic links are rejected. An empty root can be opened to create its first model.

A root supplied at startup restricts the connection to that model and the same
relative model folder in registered Git worktrees of its repository.
`open-workspace.path` accepts either a worktree checkout directory (resolving
that relative model folder) or its exact model directory. Git's shared directory
and registration back-pointer must agree by physical identity. Unrelated roots,
unregistered pointers and different model folders return `RootChangeRefused`;
missing or malformed metadata also refuses switching. A valid worktree without
the corresponding model directory reports the missing relative folder.
Submodules and `--separate-git-dir` checkouts cannot switch roots because they
lack linked-worktree `commondir` metadata; serve their model through a separate
connection instead. Symlinks remain forbidden. Start a separately authorized
connection to switch applications. Dynamic servers retain
the root selection described above.

Only one workspace is active. Switching roots clears the workspace, cached
identity state and retained proposals; reopening also clears proposals. A proposal
from root A cannot be applied in root B, even if their revisions are identical
(`UnknownProposal`). Reads and writes use the currently opened root, including
its own `.screenplay` identity state and recovery journal, unless `workspaceJson`
is explicitly supplied to import identities into a root without persisted state.
A pending journal blocks only its own root. After an opened worktree is removed,
an explicit path can return to the configured root without restarting. If the
startup root itself is removed, start a new connection: switching is refused
because worktree membership is proven from the startup root's Git metadata. No
named simultaneous workspaces are exposed. See [worktree setup](/screenplay/mcp/install/#work-on-another-branch-in-a-worktree).

For a single client-offered project, default discovery uses the common ancestor
of folders holding `.play` files, then `Source`/`src`, then `<project>/Screenplay`.
Existing `.screenplay/identities.json` or `.screenplay/pending.json` at any
ancestor-or-self directory between the offered project and that discovered folder
keeps the workspace bound to that state directory. Both endpoints are included;
an empty metadata folder or backup artifact alone does not count. Metadata path
checks reject symbolic links and reparse points before checking state presence.
With several state directories on that path, the one nearest the offered project
wins. `open-workspace` and the `workspace-state` status view report
`rootBindingConflict` with `kind: "WorkspaceRootConflict"`, `boundRoot`, ordered
`stateRoots` (nearest the offered root first), `pendingRoots` (state roots holding
`pending.json`, in the same order), and an explanatory `message`. The field is
omitted when there is no conflict. Failed tool responses also carry the conflict,
and their error message names the bound root and competing state roots.
A pending journal at any state root on the discovered path blocks opening, reads
and writes at the bound root, including visualization and a journal created after
opening or proposing. `workspace-state` remains available to inspect the conflict.
For a competing root's recovery, first call `open-workspace` with that root's explicit
`path` (opening still returns `PendingOperation`), inspect `workspace-state` there,
then explicitly call `recover-workspace` with its operation ID. A recovery call at
the outer root cannot recover a nested journal. No identities or recovery journals
are migrated, and no fallback folder is created when existing state selects a root.
Inspect competing workspaces using explicit paths before deciding which to keep.
Explicit paths and roots
fixed at startup, including `.cratis/ai.json` configuration, are unchanged.

Only `apply` and `recover-workspace` mutate files. Keep client approval enabled
for both. Source queries, schemas, proposals and status checks are read-only.
If metadata inspection fails after a verified apply, its response keeps the applied
outcome and session revision, retains the previous root-conflict snapshot, and
reports the inspection failure in `metadataProblem`. Repair that metadata before
continuing; do not retry the completed apply.

## Processing purposes

Use `kind: "Purpose"` to read a declared [processing purpose](/screenplay/purposes/). `declaration-details` summaries expose its `processingPurpose` fields and the direct `purposes` references of modules, features and slices. Reference and dependency inventories include role `purpose`. These are report-only declarations, not executable behavior or legal advice. `diagnostics` can opt into coverage prompts with `checks: "purposes"`.

`processing-record` pages one row per declared purpose, including its declared legal and retention fields, reachable concept categories and marker-declared protection mappings, DPIA prompts and erasure-exception/crypto-shredding findings. Its response includes `notice`, `coverage`, optional controller fields, `sourceRevision` and `rows` (`totalCount`, `offset`, `items`, `nextOffset`, `revision`). It requires error-free source, not successful executable binding. Supply controller information as `controllerName`/`controllerContact`, never through model syntax. Echo `sourceRevision` as `expectedSourceRevision` after the first page. Counts are capped at 200 and row pages are byte-bounded; an oversized individual row is refused rather than truncated. These declarations are not legal advice or proof of runtime protection.

## Generated values and responses

For `kind: "Concept"`, the `declaration-details` summary includes `generation` only when you authored `generate`. It reports:

| Field | Contract |
| --- | --- |
| `strategy` | Authored strategy name. |
| `modifiers` | Only the authored `prefix`, `case`, `size`, `alphabet`, `count` and `encoding` values; implicit defaults are not added. |
| `entropyBits` | Exact random bits for UUID, UUIDv7, ULID, TypeID, NanoID and bytes; null for preview strategies or inputs that do not define finite, non-negative entropy, including empty/single-character alphabets and non-positive sizes/counts. Prefixes and encodings add no entropy. |
| `entropyUnknownReason` | Present only when entropy is unknown. |
| `valuesAtOnePercentCollision` | JSON number estimating the count at 1% birthday collision probability, or null for unknown entropy. Large counts use scientific notation and can exceed a client's floating-point range. |
| `executable` | `status` is `admitted` only when the whole model produced an executable model and no error targets this concept/generation; `refused` when compiler errors target it; otherwise `notAssessed`, with `reason`. `diagnosticCodes` lists only errors at this concept/generation. An empty list alone does not imply admission. |
| `previewArguments` | Authored `pattern`, `checksum`, `range` and `rule`, each as `{ "value": "…", "executable": false }`; omitted when none are set. |

`propose-ast` can replace `ConceptSyntax` with a `generation` member of kind `GenerationSyntax`, or omit/null that member to remove it. `propose-source` accepts the equivalent complete source document. With `CanonicalizeTouchedDocuments`, both print canonical `generate` source; `propose-source` with `PreserveExactSource` retains the supplied text. Both report compiler executable diagnostics independently of authoring acceptance. `generate sequence` reports `PLAY0682`; preview strategies and generated-concept validation rules report `PLAY0268`.

`read-workspace` with `view: "repairs"` offers these revision-bound actions:

| Diagnostic | Title | Eligibility and subject |
| --- | --- | --- |
| `PLAY0683`, `PLAY0684` | Raise generation to at least 64 random bits | NanoID: smallest size reaching 64 bits for its alphabet. Bytes: count 8. Subject is the concept, even when it lives in another document than the diagnosed property. |
| `PLAY0688` | Replace the refuted rule with derived `matches` | Subject is the concept. Replaces only the diagnosed rule, preserving its message and severity. The new rule also validates input and stored values, not only generated fixtures. |
| `PLAY0683` | Add a unique constraint for the generated property | Exactly one production maps the command property to exactly one event property. Subject is the command property; the constraint is added to its slice. Not offered for identifiers. |

Pass the selected action's `diagnosticCode` and `subject` to `propose-repair` with `formatting: "CanonicalizeTouchedDocuments"`. The subjects distinguish the two `PLAY0683` actions without another tool or argument. Both require individual review through `read-proposal`, then explicit `apply`; neither supports pinned attachment evidence. Discovery and proposal never write files. The saved-file VS Code repair picker does not expose these entropy actions.

The generation summary does not serialize proof status. Executable diagnostics report Refuted (`PLAY0688`, error with strategy and witness) and Unproven (`PLAY0689`, warning with reason) rules. The `PLAY0688` derived-`matches` replacement requires individual review and canonical formatting; it does not support pinned evidence or fix-all. Checksum repairs remain unadmitted. Monaco has no independent proof engine or derived-pattern quick fix; it forwards the host's diagnostics.

Reaction `declaration-details` summaries add `runsAs`: the declared `kind`, exact `roles`, source `location`, `readiness` note and `crossBoundaryRoles` (roles referenced only by effective command gates outside the reaction's own module). It covers returned and invoked commands, not imperative pipeline calls inside implementation bodies. Cross-boundary roles are allowed without a diagnostic. Clock and application triggers may declare the identity, but have no Arc realization in Stage yet; the note reports this without a source warning. `syntax-schema` includes the init member `ReactionSyntax.runsAs` and `ReactionIdentitySyntax` (`syntaxKind: "system"`, roles); `propose-ast` may set or remove it under the ordinary review/apply contract. Reaction, slice and model readiness no longer refuse reaction command identity. The executable-model export includes `runsAs: {"kind":"system","roles":[...]}` only when declared, with ordinally sorted roles (possibly empty), selecting released ESM v10. Claim conditions evaluate to unknown for the system identity. `diagnostics` accepts `checks: "privilege"` for the event-producer comparison (`PLAY0652`).

Event `declaration-details` summaries expose `subject: { "source": "eventSource" }` by default, or `{ "source": "property", "property": "<name>" }` for a trailing event-property `subject` mark. Property pages expose `isSubject`; `syntax-schema` describes optional `PropertySyntax.isSubject`, omitted when false. This is report-only lineage metadata (`PLAY0270`), not executable subject propagation or provider output. Rename and inline extraction preserve the mark.

`declaration-details` exposes `isGenerated` on property pages and a command `response` view with typed scalar/block syntax, source property names, declared and inferred field types, and command-scoped `syntaxOnly`/`executionReadiness`. Generated values are not request/form inputs. Specification details and `find-fixtures` distinguish `generatedValues` and `thenReturns` from ordinary `whenCommand` values. These are syntax facts, not evaluated results.

Discover `CommandSyntax.response`, `RecordCommandResponseSyntax.fields`, `ScalarCommandResponseSyntax.source`, `ResponseFieldSyntax` and `PropertyResponseSourceSyntax` with `syntax-schema`. Use the existing `read-ast` handles and typed Add/Replace/Remove operations under `propose-ast`, with `validation: "Executable"` for the admitted subset, or `"Authoring"` for full-language syntax. Add a response to the command's `response` member, replace or remove its node to change or clear it, and add/replace/remove record fields through `fields`. Field types are nullable for inference. Source locations remain server-owned; response fields have no ESM identities.

Workspace and catalog revisions, expected nodes, preview and explicit acceptance still apply. Inspect `read-proposal` before `apply`; discovery and preview never write. Generated values, responses, fixtures and return expectations are admitted as ESM v7. Response-only commands report `syntaxOnly: false` and null `executionReadiness`; a command that also uses operations, handlers, exact numeric mode remains unadmitted. Generated properties on concepts with rules select V20; Refuted rules fail binding, Unproven rules warn, and opaque predicates remain Unsupported during reference execution. Null readiness is not proof that the whole application binds or that reference execution has every generation fixture. Canonical `executable-model` byte pages include `generated`, `response`, `generatedValues` and `thenReturns` when present. Pin `expectedModelRevision` and `expectedAttachmentManifestRevision` on continuation; stale revisions refuse without a page. Form response scopes and an official renderer response type remain downstream work. Inline-event extraction can prove its executable-byte invariant for admitted response-bearing commands; other refusal and comment-preservation rules still apply. Rename with `PreserveTrivia` to retain response comments; canonical rename can refuse a proposal that would drop comments. See the [response contract](/screenplay/commands/#generated-values-and-responses).

## Persona specification callers

`declaration-details` for `kind: "Persona"`, `view: "caller"` returns `caller`, policy `contributions` (kind, value, optional claim type, policy and location), and `refusal`. The caller is always authenticated. Refusal carries a policy, location and reason: `negation`, `nonLiteralClaim`, `roleClaim`, `opaqueImplementation`, `unresolvedPolicy` or `noPolicies`. A required role-URI claim refuses synthesis; an `or` skips alternatives requiring it. See [persona callers](/screenplay/personas/#persona-callers-in-specifications).

`find-fixtures` reports `role: "givenCaller"`, `origin: "persona"`, and the supplying `persona` and `policy` for synthesized caller atoms. `find-references` and rename index `given caller as` as a persona reference. `diagnostics` accepts `checks: "personas"` for opt-in coverage warnings and ambiguity information; it never runs this check implicitly.

## Specification tables

A specification summary includes typed `parameters` and `caseCount`. Its paged `cases` view returns `name`, `effectiveName`, `effectiveAddress`, `location` and `values`. Searching specifications lists an authored table once; resolving a derived address returns the table with `case` set.

`find-fixtures` accepts a table or derived `specification` address and an optional `case` name. Effective values add `table`, `case`, `origin: "case"` and `caseParameter`. Parameter references are available through `find-references`; table, case and parameter renames update their references and derived identities. A table address in `run-specifications` scope selects every row; a derived address selects one. Failure keys use derived addresses and source-bound failure messages name the case.

## Typed specification examples

Use `kind: "Example"` with `search-declarations`, `declaration-details`, `find-references` and `dependencies`. Example details report the authored underlying type and destination, with paged `values` and `generatedValues` views. Specification references point to the example; the example's `exampleType` dependency points to its underlying event, command or read model.

`find-fixtures` expands [typed examples](/screenplay/specifications/#typed-specification-examples) before selecting values. Each assignment reports its effective `target`, `value`, source `location`, and `origin`: `authored`, `example`, or `override`. `example` names the supplying declaration; `overriddenValue` holds the expression value replaced by an override, or null. Generated fixtures and destinations carry the same provenance. Expressions remain syntax, not evaluated results. Example resolution errors refuse the query rather than returning unexpanded values as effective fixtures. Omitted properties are not invented.

## Event source and stream inventories

`read-workspace` offers `event-sources`, `event-streams`, `event-source-details`, `event-stream-details` `command-routes` and the paged `event-source-diagnostics` evidence view. Supply `expectedRevision`; continuation pages also pin `expectedCatalogRevision`. Detail views require the exact `authoringKey` from the matching kind's inventory. Keys include application, kind, full owner path and name; physical handles remain separate. Duplicate sources make every child owner ambiguous, even if one duplicate alone declares that child. Detail requests return typed refusal rather than selecting a survivor. The physical inventory retains partial declarations and route candidates from errorful files without granting write eligibility. `inventoryComplete` and authoring diagnostics disclose incomplete parsed extent; otherwise noncolliding ownership is `incomplete`, not falsely `unique`, and confident details refuse with `IncompleteSource`. Parser/import and whole-assembly diagnostics are retained. Unresolved import placement is reported without an authoritative owner or key.

Detail pages use `detailShape: "compact-header-v1"`. The first `declaration` item is a compact header, not a full `syntax` subtree. Sources include `streamCount`; subsequent `stream` items page children in their physical parent's authored order. Use `fullSyntax` to request full content separately through `read-ast`. `metadata` reports UTF-8 `idBytes` and `descriptionBytes`; values above 4096 bytes are omitted explicitly, not shortened. Follow `originalSource` to `read-document` byte pages for those exact values. Source inventory pages are bounded by both item count and serialized bytes, so `nextOffset` can advance by fewer than `limit` items. An oversized single identity item refuses with an actionable byte-read alternative; reducing the item count cannot shrink that item.

Generic source details, source dependencies and editor navigation use the same source-only physical confidence as the inventories. Unresolved placement candidates are not discarded before checking duplicate parents. Unknown root extent refuses confident selection even when one surviving declaration is readable; nonrouting event and operation resolution keeps its existing rules. `confidenceReasons` explains source inventory ownership, and reference edges expose `sourceConfidenceReasons`.

`declaration-details` adds source `streams` and command `route` views. The route view retains `authoredRoute` and every `ambiguousStreamCandidates` node; it does not claim effective routing. Dependencies include `commandEventSource`, `commandStream`, `specificationEventSource`, `specificationStream` and nominal identifier/key type links. Specification route references point at the qualified `Source.Stream` operand and belong to the specification, so `find-references` on either the source or stream includes its authored event routes. These classification links do not add slice dependency-graph edges or change timeline order. Source references are application-exact; stream references are exactly `Source.Stream`, without suffix guessing.

Sources, streams, scalar/composite command routes and specification routes are admitted by ESM v8. Inventories report construct readiness separately from `executionAvailable`, which requires successful whole-workspace binding. Catalog semantic IDs identify admitted declarations; logical authoring keys and physical handles remain separate. An invalid or partly unadmitted workspace never reports executable availability merely because it has a source declaration. Old syntax JSON omissions retain additive defaults. Use [typed authoring edits](/screenplay/mcp/authoring-tools/#event-source-and-stream-authoring) or `propose-rename` for sources and streams with bound command and specification route repair; automatic routing diagnostic repairs remain unavailable. Strict malformed draft syntax content or merged syntax export refuses with `InvalidSyntaxJson`; a compact `read-ast` query can still expose replacement handles. `export-workspace` preserves exact original bytes, including invalid-but-editable drafts, rather than converting them to typed syntax.

Query `by` keys and supplied filter values obey their concept rules after authorization in every reference ESM version. Omitted optional filters are not validated. A violation is a Validation rejection, not an absent instance. One actionless queried call with one `then error` and no other outcome selects ESM v18; both `when query` and `then query … arguments` spellings use existing `thenQueries`/`thenErrors` members. Source tools preserve the malformed fixture without evaluating its rules during binding. `run-specifications` requires exactly Validation for this shape; Contract, Unauthorized and Unsupported do not pass. Consumers must explicitly admit v18. See [Query validation rejections](/screenplay/specifications/#query-validation-rejections).

## Embedding API

The `Cratis.Screenplay.Mcp` library targets .NET 10 and references the Screenplay
compiler. Hosts can embed the server without launching or installing another
executable. The standalone `screenplay mcp <root>` command remains supported and
delegates to the same server. Cratis CLI/AI-distribution integration is delivered
separately; installing this library alone does not configure an AI client.

The public entry point is
`Cratis.Screenplay.Mcp.ScreenplayMcpServer.Run(string root, TextReader input, TextWriter output)`.
It returns `void` and serves one sequential connection until input reaches EOF.

| Contract | Behavior |
| --- | --- |
| Root | Existing physical application directory; the same scope and limits as the standalone command |
| Streams | Caller-owned; never disposed by the server; the caller selects UTF-8 encoding for stdio |
| Output | JSON-RPC responses only, flushed after each response; no console logging or encoding changes |
| Failures | Startup, transport and request-size failures propagate; request errors remain protocol responses |
| Effects | No installation, update or network operations; only explicit apply/recovery requests mutate model files |

`McpFailure` identifies server admission failures such as a rejected root or an
oversized request. File-system and stream exceptions also propagate unchanged.
Internal protocol, root and tool types are not public embedding APIs. Hosts own
process exit codes and any diagnostics outside the protocol stream.

## Model understanding

Syntax queries work independently of executable backend support. Inspect
`success`, diagnostic counts and coverage. Invalid source can produce a partial
index; it is never silently presented as a valid complete model.

`describe-application` summary reports model-wide `syntaxOnly` and
`executionReadiness`, including source declarations without routed commands.
Declaration details report the member's constructs and referenced command actions,
not unrelated global declarations. A plain command can therefore have
`syntaxOnly: false` while its application is syntax-only; neither that field nor a
null member readiness message proves the whole model executable.
Event sources, streams and direct-property/literal command routes are admitted by the
executable semantic model **v8**. Property paths and handler commands remain unadmitted (`PLAY0268`); generated route sources fail binding with `PLAY0273`. `EventSource` declarations have application addresses;
`EventStream` addresses include their physical source owner (for example,
`Account.Transactions`). Command, specification and slice readiness lists every unadmitted feature when
property-path routes, operations ([#301](https://github.com/Cratis/Screenplay/issues/301)), handlers, exact numbers ([#285](https://github.com/Cratis/Screenplay/issues/285)) or generated properties on concepts with validation rules coexist,
including dependencies on referenced commands. Generated values and responses are admitted as ESM v7 and are not listed as unadmitted. Exact numeric mode affects readiness across the application. `syntaxOnly` is a boolean;
`executionReadiness` is a nullable string that names each unadmitted feature and, where available, its tracking issue.
Ambiguous route/property syntax remains blocking; readiness never selects a route.

| Tool | Selection | Result |
| --- | --- | --- |
| `describe-application` | `view`: summary, children or declarations; optional `parent`, scope/kind/document filters | Compact counts or paged logical navigation |
| `find-declaration` | Required exact `name`; optional kind/scope/document | Paged matches; typed syntax only with `includeContent: true` |
| `search-declarations` | Optional `name`, `match`: exact/prefix/contains, kind/scope/document | Compact scoped search |
| `declaration-details` | `address`, `kind`; optional `view` | Summary (including `description` and `documentation` wherever supported) or paged properties, occurrences, commands, specifications, produces, enum values, module/feature dependencies; explicit syntax view |
| `processing-record` | Optional `controllerName`, `controllerContact`, `offset`, `limit`, `expectedSourceRevision` | Paged declaration-derived controller inventory; not legal advice |
| `find-references` | `address`, `kind` (including `Property`) | Paged resolved incoming references, ambiguities and incomplete property links, with owners/roles |
| `dependencies` | `address`, `kind` (including `Property`), direction incoming/outgoing; optional descendants/document | Direct indexed dependencies and resolution candidates; property outgoing follows mapping counterparts |
| `dependency-graph` | Optional view, from/to levels, scope, direction, kinds, includeTestOnly, evidenceLimit | Inferred slice/container/context edges, ordering cycles, story-order suggestions, unresolved references or checked dependency declarations |
| `find-fixtures` | Specification address, role, property, value, scope/document | Paged effective assignments with type, value, location and authored/example/override origin, including `when append` event payloads (`whenAppendedEvent`) and `for` destinations (`whenAppendedEventDestination`) |
| `find-assertion-gaps` | Optional scope/document | Slices without specifications declaring a `then` assertion, including `then denied` |
| `run-specifications` | Optional proposalId, exact specification address or semantic id, scope, offset, limit, expectedSourceRevision | Deterministic reference execution with passed, failed and unsupported results |
| `find-specification-obligations` | Optional module/feature/slice scope, document | Per-declaration specification obligations with met/unmet status and matching specification owners |
| `find-modeling-smells` | Optional scope/document, eventFanOutThreshold/propertyFanInThreshold | Information-level modeling questions; no compiler diagnostics |
| `diagnostics` | Optional `checks` (comma-separated names, codes, or `all`), `scope`, document | Paged diagnostics, severity counts, scoped declaration counts and affected scopes |
| `read-document` | Required relative `path` | Exact original UTF-8 byte pages |
| `merged-document` | `view`: source, syntax or both | Canonical merged byte pages or explicitly requested typed AST |
| `recommend-layout` | None | Size-admissible layout choices and recommendation |
| `syntax-schema` | Optional concrete `kind` | Kind list or exact typed JSON schema |
| `semantic-diff` | Each of `before`/`after` selects active workspace, path or export; legacy `beforeWorkspaceJson`/`afterWorkspaceJson` supported; optional offset/limit/expectedSourceRevision | Paged authoring-structure comparison without rebinding the workspace |

Names and kinds are case-sensitive. Logical address example:
`Projects.Registration.RegisterProject.RegisterProject`, kind `Command`.
Modules/features combine physical fragments; all contributing locations remain
available. Duplicate leaf declarations remain visible with diagnostics.

Navigation `scope` includes descendants unless `descendants: false` is specified. Dependency
aggregation uses `descendants: true` explicitly and does not imply transitive
runtime impact. For a property, pass `kind: "Property"` and append its member name to the owner's
address, for example `Projects.Registration.RegisterProject.RegisterProject.name`.
Properties remain absent from declaration listings. Event members at that address belong to the
current generation; historical shapes use `Owner.generationN.member` and carry `historical: true`
in their target details.

Property links cover explicit production, capture-append and projection mappings, case-insensitive
projection AutoMap, declarative conditions, query filter and lookup parameters, command read keys,
projection `from`, parent and removal keys, join `on` fields, screen/form bindings,
specification/example fixtures and assertions, and seed event assignments. Seed assignments
have role `fixture`, are owned by the seed entry, and resolve to the event's current
generation; the seed group's `for` event source id is not a property. Roles are
`mapping-source`, `mapping-target`, `automap`, `condition`, `key`, `screen-binding`, `fixture`,
`assertion`, `response`, `opaque` and `unclassified`. Links retain owner, location, resolution candidates and a resolved `otherEnd` when
known. An unresolved counterpart or opaque occurrence makes the property's `complete` result false,
with incomplete evidence retained at its source location. `dependencies` outgoing follows direct
mapping counterparts, not transitive property chains. Nested composite paths remain incomplete
evidence, not resolved member chains. Legacy command `reads` retains alias and qualified-view
candidates as incomplete evidence: the binder does not admit those reads. Event-source captures
link `$.member` through their declared `source events` inputs; external source items remain
incomplete. Policy readers include commands gated at module, feature and command levels, including
logical combinations of policies. These are authored links, not proof of executable admission.

Constraint property keys and command stream-id mapping sources are classified as `key`; command
response property sources are classified as `response`. Production route overrides are separate
from event payload mappings. Both dependency directions retain all direct mapping counterparts,
including mappings with multiple sources.

Opaque text naming a property is reported as `opaque`, never resolved as code. After the typed
indexers run, a completeness backstop scans each declaration referencing the property owner,
including the owner itself, query/example indirections and effective policy caller contexts.
It compares whole-word identifiers case-insensitively in the declaration's typed source text and
comments, counting quoted text conservatively. Every occurrence not covered by a classified link
is reported as `unclassified` incomplete evidence with owner, location and `reasons`. Same-named
properties in unrelated declarations do not affect completeness. External code contents and
external registrations remain outside coverage; results state their coverage. Declaration dependency graphs retain their explicit
declaration-reference coverage; property links do not add inferred slice edges.

### Specification obligations

`find-specification-obligations` derives authored obligations, not runtime coverage. A **met** obligation says that a matching specification exists, not that behavior is fully covered or that the specification passes. Items carry a stable `ruleId`, resolvable `declaration` owner (address/kind), rule `location`, `subject`, `description`, `severity`, nullable `reason`, `status`, and matching `specifications`. The list is capped at 20 with `specificationCount` and `specificationsTruncated`; inspect the relevant slice's specification details for the complete inventory. Pages enforce both item and serialized-byte budgets.

| Rule ID | Obligation and matching evidence |
| --- | --- |
| `SPEC001` | Command success: a resolved `when` command, an outcome assertion, no error or denial |
| `SPEC002` | Each `require`: a resolved command action and exact explicit rejection message |
| `SPEC003` | Each command or concept validation rule: exact explicit rejection message; concept rules use commands accepting that concept |
| `SPEC004` | Authorized command/query (including module/feature gates): its resolved action and `then denied` |
| `SPEC005` | Each value-unique constraint: prior claim event, equal literal composite key through production mappings, different known destinations, and rejection |
| `SPEC006` | Read model: state/absence assertion or results asserted through a resolved query returning that view |
| `SPEC007` | Each projection `remove with`: append its resolved event or execute a command producing it; root removals require absence of the affected view, while child/nested removals require an assertion of the affected collection/object path. Variant-local removals match only that variant's view |
| `SPEC008` | Reaction: drive its event by append/command, fire its named or built-in trigger, drive a same-slice clock schedule, or explicitly redeliver to it; assert a declared produced event (including invoked-command events) or same-slice `then no events` |

Examples are expanded before matching. Ambiguous references do not meet obligations. Rejection rules without an explicit message remain `info`/`unmet`: a bare `then error` cannot distinguish individual rules. Opaque validation blocks report one information-level obligation rather than invented code rules. Syntax-only owners retain authored presence status but carry `info` and the executable-readiness reason. Competing claims use literal fixtures and direct command-to-event property mappings; unknown expressions do not prove an equal claim. A constraint's explicit message must match; without one, any rejection suffices alongside the competing-claim evidence. This is a deterministic presence heuristic, not simulation or proof of causation.

Optional `scope` must name exactly one module, feature or slice; unknown/ambiguous scopes reject. Scoped results include referenced concept validations and value constraints on selected events, even when declared at application level. Concept rejection evidence is matched across all commands accepting the concept, independent of the selected scope. `document` filters obligation owners by contributing source path. Use `offset`, `limit` (default 50, maximum 200) and `expectedSourceRevision`; continuation requires the first page's `sourceRevision`, and stale source is refused. The report preserves the compilation verdict and never adds compiler diagnostics.

### Advisory modeling smells

`find-modeling-smells` is separate from compilation. Every finding has `severity: "info"`, a stable `ruleId`, a resolvable `declaration` owner, nullable `property`, and a `question`, never a prescription or a compiler warning. Its findings do not change the compilation verdict, including with `--warnaserror`. Identical syntax does not prove identical business meaning.

| Rule ID | Review question prompted by |
| --- | --- |
| `SMELL001` | Event name ending in `Updated`, `Changed`, `Edited`, `Saved`, `Modified`, `Deleted`, `Synced` or `Received` |
| `SMELL002` | Command name starting with the PascalCase word `Update`, `Edit`, `Save`, `Set`, `Manage`, `Get`, `Load` or `Fetch` (not longer words such as `Settle`) |
| `SMELL003` | Nonempty event property set identical to another event produced by a different command |
| `SMELL004` | Command producing more distinct resolved events than `eventFanOutThreshold` |
| `SMELL005` | Read-model property fed by more distinct resolved events than `propertyFanInThreshold` |

Names are case-sensitive. Property sets compare declared names, type names, collection and optional modifiers, independent of order. Descriptions, intent, payload values and code are not compared. Fan-out counts event targets, not repeated production clauses or operations. Fan-in follows explicit projection mappings and known-event automap, `every`/`all`, joins, nested/child property paths and variants, including synthesized `enters on` handlers. Effective automap excludes aggregate-only `from` blocks, explicitly mapped targets and consumed join sources; it matches compatible declared properties by case-insensitive name. Unresolved references, imported event shapes and opaque code are not guessed. Thresholds default to 5 and accept integers from 1 through 200; a count equal to the threshold is not flagged.

`related` declaration evidence is capped at 20, with `relatedCount` and `relatedTruncated`. Pages use the same bounded item/byte budgets and revision contract as the obligations report. Scope must name one module, feature or slice; document filters finding owners. The report is read-only and has **no per-declaration suppression yet**. Choosing a source annotation or an external address/rule-ID suppression contract remains a separate language/tooling decision; no new `.play` syntax is accepted by this report.

### Completeness diagnostics

`diagnostics` accepts `checks: "data-bindings,input-surfaces,field-origins,query-keys,event-consumers,navigation"`, `"enum-pinning"` (or `"PLAY0671"`) or `"all"`. Unknown names are invalid parameters. Selected findings are warnings and information merged into the ordinary severity summary and paged, source-revision-bound response, including scope and document filtering. Checks run only without whole-application source errors; `completenessStatus` otherwise reports `completeness checks skipped: the model has N error(s)`. `completenessCoverage` is `structure only; a finding is a prompt to look`. See [Completeness checks](/screenplay/completeness/) for exact rules and exemptions. No executable admission or code analysis is implied.

### Scoped diagnostics

Call `diagnostics` with `scope: "Projects.Registration.RegisterProject"` to check a module, feature or slice by its full case-sensitive dotted address. Descendants in the module, feature and slice hierarchy and declarations that directly reference them are included; same-named types, concepts and event sources are not descendants. An unknown, empty or ambiguous scope is refused with a clear invalid-parameters error. Unlike navigation tools, diagnostics always includes descendants and does not accept `descendants`.

The whole application is still compiled for reference resolution. The `summary` severity counts and `success` describe the selected scope and its direct dependents before document filtering or paging; the page contains diagnostics from that set, while `wholeApplicationSuccess` preserves the full compilation verdict. `declarationCount` counts the requested scope and its descendants, and `dependentDeclarationCount` counts additional direct dependents. `affectedScopes` lists their owning scopes (an empty string means application-level). `unresolvedEventConsumers` separately reports `referenceCount` and `scopes` for unresolved event references outside the reported declarations (an empty scope string means application-level). Their former targets cannot be proved after a rename or removal, so they do not change `affectedScopes`, scoped diagnostic counts or `success`; their errors remain part of `wholeApplicationSuccess`. `possiblyAffectedReferenceCount` counts only the remaining unresolved references outside the reported declarations, excluding both direct dependents and the unresolved-event category. `dependencyCoverage` states the reference index's limits. Scope-only fields are omitted from unscoped `diagnostics` responses. An optional `document` narrows only the returned page, not `success`, `summary`, the declaration population or affected scopes. An empty document page can therefore accompany `success: false` when the scope has an error in another document, matching unscoped diagnostics filtering.

Dependents are declarations, not entire slices, and inclusion is not transitive. Ambiguous reference candidates and unresolved references matching names declared in scope are included conservatively as direct dependents. Unattributable event consumers are reported in their separate category, and other unresolved references contribute to the possibly-affected count; neither category expands the diagnostic selection. Code, imports and other unindexed references do not establish impact. Diagnostics outside declaration ranges fall back to the file's import placement or selected slice. Diagnostics without a path or valid line are application-wide and included in every scope. This is not executable readiness or a replacement for a whole-application check. Use the affected scopes to choose wider checks. The `read-workspace` `diagnostics` view accepts the same optional `scope`, returns the same counts, impact and summary alongside its workspace envelope, and pins pages with `expectedRevision`. Without `scope` it remains a whole-workspace view. `scope` is rejected for other workspace views; `executable-diagnostics` remains whole-workspace.

Pages keep the existing `offset`, `limit` and `expectedSourceRevision` contract. Incremental/watch caching is not part of this filter.

Fixture values are syntax, not evaluated expressions. Text filtering is invariant
and exact. Field types come from their own unambiguous declaration, not a global
field-name lookup. Assertion presence is not runtime coverage; no tool executes
specifications. Compact specification summaries include `thenDenied` independently of
error counts and `whenAppendedEvent` independently of the `when` command. Appended
event references and dependencies carry the `whenAppendedEvent` role, not
`thenEvent`. Fixture roles are `givenEvent`, `whenAppendedEvent`, `whenRedeliveredEvent`, `thenEvent`,
`whenCommand`, `givenReadModel`, `thenReadModel`, `queryArguments`, and
`queryResult`; each role also has a `…Destination` form for explicit `for`
destinations. Event roles additionally expose `…Stream` (`property: "stream"`, qualified source/stream text), `…StreamId` (`property: "streamId"`, literal value), and `…NoStream` (`property: "no stream"`, `value: true`) rows from effective routes. Route, id, part and no-stream rows carry the route's authored/example/override origin and the example name. A whole-route replacement reports `overriddenValue` once, on the stream or no-stream row, as the replaced route's authored text. They share the occurrence ordinal with its payload and destination; a payload property named `stream` or `streamId` remains a separate ordinary event-role row. Composite routes expose one `…StreamIdPart` row per authored mapping, with `property` equal to the part name and its literal `value`, in authored order. Stream inventory and declaration summaries expose `streamIdParts` as ordered name/type declarations. Parts have AST handles but no declaration or semantic identity; typed proposals edit the three `streamIdParts` collections through `syntax-schema` and `propose-ast`. Missing routing lines produce no route rows, not default or inferred routing. `no stream` is legal on `then` events, redelivery locators and event example declarations; it is refused on effective `given` and `when append` steps. Route rows describe syntax only: specification routes remain refused by executable-model binding with `PLAY0268` (#457).

## Dependency graph

`dependency-graph` infers dependencies from explicit references inside slices. Edges
point from the consumer to the producer: A → B means A depends on B. It does not
inspect code, expression identifiers, property paths or runtime behavior. References
outside slices, such as module-owned forms, do not acquire an inferred slice owner.
Use `dependencies` for a declaration's scoped indexed references instead.

| Argument | Type | Default | Values / limits |
| --- | --- | --- | --- |
| `view` | String | `edges` | `edges`, `cycles`, `order`, `unresolved`, `declarations` |
| `from` | String | `module` | `slice`, `feature`, `module` |
| `to` | String | `module` | `slice`, `feature`, `module`, `context` |
| `scope` | String | Whole application | Exact, unambiguous module, feature, slice or context address; includes descendants |
| `direction` | String | `outgoing` | `outgoing` filters consuming nodes; `incoming` filters producing nodes in the edges view |
| `kinds` | String array | All kinds except test-only references | Any subset of the kinds below |
| `includeTestOnly` | Boolean | `false` | Allows specification references, including imported specification facts |
| `evidenceLimit` | Integer | `3` | `0`–`20` references per edge; counts remain complete |
| `limit` | Integer | `50` | `1`–`200` items |
| `offset` | Integer | `0` | Page offset; continuation requires `expectedSourceRevision` |
| `expectedSourceRevision` | String | None | The exact `sourceRevision` returned by the first page |

Kinds are `usesFactsFrom` (projections, reducers, constraints and concurrency event
lists), `reactsTo` (named event triggers), `decidesFrom` (read-model reads), `asks`
(command invocations and actions, including guarded alternatives and execute
fallbacks), `shows` (queries and screen navigation),
`verifiedWith` (specification events and commands), and `outsideTheModel`
(imported event contracts without a local producer). References to shared
application types, concepts, policies and triggers are excluded and counted.
Unresolved graph references never become edges. Reference capture for `reads`,
concurrency dimensions and event lists, and reducer headers and rules follows
.NET's UTF-16 identifier recognition: supplementary-plane letters are not word
characters in these declarations and do not create captured names.

`view: "declarations"` pages opted-in containers in syntax order. Each item has a `container`, header `location`, `declarations` (target text, resolved address, location and status `used`, `provisional`, `unused` or `invalid`) and counted `edges` grouped by consumer, producer, kind, coverage status and covering declarations. An edge is `declared`, `provisional` or `undeclared`, with its `coveringDeclarations` and an `evidence` array of reference names, roles, locations and ambiguous alternatives. `evidenceLimit` bounds that array per edge (default `3`, maximum `20`); `evidenceCount` stays complete and `evidenceTruncated` reports omitted references. A limit of `0` returns counts and coverage without evidence. Ambiguous ownership is always provisional, even when no declaration covers a candidate; it never raises an undeclared warning. A declaration used by certain evidence remains `used` even if other evidence is ambiguous.

This view checks each container against its own declarations, including descendant consumers. It always counts `usesFactsFrom`, `reactsTo`, `decidesFrom`, `asks` and `shows`; `kinds`, `includeTestOnly`, `direction` and level selectors apply to the inferred views, not to this inventory check. `evidenceLimit` bounds only the returned evidence, never the inventory check. Internal edges and producers in a proper ancestor's own slices are excluded. `scope` selects checked containers, including descendants. Use `declaration-details` with `view: "dependencies"` and kind `Module` or `Feature` to page authored dependency syntax, or [declared dependencies](/screenplay/slices/#declared-dependencies) for diagnostic rules.

Event names resolve to the earliest slice declaring the event, including inline
events and generations. Other names resolve to the earliest declaring slice;
read-model reads prefer projection/reducer builders, including variant outputs,
then fall back to shape declarations. Resolution ignores case and uses authored
order, with syntax order as fallback. This differs from the case-sensitive,
scope-aware resolution of `dependencies`. Multiple qualifying slices retain
`ambiguous: true` and the alternatives; repeated generations in one slice do not
create alternative owners. Same-slice references are dropped.

The response includes `success`, `sourceRevision`, `orderSource` (`authored` or
`syntax`), diagnostic summaries, coverage counts and `page`. Failed compilation
can leave a partial graph; it is not evidence that all dependencies are known.
An edges page has `source` and `target` with declaration kinds and dotted addresses,
`sliceEdges` (distinct consumer/producer pairs, regardless of kind), `references`,
`byKind` reference counts, distinct `consumers` and `producers`, and ordered
`evidence`. Each reference includes its consumer, producer, kind, role, name,
ambiguity, alternatives, test-only flag and source location. `evidenceCount` is
uncapped; `evidenceTruncated` tells you whether more evidence exists. Context
addresses use `context:Shipping`, so they cannot collide with a feature named
Shipping. Contexts are not local declarations; local node addresses and kinds
can be passed to `declaration-details` or `find-references`.

Container edges exist only between disjoint containers. Equal nodes and
ancestor/descendant pairs are excluded, including a feature and its own
sub-feature. Mixed levels work in the edges view; `cycles` returns no groups for
mixed levels. Same-level cycle items contain ordered `members`. The `order` view
pages per-container `container`, suggested `children` and `changed`, independently
of from/to levels. The `unresolved` view pages consumer, kind, role, name and
location. Scope filters cycle members, order containers or unresolved consumers;
direction applies only to edges. Scope resolves to one node before filtering its
actual descendants. An unknown address or an address shared by different node kinds
(for example, a slice and its sibling feature) returns JSON-RPC `-32602`. For an
ambiguous address, use an unambiguous ancestor or descendant scope, or omit scope.
Coverage also lists unused imports.

Cycles and order use only `usesFactsFrom`, `reactsTo` and `decidesFrom`. After
removing internal cycle edges, the suggestion puts producers first, with authored
rank breaking ties and cycle members retaining their relative order. It never
applies an edit or changes executable bytes, revisions or identities. The core
story traversal visits each feature's own slices before its sub-features.

The graph can find cycles that [timeline diagnostics](/screenplay/imports/#timeline-diagnostics)
do not report: `PLAY0517` observes projection and named-trigger event flow only.
For example, Library's Catalog consumes loan events while Loans reads CatalogEntry,
so the graph finds {Catalog, Loans}; the `decidesFrom` edge is outside `PLAY0517`.
See [Explore a model](/screenplay/mcp/explore/#see-how-modules-and-features-depend-on-each-other)
for prompts.

## Event model board

Hosts that render MCP Apps views (`io.modelcontextprotocol/ui`) are also offered
`visualize-model`, which draws the application as an event model board, and the
`ui://screenplay/event-model-board.html` resource it is drawn with. With a
`proposalId` or a `sketch` of whole `.play` documents, the board shows what the
change would make of the application, and the result lists the drawn declarations
it adds and removes. Nothing is written. See
[See a model as an event model board with MCP](/screenplay/mcp/view/).

## Paging and snapshots

Source queries return `sourceRevision`. When requesting a page after offset zero,
pass it as `expectedSourceRevision`. Changed bytes reject continuation, including
same-length edits. Current-workspace reads likewise require `expectedRevision`.
Caching never substitutes timestamps for exact disk checks.

Pages expose counts and continuation. Scope/filter/select the page before
requesting expensive typed content. Large single items or whole-tree responses
exceeding the response budget reject with a bounded alternative; they are not
silently truncated.

`read-document`, merged `source`, proposal content and workspace export use base64
byte pages. Offsets/limits count **decoded bytes**, not characters or base64 text.
Original-document reads preserve comments, BOM and line endings; merged source
is canonicalized.

## Execute specifications

`run-specifications` is read-only. It executes scenarios through the in-memory reference evaluator without applying or mutating proposals, writing files, executing attached code or contacting external services. With no `proposalId`, it runs the current model: an opened workspace keeps its identities; reopen it after source edits. Without an opened workspace, the tool reads the current root and its persisted identity state.

With `proposalId`, it runs that outstanding proposal's retained candidate, including its identity catalog and planned renames or advancements. The snapshot is not refreshed from disk: an externally changed base still runs the candidate you proposed, not the newer current model. This does not make a stale proposal applicable. Unknown, discarded or applied proposals return `UnknownProposal`; reopening the workspace also discards outstanding proposals.

All arguments are optional:

| Argument | Type | Meaning |
| --- | --- | --- |
| `proposalId` | string | Outstanding proposal from this connection; omitted to execute the current model |
| `specification` | string | Exact, case-sensitive dotted module/feature/slice/specification address, or semantic id in the selected model |
| `scope` | string | Case-sensitive dotted module, feature or slice prefix; selects descendant specifications |
| `offset` | integer | Result offset, default `0` |
| `limit` | integer | Page size, default `50`, range `1`–`200` |
| `expectedSourceRevision` | string | Pin `sourceRevision` from the first response; required for nonzero offsets. With `proposalId`, pins candidate source, not current source |

A selection matching no specification is invalid parameters, not a pass. If both selectors are supplied, their intersection is used. Results are sorted by address. Counts and the overall outcome cover the complete selection, not just the returned page. Continue using `page.nextOffset` and pin `sourceRevision`; changed source refuses continuation. Responses exceeding the server's byte budget refuse rather than truncate.

A proposal run also returns `proposalId`; its `sourceRevision` identifies the candidate's source bytes. Use that same proposal ID and source revision for every continuation page. A wrong revision returns `StaleSourceRevision`.

The response contains `sourceRevision`, `outcome`, `discovered` (all specifications), `selected`, `executed`, `passed`, `failed`, `unsupported`, `executableDiagnostics`, `planIssues` and `page`. `executed` excludes unsupported scenarios. Each page item contains `address`, `semanticId`, `outcome` (`passed`, `failed`, `unsupported`), `executionOutcome`, comparison `failures`, and nullable `capability` and `reason`. `executionOutcome` is the evaluator's normalized result, so an expected rejection or denial can pass. Unsupported scenarios name the blocking capability and declared reason. A plan-admission failure returns its typed `planIssues` and marks selected scenarios unsupported.

An unbound model returns `outcome: "unbound"`, executable diagnostics and `page: null`, never an empty passing list. Failed, unsupported and unbound responses set `isError: true`; failed takes precedence over unsupported in the overall outcome. Failures include expected and actual facts, read models, scalar or record command responses and query results, with effective fixture provenance for example-based scenarios. A bound model with zero specifications has zero discovered: a pass is not a coverage verdict. See [`screenplay test`](/screenplay/tool/#run-the-models-specifications) for the same evaluator from the command line.

## Release baseline

The committed `.screenplay/release-baseline.json` records which event revisions a
release admitted. It is independent of source, workspace, semantic and catalog
revisions. Admit with the [CLI](/screenplay/tool/); MCP never writes or repairs it.

`open-workspace` and `workspace-state` report `releaseBaseline`:

| Member | Contract |
| --- | --- |
| `status` | `absent`, `valid` or `unreadable`; unreadability never blocks opening or reading |
| `revision` | `brev1:` canonical hash; `brev1:absent` without a file, null when unreadable |
| `reason` | Unreadable reason, otherwise null |
| `entries` | Admitted revision count, null when unreadable |
| `violations` | Changed or missing revision count; null when the current model cannot bind |
| `checkFailure` | Check refusal when a valid baseline cannot be compared, otherwise null |

`list-release-baseline` requires an open workspace. It takes `offset`, `limit`
(default 50, maximum 200) and `expectedReleaseBaselineRevision` (required on
continuation). The response has `releaseBaseline` and `page`. Each entry has
`event` (contract ID), `revision`, `eventType`, `lastAddress`, `admittedIn`,
`admittedAt`, `status` and `path`. Status is `unchanged`, `changed` (with its first
differing stored path), or `missing`; `unknown` means the current model cannot
bind. An unreadable baseline returns its status with `page: null`, not an empty
verified set.

Property `generations[]` and event `eventRename` results carry
`mutability: {basis, releaseBaselineRevision, admittedIn}`:

| Basis | Property edits | Event rename |
| --- | --- | --- |
| `noBaseline` | Exactly one of `newGeneration` or `eventNeverPersisted` | Pins by default |
| `notInBaseline` | No flag defaults to `inPlace`; either explicit flag is honored | Does not add a pin |
| `baselined` | Only explicit `newGeneration`; otherwise `BaselinedRevisionImmutable` | Preserves stored name; `eventNeverPersisted` refuses |

`releaseBaselineRevision` is null only for `noBaseline`; `admittedIn` is set only
for `baselined`. `eventRename.pinAdded` reports whether the rename inserted a pin.
If any historical revision is admitted, a logical event rename preserves its pin.

Every proposal checks the complete candidate's resolved contracts before retention.
`BaselinedRevisionChanged` and `BaselinedRevisionMissing` refusals list all affected
revisions as `violations` (`kind`, `event`, `revision`, first differing `path`).
Shared concept, type or enum changes also count; descriptions and type names do
not. An unreadable baseline refuses event-contract changes with
`UnreadableReleaseBaseline`, but permits non-contract edits.

Proposals record `releaseBaselineRevision`. `read-proposal view=release-baseline`
reports retained baseline status, violations and a page of original event revisions
with their mutability; pin `expectedReleaseBaselineRevision` on continuation.
All proposal tools, `read-proposal` and `apply` accept that optional pin. Apply also
checks the retained baseline and resolved contracts again, returning
`StaleReleaseBaseline` if the baseline changed. Semantic-diff bytes are unchanged;
its retained disk baseline is not the release baseline.

## Model notes

Notes live outside source and executable revisions in the committed
[model notes sidecar](/screenplay/model-notes/). `open-workspace` and the `workspace-state`
status view include `notesRevision` and `notes: { status, reason, count }`.
Statuses are `ok`, `absent` and `unreadable`; reason is null when readable, revision
and count are null when unreadable. The absent revision is `nrev1:absent`.
Unreadable notes never block model opening or source edits.

`list-model-notes` requires an open workspace and returns `notesRevision`, `view`
and `page` (`revision`, `totalCount`, `offset`, `items`, `nextOffset`). Filters are:

| Argument | Values and default |
| --- | --- |
| `anchor` | `{ semanticId }` (including an orphan's ID) or `{ address, kind }` with a dotted declaration address |
| `scope` | Optional dotted module/feature/slice; descendants and ancestor notes |
| `kind` | `question`, `task`, `note`; omitted selects all kinds |
| `state` | `open` (default), `resolved`, `orphaned`, `all` |
| `ref` | Exact reference identifier in `refs` or `resolution.ref` |
| `view` | `items` (default) or `findings` |
| `offset`, `limit` | Item pages; at most 200 items, also bounded by serialized bytes |
| `expectedNotesRevision` | Optional on the first page; required when offset is greater than zero |

An item carries `note`, current `address`, `state` and nullable `orphanReason`.
Orphans retain their durable note, return a null current address and reason
`unknown` when the catalog lacks the identity. `findings` returns one finding per
selected unresolved question: `noteId`, `severity: "info"`,
`code: "model-note.open-question"`, `message` and owning slice/container `address`.
These are host findings, not PLAY diagnostics. Use `state: "all"` to include
orphaned unresolved questions in a findings query.

`propose-model-notes` pins `expectedRevision`, `expectedCatalogRevision` and
`expectedNotesRevision`, and requires 1–200 `operations`. Each operation has
`operation` and these additional fields:

| Operation | Fields |
| --- | --- |
| `add` | Required kind, text, author, and exactly one anchor or target; optional refs |
| `edit` | Required id; optional kind, text, refs (replaces the whole refs list) |
| `resolve` | Required id and resolution: by, text, author; optional semanticId, ref. specification/description require semanticId; external-decision requires ref |
| `reopen` | Required id; clears resolution |
| `reanchor` | Required id and exactly one anchor or target |
| `remove` | Required id; explicitly removes the note |

Anchors are `{ semanticId }` or `{ application: true }`. A target can also be an
address (`{ address, kind }` or the typed address used by move) or a current
`{ revision, documentId, path }` source handle. Syntax-only targets fall back to
their nearest identified slice/feature/module/application. Substitutions report
the original target, resolved anchor/address and reason. Document and authoring-key
anchors refuse. References use `{ id, url? }`; URLs must be absolute HTTP(S) and
are never fetched. Text and author are nonempty, trimmed and at most 16384
characters. Each note has at most 100 references. The sidecar permits at most
10000 notes and 4 MiB UTF-8. Author is unverified free text; ID and timestamps are
server-generated, not client inputs.

The response includes `success`, `proposalId`, `notesRevision`,
`afterNotesRevision`, `addedNotes` IDs, `substitutions`, `changeCount: 0`,
`stateChange` and review guidance. A failed batch retains nothing and writes nothing.
`read-proposal view=notes` returns before/after notes revisions and a page of
`{ id, before, after }` pairs; pin `afterNotesRevision` as `expectedNotesRevision`
on continuation. Existing `apply` journals the retained notes plan; it verifies
notes revision and exact original bytes without requiring another notes parameter.
Source/AST/rename/move/property proposals and proposal reviews add `orphanedNotes`
IDs for notes their resulting catalog would orphan, plus `notes` status/reason/count.
If notes are unreadable, `orphanedNotes` is null (unknown), not an empty proven set,
and `notes.status` is `unreadable` with its reason. Unknown note `view` or `state`
values are rejected as `InvalidArguments`, including direct server-side calls.
Rename and move never rewrite
the notes file.

`UnreadableNotes` refuses note reads or writes without auto-repair.
`NotesRevisionDrift` refuses stale notes proposals/apply; paged revision mismatch
uses the usual stale revision error. `StaleRevision` covers workspace/catalog
changes. `PendingOperation`, `RecoveryRequired` and `UnknownProposal` retain their
[recovery behavior](/screenplay/mcp/recovery/). Invalid operation shapes/targets are invalid
arguments or failed requests, never successful partial batches.

## Workspace tools

| Tool | Required arguments | Optional arguments |
| --- | --- | --- |
| `repair-capabilities` | None | None |
| `open-workspace` | None | `applicationName`, `path`, `workspaceJson`, `includeContent` |
| `read-workspace` | `expectedRevision` | view (`source-map` for compiler source locations; `repairs` for typed diagnostic repairs; `implementation-requirements` for code attachment requirements; `executable-model` for canonical ESM bytes), offset, limit, `expectedModelRevision`, `expectedAttachmentManifestRevision` |
| `read-ast` | `expectedRevision` | documentId, path, kind, name, semanticId, view, includeContent, offset, limit |
| `propose` | Expected workspace/catalog revisions, operations | Explicit migrations/retirements, includeContent; legacy single-operation form supported |
| `propose-ast` | Expected revisions, formatting | operations, documents, validation, referencePolicy, migrations/retirements, includeContent |
| `propose-source` | `expectedRevision`, `expectedCatalogRevision`, `formatting`, `documents` | validation (`Authoring` default or `Executable`), referencePolicy (`Safe` default or `Draft`), semanticRenames, eventRenames, retiredSemanticAddresses, retiredEventAddresses, includeContent |
| `propose-repair` | `expectedRevision`, `expectedCatalogRevision`, `diagnosticCode`, `subject` handle, `formatting` | `line` for a `PLAY0565` concept-line repair, `pinRepairEvidence`, `expectedRepairEvidenceRevision`, includeContent; use the discovered `requiredFormatting`. `PLAY0479`, `PLAY0565` and `PLAY0653` support `PreserveTrivia` or explicit `CanonicalizeTouchedDocuments`; other repairs require `CanonicalizeTouchedDocuments` |
| `propose-rename` | Expected revisions, target handle, expectedName, newName | formatting, validation, includeContent, `eventNeverPersisted` (boolean, default false) |
| `propose-move` | Expected revisions, target address, newParent address | targetHandle, newParentHandle, formatting, validation, includeContent |
| `propose-property` | `expectedRevision`, `expectedCatalogRevision`, `action`; rename/remove: `target`, `expectedName` (rename also `newName`); add: `owner`, `expectedOwnerName`, `name`, `type` | `alsoRename` or `alsoRemove`; add: `optional`, `hops`, `fixtureValue`; `newGeneration`, `eventNeverPersisted`, formatting (`PreserveTrivia` default), validation (`Authoring` default), includeContent |
| `propose-number-enum-members` | `expectedRevision`, `expectedCatalogRevision`, unnumbered enum `subject` handle, `formatting` | validation, includeContent; only `CanonicalizeTouchedDocuments` is admitted; never renumbers numbered or mixed enums |
| `propose-extract-inline-event` | `expectedRevision`, `expectedCatalogRevision`, inline event `subject` handle, `formatting` | validation, includeContent; only `CanonicalizeTouchedDocuments` is admitted |
| `expand-layout` | Expected revisions | layout (`single`, `module`, `feature`, `slice`; default `slice`, one file per slice), validation, formatting, referencePolicy, includeContent |
| `read-proposal` | proposalId | `expectedRepairEvidenceRevision`, view (`semantic-diff` for structural impact; `implementation-requirements` for attachments; `notes` for note before/after items; `release-baseline` for mutability and violations), documentId, offset, limit, expectedSourceRevision, expectedNotesRevision, expectedReleaseBaselineRevision |
| `export-workspace` | expectedRevision | proposalId, offset, limit |
| `workspace-state` | None | view, proposalId, expectedStateRevision, offset, limit |
| `list-release-baseline` | None | offset, limit, expectedReleaseBaselineRevision |
| `list-model-notes` | None | anchor, scope, kind, state, ref, view, offset, limit, expectedNotesRevision |
| `propose-model-notes` | expectedRevision, expectedCatalogRevision, expectedNotesRevision, operations | None |
| `discard-proposal` | proposalId | None |
| `apply` | proposalId, expectedRevision, expectedCatalogRevision | includeContent, `expectedRepairEvidenceRevision`, `expectedReleaseBaselineRevision` |
| `recover-workspace` | operationId | None |

`expand-layout` guards relative module, feature and slice sibling order in each
scope before creating a proposal, using the timeline's ordering root: the sole
document, an importing `application.play`, or the unique importer not itself
imported. An import-less `application.play` can coexist with that importer. With
no ordering root, expansion uses path order and discloses this in the existing
proposal `review` text. Imported children retain their parent-relative positions
among other container members on collapse, and header comments stay with their
physical declarations. Order does not change executable-model bytes or identities.

Use `propose-source` for whole `.play` text, `propose-ast` for typed node/document
edits, and `propose` for executable-only exact byte operations. The two authoring
tools require explicit formatting consent and share identity continuity, reference
policy and proposal review. `propose-source.documents` accepts `create-document`
(`path`, `stableKey`, `source`, optional `encoding`: `Utf8` or `Utf8WithBom`),
`replace-document` (`documentId`, `source`), `remove-document` (`documentId`),
`move-document` (`documentId`, `path`) and `rename-document-key` (`documentId`,
`stableKey`). It accepts no node `operations`. Each document is targeted at most
once per batch. Source is parsed with final-set import placement and then passed
to the typed authoring transaction. Parse/import errors return `SourceParseFailed`
with located `authoringDiagnostics` and no proposal ID; other validation failures
use the ordinary authoring rejection. Accepted source-only models can report
`executableReady: false`; `Executable` never falls back to `Authoring`. Review
with `read-proposal`, including exact bytes, dropped comments and introduced
executable errors, before `apply`. See [whole-source authoring](/screenplay/mcp/authoring-tools/#propose-whole-source-documents).

`tools/list` supplies nested argument schemas. Revisions, IDs and handles come
from the server; do not infer them from names or line numbers.

`read-workspace` views: documents, semantics, eventContracts, diagnostics,
executable-diagnostics, source-map, repairs, implementation-requirements, handler-intents, handler-intent-details, named-rule-intents, named-rule-intent-details, typed-contexts and executable-model. The `source-map`
view pages the compiler's semantic entries ordered by semantic ID: `semanticId`,
`role` (Declaration or Description), identity `origin`, `documentId`, `path`,
and exact `span` (zero-based UTF-16 start/length and one-based start/end
line/column). `available` is true only when compilation succeeded; otherwise
entries are empty. `executableDiagnosticsCount` and `executableDiagnosticsView`
(`executable-diagnostics`) are always present, including when the map is available;
read that paged view for errors and warnings. An empty page alone is not evidence
of successful compilation. Continuations require the current `expectedRevision`
as usual. Attachment changes can alter compilation availability between pages
without changing the workspace revision. The
`implementation-requirements` view pages implementation requirement envelopes
by role, owner address, optional member, language or
file, `RequirementId`, context/result contract versions, `RequiredCapability`,
`AttachmentResolution`, content hash, semantic/document ID and source line/column.
Each item also carries `bodySpan` (start/end-exclusive UTF-16 offsets and one-based
start/end line/column) and `bodyLines` (run-length-encoded `{line, column, count}`
entries: each run starts at the one-based original `line` and dedented starting
`column`, and covers `count` consecutive lines with that column; adjacent lines
with different columns start new runs). The existing `line`/`column` identify the directive,
not the code body. For inline code the offsets refer to the `.play` document; for
a resolved `file` they refer to the attached file, beginning at offset 0, line 1,
column 1, and `bodyLines` is empty. An inline block without parser positions
has `bodySpan: null` and an empty line map. An unresolved file has `bodySpan: null` and an
empty line map. Columns count UTF-16 code units; a tab is one column, and CRLF
occupies two offsets but one line break. Pagination remains by requirement, with
the usual response-size limit rather than a truncated body map.
The `typed-contexts` view pages descriptors in requirement and use-site order. Failed compilations expose only resolved command-handler shapes, explicitly `isWrapperReady: false`, with `available: false`; do not generate a wrapper from these. Each item carries requirement ID, role, context version, matching model revision (null for failed compilations), operation ID, ordered members, a transitive `types` table of concept/composite definitions, portable type (including shaped payload properties), nullability, derived status and semantic source identity/path (including literal `constantValue` and current `eventRevision` where applicable). The `implementation-requirements` view embeds only a small `typedContext: { count, operationIds }` reference, including count zero or multiple use sites; get members from `typed-contexts`. Unused policies have no wrapper-ready descriptor. Workspace and proposal views return `descriptorContractRevision: 1` and `available`; continuation requires `expectedDescriptorContractRevision: "1"` in addition to the usual workspace revision. An unknown contract revision refuses continuation. This revision is independent of `attachmentManifestRevision`, whose content-hash semantics are unchanged. Neither view exports descriptor bytes into the canonical ESM. A host must pair the sidecar and ESM from the same compilation and check provenance before rendering.

The MCP server loads implementation attachments from its trusted physical root for content hashing ([#244](https://github.com/Cratis/Screenplay/issues/244)), with warnings for refused files (`PLAY0430`–`PLAY0434`). It refreshes contents on each workspace operation, including when only the attachment changes; neither attachment text nor diagnostics enter persisted identity state or workspace revisions. For a file attachment whose contents could not be supplied, the content hash is empty;
bodied reducers no longer block binding. The `implementation-requirements` response includes `attachmentManifestRevision`, a deterministic hash of all requirement IDs, content hashes and resolution states. Legacy continuations (`offset > 0`) without `expectedAttachmentManifestRevision` remain valid but unpinned to attachment content. Clients can pin continuations by passing the response's revision as `expectedAttachmentManifestRevision`; when supplied, a changed manifest refuses the page with `StaleRevision`, even when `expectedRevision` is unchanged. Start again at offset zero after any refusal. Rejected compilations still expose
attachments without admitting an executable model. Document results contain root handles. `read-ast` returns
original occurrences, names, child counts and existing identities. Its `children`
view selects a parent document/path. Typed content is opt-in.

## Property change planner

`propose-property` supports `action: "add"`, `"rename"` and `"remove"` on command, current
event and non-key read-model properties. `target` and each additional selection
are original `read-ast` handles, with exact expected names. `alsoRename` and
`alsoRemove` contain `{target, expectedName}` objects for their matching action.
Only explicitly selected properties change; a resolved chain does not authorize
extra edits. Off-chain same-named fields appear in `notChanged` with `reason: "offChain"`.

For add, the required fields are:

| Parameter | Type and meaning |
| --- | --- |
| `owner` | Revision-bound `read-ast` handle of the command, current event or read-model declaration |
| `expectedOwnerName` | Exact original declaration name |
| `name` | New unqualified property name; existing names refuse |
| `type` | Authored type text, such as `String` or a concept name; does not create a concept |
| `optional` | Boolean, default false; use this flag rather than an optional suffix in `type` |
| `hops` | Array, default empty; each entry is `{at, name}` at another declaration or `{at, value}` at a `produces`, `from` or capture `append` block |
| `fixtureValue` | Optional literal source text; required when a non-optional addition has existing fixture or assertion occurrences |

Declaration hops use the owner's added type. Mapping hops supply the target's
value expression. AutoMap satisfies a same-named projection hop. Every selected
event producer and read-model consumer needs a mapping decision or a compatible
AutoMap source. Required fixture values are added to existing specification steps,
examples, query results and every seed entry of the selected event, including entries
without assignments; optional properties do not add fixture values. Missing required
`fixtureValue` refuses with `FixtureValueRequired` and lists the seed occurrences.
`target`, `expectedName`, `newName`, `alsoRename` and `alsoRemove` are invalid for
add; add-only fields are invalid for rename/remove. Off-chain hops refuse.

Reference policy is always `Safe`. Rename rewrites proven mappings and fixtures;
it inserts an explicit projection mapping when the unselected counterpart would
lose AutoMap. Remove refuses with remaining readers, removes writers and fixtures,
and marks removed assertion values `weakensAssertion: true`. Seed assignment renames
report `fixtureRewritten`, removals report `fixtureRemoved` with `weakensAssertion: false`,
and required additions report `fixtureAdded`. Unselected inputs
remain `chainCandidates`, not implicit removals. Type changes and nested composite
paths, read-model keys, opaque/unclassified text and unsupported spans are not
inferred or rewritten. Inline events require extraction first.

Without a release baseline, selected event fields require exactly one of
`newGeneration` or `eventNeverPersisted`. With a baseline, unadmitted current
revisions default to `inPlace`; admitted revisions require explicit `newGeneration`. The flags choose one mode for all selected events and select
no extra fields. Both true returns JSON-RPC `-32602`. The current generation alone
can be edited in place; older generations are immutable. Advancing creates one
new generation per selected event owner, retaining old property identities and
assigning new ones for the new shape. Command/read-model renames retain IDs.

The normal retained proposal response adds `property`, also returned by
`read-proposal` for every view:

| Member | Contract |
| --- | --- |
| `action`, `target` | Requested action; original property address for rename/remove or candidate property address for add, owner kind and optional generation |
| `changed` | Original address, owner kind, indexed role, `change`, source `location`, `weakensAssertion` |
| `notChanged` | Off-chain property address, kind and `reason: "offChain"` |
| `needsDecision` | Refusal reason, subject and candidate addresses/kinds/roles/locations |
| `chainCandidates` | Unselected chain address, kind, role and reason |
| `generations` | Required array of `{event, from, to, mode, mutability}`; canonical EventContract addresses, stable address order, one entry per selected event owner |
| `deployableWithoutTransformation` | False when any candidate event has several generations; no transformation is inferred |
| `esmVersionChange` | Selected ESM version pair, or null if unchanged or unknown |
| `esmVersionKnown` | Whether the candidate bound, independently of authoring acceptance |

`change` is one of `renamed`, `removed`, `mappingRewritten`,
`explicitMappingInserted`, `fixtureRewritten`, `fixtureRemoved`, `added`,
`fixtureAdded`. Generation mode
is `newGeneration`, `eventNeverPersisted` or `inPlace`. Each event retains its own original
revision; in-place edits report equal `from` and `to`. `generations: []` means no
accepted planned event-contract edits, not a successful proposal. There is no
singular `property.generation` alias. This tool had no published singular result;
that does not establish compatibility with independent external implementations.

Refusals return `success: false`, no writable partial candidate, and a
`failureKind`: `HopNotOnChain`, `PropertyReadersExist`,
`EventContractChangeRequiresDecision`, `HistoricalGenerationImmutable`,
`InlineEventRequiresExtraction`, `AmbiguousPropertyReference`,
`OpaquePropertyReference`, `UnsupportedPropertySpan`, `SourcesRequired` or
`FixtureValueRequired`. Ordinary stale revision,
formatting, compilation and identity-migration checks still apply. Structural
edits need explicit canonical formatting consent; trivia preservation never falls
back silently. Review exact bytes, identities and `view: "semantic-diff"`, then
use the ordinary journaled `apply`.

## Handler intent inventory

### Command named-rule intent views

Use `read-workspace` with the current `expectedRevision` and `view: "named-rule-intents"`. Coverage is **CommandNamedRule** only. It works without successful ESM binding and pages occurrence handles, owner identity/origin, member, hint count and selected source. Pending entries have no requirement ID; provisional owner IDs are not authoritative. Equal attached rules retain their existing distinct `#n` allocation. Colliding physical owners claim no attachment identity, and unresolved placement appears as separate refusals.

`named-rule-intent-details` accepts either `subject` (a revision-local occurrence handle, including pending) or an attached `requirementId`, not both, and pages ordered hints. Continuations require `expectedCatalogRevision`; stale workspace/catalog revisions refuse. Selection is not execution evidence. Existing `implementation-requirements` and `typed-contexts` views expose the bound attached predicate contract without treating hints as code. Edit through `propose-ast`, preview and explicit `apply`; no inventory call reads code files, realizes intent or confirms it.

### Handler intent views

Call `read-workspace` with the current `expectedRevision` and `view: "handler-intents"`. Coverage is **CommandHandler** only, independent of ESM readiness. Paged entries expose owner address/ID, requirement ID, identity origin and explicit provisional status, hint count, file/language, derived pending/file/inline state, and the handler AST handle. `executableReady` remains false. The inventory selects links from syntax; it does not read implementation files or report confirmation/freshness.

For ordered hints, use `view: "handler-intent-details"` with `requirementId`, `offset` and `limit`. Both views require the returned catalog revision as `expectedCatalogRevision` on continuation, as well as `expectedRevision`. Stale workspace/catalog revisions refuse rather than mixing snapshots. No attachment-manifest pin is needed for these model-only views.

Discover `HandlerSyntax`, `ImplementationSyntax` and `ImplementationHintSyntax` with `syntax-schema`. Existing `propose-ast` operations edit hints, wrapper and payload under `validation: "Authoring"`; preview with `read-proposal`, then explicitly apply. Unwrapping retains an attachment, while unwrapping pending metadata is refused. No tool confirms code or invokes AI. Other owners' wrappers and the lock/drift lifecycle remain deferred. See [AST edits](/screenplay/ast-authoring/#handler-intent-edits).

## Canonical executable model export

Call `read-workspace` with `view: "executable-model"` and the current
`expectedRevision`. When compilation succeeds, the response has `available: true`,
`schema` (`cratis.screenplay.esm`), `schemaVersion`, `languageVersion`,
`semanticVersion`, `modelRevision`, `attachmentManifestRevision`, `totalBytes`,
and `page` (`revision`, `totalBytes`, decoded-byte `offset`, `byteCount`,
`bytesBase64`, `nextOffset`). `limit` is decoded bytes, 1–196608 (default 49152)
for this view; other workspace views use 1–200 items (default 50).
`expectedModelRevision` applies only to `executable-model`; `expectedAttachmentManifestRevision` applies only to `executable-model` and `implementation-requirements`. Other views ignore these arguments, and `implementation-requirements` ignores `expectedModelRevision`.
Decode each `bytesBase64` page and concatenate in offset order until `nextOffset`
is null. For every subsequent page pass `expectedRevision`,
`expectedModelRevision` and `expectedAttachmentManifestRevision` from the first
response with `offset` set to the prior `nextOffset`. A changed revision refuses
continuation without bytes; restart at offset zero. The result is precisely the
canonical UTF-8 bytes from `SemanticModelSerializer.Serialize` and may be read
with its strict `Deserialize` reader. The 1 MiB structured-content response cap
still applies; the maximum byte page fits the cap.

If compilation failed, `available: false` points to `executable-diagnostics`
and contains no model bytes or last-good result. Successful compilation does not
promise any target can realize the model. The exported ESM alone is neither an
equivalence proof under [decision 0013](https://github.com/Cratis/Screenplay/blob/main/decisions/0013-equivalence-for-screenplay-code-round-trips.md)
(which also compares attachment hashes) nor a runnable attachment bundle: attachment
bodies are absent. The view is read-only, not a way to edit the ESM; use typed
workspace proposals for changes.

## Repair contract v1 and pinned evidence

`repair-capabilities` is read-only and takes an empty argument object. It needs
no open workspace. Its `structuredContent` identifies
`schema: "cratis.screenplay.mcp.repair-capabilities"`, `schemaVersion: 1`,
`repairContractVersion: 1` and the same assembly `serverVersion` as `initialize`.
The [narrow response schema](https://github.com/Cratis/Screenplay/blob/main/Documentation/screenplay/mcp/repair-capabilities-v1.schema.json) covers capabilities,
evidence metadata and the failure discriminator, not every MCP feature.

The contract advertises `PLAY0166`, `PLAY0478`, `PLAY0563`, `PLAY0564`, `PLAY0614`, `PLAY0653`, `PLAY0661`, `PLAY0683` and `PLAY0684` through `propose-repair`. `PLAY0661` completes missing command-read key parts only with unique same-named compatible sources; queries and trigger reads are not repaired. `PLAY0683`/`PLAY0684` raise NanoID size or bytes count to at least 64 random bits using the concept subject, including across documents. `PLAY0683` also offers a unique constraint using the command-property subject when exactly one production maps it to exactly one event property. Both generation repairs require individual review and canonical formatting and do not support pinned evidence. `PLAY0653` removes duplicate compliance markers from one concept header with `PreserveTrivia` and does not support pinned evidence. `PLAY0614` declares the translation direction (`inbound` or `outbound`) and is offered only when exactly one direction is consistent with the slice; it requires individual review and does not support pinned evidence. Guarded interaction repairs (`PLAY0563`/`PLAY0564`) require individual review and canonical formatting, refuse trailing-comment relocation, and do not support pinned evidence. The original `PLAY0166`/`PLAY0478` actions retain
`CanonicalizeTouchedDocuments` and optional evidence pinning v1. Existing
repairs outside this contract remain available to legacy clients. Feature support
comes from negotiation, not a CLI version or an ESM version. Initialize with MCP
`2025-06-18`, send `notifications/initialized`, check `tools/list`, then read the
capabilities. Consumers validate the fields they use, tolerate additive response
fields and refuse unsupported contract majors or malformed responses.

To opt in for either action:

1. Read `diagnostics` and `repairs` from `read-workspace`. Both return
   `repairEvidenceRevision`. Supply it as optional `expectedRepairEvidenceRevision`
   on further workspace reads so attachment drift refuses a page.
2. Send one selected `propose-repair` request with `pinRepairEvidence: true` and
   `expectedRepairEvidenceRevision`, alongside the existing revisions, subject and
   formatting consent. Both evidence fields are required together in this mode.
3. The retained response includes `repairEvidence: { beforeRevision,
   candidateRevision }`. These opaque, bounded `re1:` revisions cover source and
   catalog revisions plus the authoritative attachment loader's text inputs and
   loading/refusal diagnostics, including missing and unreadable states. They
   include inputs even when an unsuccessful or unsupported binding omits a compiled
   requirement. Unreferenced files and implementation locks are not evidence.
4. Preview every changed document and identity-state byte page, then explicitly
   apply. `read-proposal` returns the same evidence metadata. You may echo
   `beforeRevision` as `expectedRepairEvidenceRevision` on preview or apply;
   the server enforces the retained pin even when that optional field is omitted.

Pinned candidates never refresh readiness silently. Validation loads attachments
from the final candidate sources under the approved root, retaining those exact
inputs and loading diagnostics separately from the base snapshot. Unchanged missing,
unreadable, refused or oversized attachment warnings do not themselves invalidate
a pin; existing repair eligibility still applies. Fresh base and candidate loads
must match their validated evidence; otherwise retention returns
`RepairEvidenceDrift`, without creating a proposal or repeating the selected repair
transaction. Changed resolution or loading diagnostics are refused, not re-proven
by a refresh.

A pin also requires disjoint attachment inputs and planned writes. The server checks
all model-selected base and candidate references, including absent or unreadable
files, against source and parent-directory writes, identity state, the recovery
journal and the server-selected operation's staging, backup and rollback paths.
Rollback paths include every original document index, even unchanged documents;
the original sources also belong to recovery because rollback restores their
access settings. Overlap returns `RepairEvidenceWriteConflict`
before any accepted preview is retained. Existing hard links and filesystem-resolved
case aliases count as overlap; uncertain missing-path aliases fail closed with the
same kind. On Windows, plausible DOS 8.3 tilde aliases also fail closed when a
planned long-file creation or replacement, or parent-directory creation, could
generate the missing name and filesystem metadata cannot prove separation. This
can conservatively refuse a name that the volume would not actually generate; unrelated missing
names remain eligible. The server does not impose Windows short-name rules on
Unix filesystems. This is not a ban on `.play` attachments: a file outside the
recovery-owned source set remains eligible if otherwise disjoint. No attachment,
implementation lock or directory is written to establish disjointness.
Capabilities advertise `evidence.plannedWriteOverlap` and
`evidence.plannedWriteOverlapFailureKind` for this opt-in refusal.

All proposal-backed previews recheck both snapshots under the approved root, as
does the final pre-install check after staging, including write-overlap admission.
Pinned installation keeps the validated candidate; it does not refresh proof after
writing. Drift requires rediscovery and a
fresh selected proposal. You can discard a stale proposal without reading it.
Workspace/catalog revisions, exact `.play` set/bytes and identity-state preimages
remain independently authoritative. Workspace and ESM serialization are unchanged.
Legacy requests without either evidence field retain attachment refresh behavior.

Tool failures add `failureKind` while preserving `success`, `error`, `message`,
conflicts and recovery fields where already present. JSON-RPC errors preserve
`code` and `message` and add `error.data.failureKind`. Admission kinds include
`InvalidJson`, `InvalidRequest`, `UnknownMethod`, `InvalidArguments`,
`FormattingConsentRequired`, `UnknownRepair`, `UnsupportedRepair`, `UnknownProposal`,
`RootChangeRefused`, `StaleRevision`, `DiskDrift`, `IdentityStateDrift`,
`RepairEvidenceDrift`, `RepairEvidenceWriteConflict` and `LimitExceeded`. `ProposalRejected` retains its detailed
conflicts; `PendingOperation` requires workspace-state inspection. Apply failures
report `ApplyRolledBack`, a specific refusal kind, or `RecoveryRequired` as
appropriate. A failed explicit `recover-workspace` also returns
`failureKind: "RecoveryRequired"` with its existing status, conflict and retained
recovery instructions. `RequestFailed` and an unfamiliar kind must not be
interpreted as proof that an apply made no changes. Never parse message prefixes.

Cancellation notifications are ignored (`cancellation.supported: false`). EOF,
process failure or `ApplyOutcomeUnknown` after dispatch leaves the outcome unknown;
never retry apply automatically. Reconnect, inspect `workspace-state` and require
separate approval for recovery. Journaled apply assumes an exclusively owned root;
the pre-install evidence check is not a kernel-atomic lock across files or a
crash-atomic multi-file guarantee. Nondestructive tool annotations do not authorize
writes or substitute for user-approved roots and explicit Apply.

## Diagnostic repair workflow

Page `read-workspace` with `view: "repairs"` and the current `expectedRevision`.
Each item includes `diagnosticCode`, diagnostic `location`, a revision-bound
`subject` handle, a typed operation summary, `requiredFormatting`, optional `title`,
`canFixAll` and `retiredSemanticAddresses`. A contract-changing `PLAY0469` repair
has `canFixAll: false` and lists the payload property's retirement. Supply the code and subject to
`propose-repair` with both current revisions and explicit
`formatting: "CanonicalizeTouchedDocuments"`. The server regenerates the repair
from the current compiler diagnostics, never from a message string or supplied
text edit. The `validate csharp` repair is an identity replacement: canonical
printing performs the migration by reprinting the **whole touched document**.
Whitespace and other legacy forms in the file can also change. A repair that
would drop a comment anywhere in that document is refused with a typed
`RepairWouldDropComments` conflict. Missing formatting consent fails with
`FormattingConsentRequired`; `PreserveTrivia` cannot perform the migration.
Unknown, ambiguous and unsupported repairs fail closed; stale workspace/catalog
revisions return typed conflicts. A successful response retains the same proposal
as `propose-ast`: use `read-proposal` to inspect exact before/after bytes,
then call `apply` explicitly. Neither discovery nor
proposal writes. Available repairs are `PLAY0166` (infer an undeclared produced
event), `PLAY0478` (change routing from an allocated destination to the command's
identifier, with an explicit routing-change title and `canFixAll: false`), `PLAY0469`
(remove an inline identifier payload copy), `PLAY0471` (remove a redundant event
pin), and `PLAY0397` (`validate csharp` migration). See the
[repair conditions](/screenplay/mcp/authoring-tools/#fix-a-diagnostic) before choosing one.
Discovery verifies acceptance and comment preservation for the first four, with
their routing, consumer or model-preservation checks. `PLAY0397` discovery identifies
the recipe only; its proposal may still be refused. `PLAY0470` remains deferred,
and repairs requiring a choice are not offered.

## Editing and validation

`propose-ast` creates, replaces, removes or moves typed nodes/documents in one
atomic proposal. Disjoint edits in one file compose before validation. Typed
whole-document replacement can repair parser-invalid source without node handles.

### Logical parent moves

`propose-move` accepts `target` and `newParent` as `{kind, parts}` semantic addresses,
using the same typed parts returned by the identity inventories. Copy the complete
address, including its application identity. Optional `targetHandle` and
`newParentHandle` must resolve to the same logical declarations. Both expected
revisions are required. Formatting defaults to `PreserveTrivia`, validation to
`Authoring`; formatting consent and executable validation work as for rename.

The first release moves a Slice to a Feature or a Feature to a Module or Feature.
Moving modules or declarations between slices is not offered. Every assigned
subtree declaration gets an automatic semantic migration and every affected event
contract an event migration. Event names and identity pins do not change. Qualified
typed references, including container `depends on` references and interaction
`navigate to`, `execute` and `refresh` operands, are repaired. Named-behavior
parameter operands stay unchanged; their declaration-valued `uses` arguments are
bound and repaired at the attachment's scope. Arguments used for multiple target
domains refuse with the behavior and parameter named.

No-op moves, collisions, capture or ambiguous references, descendant destinations,
disallowed parent kinds and any identity loss refuse with structured conflicts.
Inherited `authorize` and screen `on`/`uses` bindings must not change. Interaction
continuity compares resolved targets as well as attachment text, so identical
attachments that resolve to different screens, commands or queries refuse. When the
original model is executable, the candidate executable model is compared by
preserved identity, allowing only container ownership changes; differences refuse.
Authoring-only source still has inherited binding and reference continuity checked.

Split subtrees move all fragments together. Inline and restated-header fragments
remain in their documents. Restated headers outside the file's import-placement
scope remain authored headers; only the actual placement scope is header-less.
Literal placing imports that directly own the moved fragment move to one authored destination
(or a single own-named file for a split destination); placing globs and ambiguous
split destinations refuse. An empty metadata-free wrapper fragment can be removed,
but the old logical parent remains. Files are never relocated; `expand-layout` is
a separate operation.

The proposal response and every `read-proposal` response include `moveReport`:
`identityMigrations` (domain, unchanged id, previousAddress and currentAddress),
`retired: []`, `referenceRepairs` and `fragmentsMoved`. Review the usual source,
dropped-comment and semantic-diff views before the existing `apply` tool.

`propose-rename` coordinates logical declarations, fragments, supported typed
references and assigned identities. Event sources, streams, examples and specifications are supported rename targets. Source rename migrates its catalog entry and owned streams atomically; existing pins stay unchanged, while unpinned renames change stored names. Pin before renaming a declaration with stored events; underlying event, command and read-model renames update example type references. Examples have no ESM identities. It preserves trivia by default. Ambiguity,
name capture, opaque text naming the old or new name, unsupported spans or resolver
disagreement refuse automation. It is not global text replacement or automatic property-schema evolution.
Typed references include refusal-branch constraints and produced events, plus the event and reaction named by `when redelivered`. Reaction rename proposals update those references. Safe rename refuses constraints: their names are executable identity, so a rename starts an empty constraint index and changes default rejection messages. Use explicit coordinated typed edits when that contract change is intended. Refusal branches and `$refusal` values bind and execute from ESM v19; `when redelivered` and `then no events` bind and execute from ESM v17. Their indexed roles are `refusalConstraint`, `refusalProduces`, `whenRedeliveredEvent` and `redeliveryReaction`; redelivery dependencies are test-only `verifiedWith` edges, not timeline-order dependencies.
Source and stream renames repair bound command and specification routes without changing identity-catalog entries. Existing pins stay untouched, and no pins are added automatically; add `id` with a typed edit when an external stored name matters. `eventNeverPersisted` has no effect on these renames.
Event renames retain an existing `id` pin or insert the previous name by default.
`eventNeverPersisted: true` omits a new pin and removes a redundant pin equal to the current name; a pin naming an earlier identity is kept. A rename
that inserts a pin also refuses comment loss or duplication.

Event pins that differ from the display name select ESM v16 and carry the effective stored name as `storedName`; equal-name pins keep the earlier version and bytes. Consumers must explicitly admit that version or refuse the model. Proposal and `read-proposal` responses retain the compatibility `eventPinWarnings` array, now empty because `PLAY0686` is retired. Invalid pins, generation disagreements and stored-name collisions are binding errors; review those diagnostics before applying. Source and stream pins are unaffected.

`propose-extract-inline-event` moves a declaration into its owning slice and makes
an implicit destination explicit. It requires canonical formatting consent,
byte-identical canonical ESM, unchanged catalog assignments and every comment
preserved exactly once.

Two independent choices control AST authoring:

- **Validation:** `Authoring` requires valid full-language source and identity
  continuity. `Executable` also requires backend binding; strict `propose` stays
  executable-only.
- **Reference policy:** `Safe` is the default. It rejects new unresolved/ambiguous
  model references and unintended capture. `Draft` reports deliberate unresolved
  debt but does not waive structure, identities or existing-binding protection.

Identity migration refusals use conflict kind `InvalidIdentityMigration` and return
`identityMigrationIssues` on rejected `propose`, `propose-ast` and `propose-source`
results. Each item has `arguments` (input array names) and `address` with only `kind`
and typed `parts`, matching the MCP address schema. Stale renames identify both
endpoints in `semanticRenames` or `eventRenames`; invalid retirements identify
`retiredSemanticAddresses` or `retiredEventAddresses`. Missing continuity lists
rename and retirement as alternatives, not instructions to retire an identity.
Events have both semantic and event-contract assignments, so both must be addressed,
as must all assigned descendants. The conflict message contains the same details.

Source acceptance is not executable readiness or proof of business correctness.
Explicit valid reference edits differ from an untouched reference changing meaning.

Formatting policies are `PreserveExactSource`, `PreserveTrivia`, and
`CanonicalizeTouchedDocuments`. For `propose-source`, `PreserveExactSource` writes
supplied text verbatim after final-placement parse, intended-AST equality, merged
compilation and the usual identity, reference, source-transition and release-baseline
checks. Comments, alignment, blank lines, CRLF and a missing final newline are kept;
replacement BOM policy comes from the original, creation BOM policy from `encoding`.
It emits no `PLAY0288`. For example, replacing `application.play` with source that
adds `import "Notifications/Notifications.play"`, alongside creation of that file,
does not reformat the supplied surrounding text. `propose-ast` has no source witness
and exact mode still refuses edits requiring printing.

Verified `PreserveTrivia` patches cover identifiers, literal
values, whole property mappings and event-pin insertion/removal, and must reparse
to the intended AST; unsupported patches reject rather than silently canonicalizing.
Structural `propose-source` changes in trivia mode are refused with guidance to
use `PreserveExactSource`. `CanonicalizeTouchedDocuments` reprints canonically,
retains attached comments and parsed member order, normalizes
whitespace, and reports any unplaceable comments in `droppedCommentCount`.
Repairs, extraction and pin-inserting event renames refuse comment loss or
duplication; other explicitly canonicalized edits may disclose dropped comments.
Untouched bytes and BOM policy are retained. Printer omissions reject the proposal.

See [the AST API](/screenplay/ast-authoring/) and [authoring procedure](/screenplay/mcp/authoring-tools/).

## Semantic proposal difference

Both semantic-diff views use the structural implementation behind
[`ModelComparison`](/screenplay/model-comparison/) in `Cratis.Screenplay`.
The library offers typed, unpaged differences and explicit Identity or Address
matching; MCP retains its existing JSON shape, revision checks and paging.

`read-proposal` with `view: "semantic-diff"` compares the retained disk baseline
with the proposal, without applying it. It works without MCP Apps and does not
execute specifications. Changes to `.play` files or the retained
`.screenplay/identities.json` bytes since proposal creation refuse this view,
including revision-pinned continuation pages. Pending recovery also refuses
review; inspect `workspace-state` and explicitly recover the identified operation.
Create and review a fresh proposal after baseline drift rather than combining
different baselines.

The `result` contains:

| Field | Meaning |
| --- | --- |
| `sourceRevision` | The proposal workspace revision, including its identity catalog. |
| `beforeRevision` | The retained baseline workspace revision. |
| `afterRevision` | The candidate workspace revision (the same as `sourceRevision` for proposal review). |
| `comparisonLevel` | `authoring-structure`: normalized typed members, not source lines or an execution/equivalence verdict. |
| `executableBeforeAvailable`, `executableAfterAvailable` | Whether each snapshot binds executably; structural review does not require binding. |
| `complete` | Whether every comparison section is complete. |
| `hasSemanticChange` | `true` for a known structural change (including owner moves and opaque-content changes), `false` for a complete comparison with only document moves or no changes, or `null` when incomplete data cannot establish no change. |
| `sections` | Each section's `complete` flag and `unavailable` reasons. An empty incomplete section never means no change. |
| `limits` | Comparison exclusions and fallback rules. |
| `page` | `revision`, `totalCount`, `offset`, `items`, `nextOffset`. |

Items are ordered by section, semantic ID, kind, addresses, change kind, member,
dependency snapshot/address/role and generation. They share `section`, `changeKind`,
`semanticId`, `kind`, `beforeAddress` and `afterAddress`. Other fields are nullable
and apply only to the corresponding record.

`kind` uses one vocabulary across assigned declarations, authoring fallback,
event contracts, identities and dependant records. Allowed values are:
`Application`, `Capture`, `Command`, `Concept`, `Constraint`, `ContributionPoint`,
`DialogTemplate`, `Event`, `EventSource`, `EventStream`, `Feature`, `Form`,
`Layout`, `Module`, `Operation`, `Persona`, `Policy`, `Projection`, `Property`,
`Query`, `QueryArgument`, `Reaction`, `ReadModel`, `Reducer`, `Screen`,
`ScreenTemplate`, `Slice`, `Specification`, `System`, `Theme`, `Trigger`, `Type`,
and `UiProfile`. Event contracts use `Event`, and composite types use `Type`;
`eventContractId` distinguishes event-contract identity records. A property's
aggregated dependants retain `kind: "Property"`, not the owner's kind.

- **`declarations`**: `added`, `removed`, `renamed`, or `moved`. Catalog semantic
  IDs match declarations across snapshots. Preserved IDs with changed names
  report renames; changed owner addresses or documents report moves.
  `moveKind: "owner"` identifies logical address changes, with `beforeOwner` and
  `afterOwner`; these are semantic changes because inherited authorization and
  reference resolution can change. `moveKind: "document"` identifies layout-only
  moves. `beforeDocuments` and `afterDocuments` are arrays of `{ documentId, path }`
  locations. Only document-only moves receive the no-semantic-change treatment.
  Adding generation qualification to a preserved property's catalog address is
  reported as an identity `migrated` record, not a logical owner move.
- **`events`**: `property-added`, `property-removed`, `property-type-changed`,
  `generation-added`, or `generation-removed`. `member`, `beforeType` and `afterType` describe the
  field (types are canonical typed JSON strings). `contractBreaking` is a
  conservative stored-contract risk flag, including additions.
  `generationCovered` is true only when an explicitly declared newer generation
  retains the previous generation's property names and types unchanged.
  `beforeGeneration` and `afterGeneration` identify the compared generations.
  Every existing generation is compared with the same generation in the proposal,
  and every introduced generation with its immediate declared predecessor. This
  preserves intermediate additions and removals even when the final shape matches
  the original. Generation coverage is reported per transition and is not runtime
  migration proof.
- **`members`**: changed typed members of commands, read models, projections,
  queries, reactions, captures, triggers (including trigger data properties), and
  other identity-bearing declarations. Application, module, feature and slice
  comparisons include their own members (such as domain, authentication,
  authorization and slice kind), but exclude separately indexed child declarations
  and physical import-placement metadata. Split hierarchy fragments use the
  source index's merged meaning; unavailable merges remain incomplete.
  `member`, `beforeHash` and `afterHash` locate a structural difference without
  copying a whole subtree. Event member keys include their generation.
  `opaque-changed` records retain changes to inline code or file references by
  hash without interpreting their behavior. Description and documentation prose
  are excluded from structural comparison.
  Constraints have no catalog semantic kind; their fallback uses exact authoring
  kind/address keys with `semanticId: null`, not an invented identity.
- **`specifications`**: additions, removals and `expected-outcome-changed` records
  for changed authored `then*` members, with member names and hashes. These are
  static expected assertions, not inferred or executed outcomes. Other fixture
  changes appear under `members`.
- **`dependants`**: `direct` indexed references in the `before` or `after`
  `snapshot`, with `dependantAddress`, `role` and `resolution`. Properties use
  their owner's references; containers aggregate external direct references to
  contained declarations, excluding references originating inside that same
  container. Unresolved or ambiguous indexes mark this section incomplete.
  These are not transitive dependencies or runtime impact guarantees.
- **`identities`**: `assigned`, `retired` and `migrated` catalog identities,
  including `eventContractId` for event-contract identities. Migrations are
  inferred from preserved IDs and their changed addresses, not name similarity.

Use item `offset` (default `0`) and `limit` (default `50`, range `1`–`200`).
Pages also have a 192 KiB serialized-item budget; always continue from
`nextOffset`, which may advance by less than `limit`. A single oversized record
refuses with `LimitExceeded`, without truncation. Echo `sourceRevision` as
`expectedSourceRevision` on every continuation; missing pins or stale pins refuse.

When binding fails, the view still compares available authored members and
preserved catalog IDs. Every assigned semantic ID, of any kind, must have unique
comparable authored members or be counted in its section's unavailable reasons.
Every indexed kind/address group is likewise compared or counted as incomplete;
unassigned multi-generation events retain all their generations rather than
being discarded. Unassigned declarations use exact kind/address fallback keys
and explicitly incomplete identity/rename sections. Missing source owners,
implicit shapes, ambiguous declarations and unavailable hierarchy merges never
establish no change.

Inline opaque content and file references are compared by hash. The view does
not analyze behavior inside code, load or compare external attachment file
contents (inspect `implementation-requirements` hashes separately), execute
specifications, prove runtime or transitive impact, or compare two arbitrary
revisions.

## Semantic revision difference

`semantic-diff` compares two model sources without replacing the active workspace,
clearing proposals or writing source or identities. Each of `before` and `after`
must select exactly one source form:

| Form | Source |
| --- | --- |
| `{ "workspace": "active" }` | The open workspace at its current revision, with its identities; refused if none is open |
| `{ "path": "../main/Screenplay" }` | A read-only model folder or worktree; relative paths use the server working directory |
| `{ "workspaceJson": "<canonical export JSON>" }` | Decoded, reassembled UTF-8 JSON from `export-workspace`, not raw `.play` text |

For example, compare the active model against a worktree model folder:

```json
{
  "before": { "workspace": "active" },
  "after": { "path": "../main/Screenplay" },
  "limit": 50
}
```

An export can replace either side. This illustrative argument shape assumes
`<canonical export JSON>` is replaced with the complete export string:

```json
{
  "before": { "path": "Screenplay" },
  "after": { "workspaceJson": "<canonical export JSON>" }
}
```

The legacy `beforeWorkspaceJson` and `afterWorkspaceJson` strings still work,
including alongside the other side's source object. Supplying both a source
object and its matching legacy string, no source for a side, an empty or
multi-form object, or a workspace value other than `active` is invalid.

Path admission matches `open-workspace`: a fixed-root server admits its root or
the corresponding model folder in a registered Git worktree; a dynamic server
admits folders under the normal root checks. Paths restore existing
`.screenplay/identities.json` without bootstrapping metadata. With no identity
state, declarations match by address; a missing root reads as an empty model
without creating a directory. A pending recovery journal refuses a path source;
active sources also respect pending or competing journals. Git refs are not
resolved: compare a committed revision through its worktree path instead.

Declarations match by Identity when both sources have persisted identities;
otherwise they match by exact kind and address. Exports (including legacy string
arguments) carry identities. Paths have persisted identities only when
`.screenplay/identities.json` exists; the active workspace uses the persisted
state loaded for its bound root. In Address mode, `limits` adds: "Declarations are
matched by exact kind and address because at least one side has no persisted
identities; renames and owner moves appear as a removal and an addition."

Only Identity mode requires the same application identity; different application
identities return `IncompatibleRevisions`. Preserve authoritative catalogs:
separately generated IDs do not establish rename continuity. The tool never
guesses a rename from similar names or edits a catalog to make it match.
Malformed, noncanonical, incomplete, duplicated or content-revision-mismatched
exports return a tool error with `failureKind: "UnreadableRevision"` naming the
unreadable input. Structurally ambiguous declarations in a readable snapshot
retain the proposal comparison's explicit incomplete sections.

The result reuses the [semantic proposal difference](#semantic-proposal-difference)
fields, sections, ordering, completeness rules and exclusions. `beforeRevision`
and `afterRevision` identify the two validated workspace revisions, including
catalogs and exact sources. `sourceRevision` and `page.revision` instead identify
the ordered pair with a SHA-256 comparison token. Send the same source selections
and echo that token as `expectedSourceRevision` on every continuation. Both sides
reload on every page: an active-workspace revision change, path disk change or
changed export rejects continuation with `StaleRevision`. Missing continuation
pins are invalid arguments. No retained proposal or MCP Apps support is required.

Pages use `offset`, `limit` (default `50`, range `1`–`200`) and the same 192 KiB
serialized-item budget; follow `nextOffset` rather than assuming a full count
page. Inline exports must fit the ordinary request envelope together; every
source is subject to the existing workspace/source admission bounds. This tool does not
resolve Git refs directly or provide a chunked snapshot-upload protocol.

As with proposal review, this is not an equivalence or execution verdict. Opaque
inline content and file references are compared by hash, but code behavior and
external file contents are not analyzed. Specification expectations are static,
not executed; dependants are direct indexed references, not transitive or runtime
impact. Unassigned declarations use exact kind/address fallback keys, never
identity-preserving rename claims. These exclusions are returned in `limits`;
only the proposal-specific exclusion of revision-to-revision comparison is absent.

## Review, durable state and recovery

Open/proposal/apply responses are compact. `read-proposal` exposes paged changed
paths/identities, exact before/after bytes, authoring diagnostics, executable
diagnostics and `dropped-comments`, every comment a changed document loses. `workspace-state` also exposes proposed identity-state bytes.

Apply accepts only a retained server proposal and checks revisions, source
preimages, identity-state preimages and destination ownership. Stable assignments
persist automatically in `.screenplay/identities.json`; keep it with the model.
Canonical `export-workspace` is optional for portable transfer or backup; ordinary
restart does not require manual export to retain identities. Initialize instructions
likewise distinguish `read-proposal` review and source acceptance from executable
readiness before `apply`.

A durable pending journal precedes source mutation. Interrupted or uncertain
operations block normal editing; inspect status and explicitly request rollback.
Recovery refuses unexpected external bytes and retains uncertain backups. This
is not simultaneous crash-atomic visibility across files. See
[identity state and recovery](/screenplay/mcp/recovery/).

## Protocol and limits

MCP `2025-06-18`, UTF-8 newline-delimited JSON-RPC over stdio. Standard output is
protocol-only. Initialize, then send `notifications/initialized`. Supported
methods: initialize, ping, tools/list and tools/call. Requests are sequential.
HTTP, cancellation, progress, resources, prompts and server-initiated requests
are not implemented. No tool follows external realization files or executes code.

| Resource | Limit |
| --- | --- |
| Request / JSON depth | 33,554,432 characters / 256 levels |
| Pure structured result | 1 MiB UTF-8; effect/recovery verdicts are never hidden by this limit |
| Source population | 512 files; 8 MiB combined; 2 MiB per file |
| Source lines | 20,000/file; 8,192 characters/line; 128 leading whitespace characters |
| Traversal | 32,768 entries; 16 directory levels |
| Reopening envelope | 16 MiB UTF-8, also bounded by encoded request size |
| Operations/migrations | 256 entries per array |
| Page | 200 items or 192 KiB decoded bytes |
| Outstanding proposals | 16; discard or reopen to clear |

Limits reject, never truncate silently. Discovery excludes `.git`, `.ai-work`,
`bin`, `obj`, `node_modules` and reserved `.screenplay` state. Model writes use
portable relative `.play` paths and strict UTF-8. The root must be trusted and
exclusively writable during apply/recovery.
