MCP reference
This page is the reference for the server’s tools. To use the server through an assistant, start with the MCP server overview, and see Create a model, Explore a model, View a model and Edit a model for prompts.
Key and route admission
Section titled “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.
Installation and scope
Section titled “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):
docker run -i --rm -v "$PWD/specifications:/model" cratis/screenplay mcp /modelor, with the tool installed through dotnet tool install --global Cratis.Screenplay.Tool:
screenplay mcp ./specificationsSee installation and client configuration. 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.
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
Section titled “Processing purposes”Use kind: "Purpose" to read a declared processing purpose. 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
Section titled “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.
Persona specification callers
Section titled “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.
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
Section titled “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
Section titled “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 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
Section titled “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 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.
Embedding API
Section titled “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
Section titled “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), handlers, exact numbers (#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
Section titled “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
Section titled “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
Section titled “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 for exact rules and exemptions. No executable admission or code analysis is implied.
Scoped diagnostics
Section titled “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
Section titled “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 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
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
for prompts.
Event model board
Section titled “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.
Paging and snapshots
Section titled “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
Section titled “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 for the same evaluator from the command line.
Release baseline
Section titled “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; 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
Section titled “Model notes”Notes live outside source and executable revisions in the committed
model notes sidecar. 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. Invalid operation shapes/targets are invalid
arguments or failed requests, never successful partial batches.
Workspace tools
Section titled “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.
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), 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
Section titled “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
Section titled “Handler intent inventory”Command named-rule intent views
Section titled “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
Section titled “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.
Canonical executable model export
Section titled “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
(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
Section titled “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 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:
- Read
diagnosticsandrepairsfromread-workspace. Both returnrepairEvidenceRevision. Supply it as optionalexpectedRepairEvidenceRevisionon further workspace reads so attachment drift refuses a page. - Send one selected
propose-repairrequest withpinRepairEvidence: trueandexpectedRepairEvidenceRevision, alongside the existing revisions, subject and formatting consent. Both evidence fields are required together in this mode. - The retained response includes
repairEvidence: { beforeRevision, candidateRevision }. These opaque, boundedre1: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. - Preview every changed document and identity-state byte page, then explicitly
apply.
read-proposalreturns the same evidence metadata. You may echobeforeRevisionasexpectedRepairEvidenceRevisionon 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
Section titled “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 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
Section titled “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
Section titled “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:
Authoringrequires valid full-language source and identity continuity.Executablealso requires backend binding; strictproposestays executable-only. - Reference policy:
Safeis the default. It rejects new unresolved/ambiguous model references and unintended capture.Draftreports 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 and authoring procedure.
Semantic proposal difference
Section titled “Semantic proposal difference”Both semantic-diff views use the structural implementation behind
ModelComparison 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, ormoved. 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, withbeforeOwnerandafterOwner; these are semantic changes because inherited authorization and reference resolution can change.moveKind: "document"identifies layout-only moves.beforeDocumentsandafterDocumentsare 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 identitymigratedrecord, not a logical owner move.events:property-added,property-removed,property-type-changed,generation-added, orgeneration-removed.member,beforeTypeandafterTypedescribe the field (types are canonical typed JSON strings).contractBreakingis a conservative stored-contract risk flag, including additions.generationCoveredis true only when an explicitly declared newer generation retains the previous generation’s property names and types unchanged.beforeGenerationandafterGenerationidentify 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,beforeHashandafterHashlocate a structural difference without copying a whole subtree. Event member keys include their generation.opaque-changedrecords 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 withsemanticId: null, not an invented identity.specifications: additions, removals andexpected-outcome-changedrecords for changed authoredthen*members, with member names and hashes. These are static expected assertions, not inferred or executed outcomes. Other fixture changes appear undermembers.dependants:directindexed references in thebeforeoraftersnapshot, withdependantAddress,roleandresolution. 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,retiredandmigratedcatalog identities, includingeventContractIdfor 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
Section titled “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:
{ "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:
{ "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
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
Section titled “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.
Protocol and limits
Section titled “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.