Skip to content

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.

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.

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):

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

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

Terminal window
screenplay mcp ./specifications

See 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.

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.

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

FieldContract
strategyAuthored strategy name.
modifiersOnly the authored prefix, case, size, alphabet, count and encoding values; implicit defaults are not added.
entropyBitsExact 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.
entropyUnknownReasonPresent only when entropy is unknown.
valuesAtOnePercentCollisionJSON 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.
executablestatus 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.
previewArgumentsAuthored 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:

DiagnosticTitleEligibility and subject
PLAY0683, PLAY0684Raise generation to at least 64 random bitsNanoID: 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.
PLAY0688Replace the refuted rule with derived matchesSubject 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.
PLAY0683Add a unique constraint for the generated propertyExactly 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.

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.

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.

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.

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.

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.

ContractBehavior
RootExisting physical application directory; the same scope and limits as the standalone command
StreamsCaller-owned; never disposed by the server; the caller selects UTF-8 encoding for stdio
OutputJSON-RPC responses only, flushed after each response; no console logging or encoding changes
FailuresStartup, transport and request-size failures propagate; request errors remain protocol responses
EffectsNo 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.

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.

ToolSelectionResult
describe-applicationview: summary, children or declarations; optional parent, scope/kind/document filtersCompact counts or paged logical navigation
find-declarationRequired exact name; optional kind/scope/documentPaged matches; typed syntax only with includeContent: true
search-declarationsOptional name, match: exact/prefix/contains, kind/scope/documentCompact scoped search
declaration-detailsaddress, kind; optional viewSummary (including description and documentation wherever supported) or paged properties, occurrences, commands, specifications, produces, enum values, module/feature dependencies; explicit syntax view
processing-recordOptional controllerName, controllerContact, offset, limit, expectedSourceRevisionPaged declaration-derived controller inventory; not legal advice
find-referencesaddress, kind (including Property)Paged resolved incoming references, ambiguities and incomplete property links, with owners/roles
dependenciesaddress, kind (including Property), direction incoming/outgoing; optional descendants/documentDirect indexed dependencies and resolution candidates; property outgoing follows mapping counterparts
dependency-graphOptional view, from/to levels, scope, direction, kinds, includeTestOnly, evidenceLimitInferred slice/container/context edges, ordering cycles, story-order suggestions, unresolved references or checked dependency declarations
find-fixturesSpecification address, role, property, value, scope/documentPaged effective assignments with type, value, location and authored/example/override origin, including when append event payloads (whenAppendedEvent) and for destinations (whenAppendedEventDestination)
find-assertion-gapsOptional scope/documentSlices without specifications declaring a then assertion, including then denied
run-specificationsOptional proposalId, exact specification address or semantic id, scope, offset, limit, expectedSourceRevisionDeterministic reference execution with passed, failed and unsupported results
find-specification-obligationsOptional module/feature/slice scope, documentPer-declaration specification obligations with met/unmet status and matching specification owners
find-modeling-smellsOptional scope/document, eventFanOutThreshold/propertyFanInThresholdInformation-level modeling questions; no compiler diagnostics
diagnosticsOptional checks (comma-separated names, codes, or all), scope, documentPaged diagnostics, severity counts, scoped declaration counts and affected scopes
read-documentRequired relative pathExact original UTF-8 byte pages
merged-documentview: source, syntax or bothCanonical merged byte pages or explicitly requested typed AST
recommend-layoutNoneSize-admissible layout choices and recommendation
syntax-schemaOptional concrete kindKind list or exact typed JSON schema
semantic-diffEach of before/after selects active workspace, path or export; legacy beforeWorkspaceJson/afterWorkspaceJson supported; optional offset/limit/expectedSourceRevisionPaged 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.

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 IDObligation and matching evidence
SPEC001Command success: a resolved when command, an outcome assertion, no error or denial
SPEC002Each require: a resolved command action and exact explicit rejection message
SPEC003Each command or concept validation rule: exact explicit rejection message; concept rules use commands accepting that concept
SPEC004Authorized command/query (including module/feature gates): its resolved action and then denied
SPEC005Each value-unique constraint: prior claim event, equal literal composite key through production mappings, different known destinations, and rejection
SPEC006Read model: state/absence assertion or results asserted through a resolved query returning that view
SPEC007Each 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
SPEC008Reaction: 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.

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 IDReview question prompted by
SMELL001Event name ending in Updated, Changed, Edited, Saved, Modified, Deleted, Synced or Received
SMELL002Command name starting with the PascalCase word Update, Edit, Save, Set, Manage, Get, Load or Fetch (not longer words such as Settle)
SMELL003Nonempty event property set identical to another event produced by a different command
SMELL004Command producing more distinct resolved events than eventFanOutThreshold
SMELL005Read-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.

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.

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 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.

ArgumentTypeDefaultValues / limits
viewStringedgesedges, cycles, order, unresolved, declarations
fromStringmoduleslice, feature, module
toStringmoduleslice, feature, module, context
scopeStringWhole applicationExact, unambiguous module, feature, slice or context address; includes descendants
directionStringoutgoingoutgoing filters consuming nodes; incoming filters producing nodes in the edges view
kindsString arrayAll kinds except test-only referencesAny subset of the kinds below
includeTestOnlyBooleanfalseAllows specification references, including imported specification facts
evidenceLimitInteger30–20 references per edge; counts remain complete
limitInteger501–200 items
offsetInteger0Page offset; continuation requires expectedSourceRevision
expectedSourceRevisionStringNoneThe 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.

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.

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.

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:

ArgumentTypeMeaning
proposalIdstringOutstanding proposal from this connection; omitted to execute the current model
specificationstringExact, case-sensitive dotted module/feature/slice/specification address, or semantic id in the selected model
scopestringCase-sensitive dotted module, feature or slice prefix; selects descendant specifications
offsetintegerResult offset, default 0
limitintegerPage size, default 50, range 1–200
expectedSourceRevisionstringPin 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.

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:

MemberContract
statusabsent, valid or unreadable; unreadability never blocks opening or reading
revisionbrev1: canonical hash; brev1:absent without a file, null when unreadable
reasonUnreadable reason, otherwise null
entriesAdmitted revision count, null when unreadable
violationsChanged or missing revision count; null when the current model cannot bind
checkFailureCheck 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}:

BasisProperty editsEvent rename
noBaselineExactly one of newGeneration or eventNeverPersistedPins by default
notInBaselineNo flag defaults to inPlace; either explicit flag is honoredDoes not add a pin
baselinedOnly explicit newGeneration; otherwise BaselinedRevisionImmutablePreserves 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.

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:

ArgumentValues and default
anchor{ semanticId } (including an orphan’s ID) or { address, kind } with a dotted declaration address
scopeOptional dotted module/feature/slice; descendants and ancestor notes
kindquestion, task, note; omitted selects all kinds
stateopen (default), resolved, orphaned, all
refExact reference identifier in refs or resolution.ref
viewitems (default) or findings
offset, limitItem pages; at most 200 items, also bounded by serialized bytes
expectedNotesRevisionOptional 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:

OperationFields
addRequired kind, text, author, and exactly one anchor or target; optional refs
editRequired id; optional kind, text, refs (replaces the whole refs list)
resolveRequired id and resolution: by, text, author; optional semanticId, ref. specification/description require semanticId; external-decision requires ref
reopenRequired id; clears resolution
reanchorRequired id and exactly one anchor or target
removeRequired 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.

ToolRequired argumentsOptional arguments
repair-capabilitiesNoneNone
open-workspaceNoneapplicationName, path, workspaceJson, includeContent
read-workspaceexpectedRevisionview (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-astexpectedRevisiondocumentId, path, kind, name, semanticId, view, includeContent, offset, limit
proposeExpected workspace/catalog revisions, operationsExplicit migrations/retirements, includeContent; legacy single-operation form supported
propose-astExpected revisions, formattingoperations, documents, validation, referencePolicy, migrations/retirements, includeContent
propose-sourceexpectedRevision, expectedCatalogRevision, formatting, documentsvalidation (Authoring default or Executable), referencePolicy (Safe default or Draft), semanticRenames, eventRenames, retiredSemanticAddresses, retiredEventAddresses, includeContent
propose-repairexpectedRevision, expectedCatalogRevision, diagnosticCode, subject handle, formattingline 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-renameExpected revisions, target handle, expectedName, newNameformatting, validation, includeContent, eventNeverPersisted (boolean, default false)
propose-moveExpected revisions, target address, newParent addresstargetHandle, newParentHandle, formatting, validation, includeContent
propose-propertyexpectedRevision, expectedCatalogRevision, action; rename/remove: target, expectedName (rename also newName); add: owner, expectedOwnerName, name, typealsoRename or alsoRemove; add: optional, hops, fixtureValue; newGeneration, eventNeverPersisted, formatting (PreserveTrivia default), validation (Authoring default), includeContent
propose-number-enum-membersexpectedRevision, expectedCatalogRevision, unnumbered enum subject handle, formattingvalidation, includeContent; only CanonicalizeTouchedDocuments is admitted; never renumbers numbered or mixed enums
propose-extract-inline-eventexpectedRevision, expectedCatalogRevision, inline event subject handle, formattingvalidation, includeContent; only CanonicalizeTouchedDocuments is admitted
expand-layoutExpected revisionslayout (single, module, feature, slice; default slice, one file per slice), validation, formatting, referencePolicy, includeContent
read-proposalproposalIdexpectedRepairEvidenceRevision, 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-workspaceexpectedRevisionproposalId, offset, limit
workspace-stateNoneview, proposalId, expectedStateRevision, offset, limit
list-release-baselineNoneoffset, limit, expectedReleaseBaselineRevision
list-model-notesNoneanchor, scope, kind, state, ref, view, offset, limit, expectedNotesRevision
propose-model-notesexpectedRevision, expectedCatalogRevision, expectedNotesRevision, operationsNone
discard-proposalproposalIdNone
applyproposalId, expectedRevision, expectedCatalogRevisionincludeContent, expectedRepairEvidenceRevision, expectedReleaseBaselineRevision
recover-workspaceoperationIdNone

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.

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:

ParameterType and meaning
ownerRevision-bound read-ast handle of the command, current event or read-model declaration
expectedOwnerNameExact original declaration name
nameNew unqualified property name; existing names refuse
typeAuthored type text, such as String or a concept name; does not create a concept
optionalBoolean, default false; use this flag rather than an optional suffix in type
hopsArray, default empty; each entry is {at, name} at another declaration or {at, value} at a produces, from or capture append block
fixtureValueOptional 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:

MemberContract
action, targetRequested action; original property address for rename/remove or candidate property address for add, owner kind and optional generation
changedOriginal address, owner kind, indexed role, change, source location, weakensAssertion
notChangedOff-chain property address, kind and reason: "offChain"
needsDecisionRefusal reason, subject and candidate addresses/kinds/roles/locations
chainCandidatesUnselected chain address, kind, role and reason
generationsRequired array of {event, from, to, mode, mutability}; canonical EventContract addresses, stable address order, one entry per selected event owner
deployableWithoutTransformationFalse when any candidate event has several generations; no transformation is inferred
esmVersionChangeSelected ESM version pair, or null if unchanged or unknown
esmVersionKnownWhether 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.

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.

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.

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-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:

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

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

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

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

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

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

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.

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.

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

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

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

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

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

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

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

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

Two independent choices control AST authoring:

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

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

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

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

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

See the AST API and authoring procedure.

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:

FieldMeaning
sourceRevisionThe proposal workspace revision, including its identity catalog.
beforeRevisionThe retained baseline workspace revision.
afterRevisionThe candidate workspace revision (the same as sourceRevision for proposal review).
comparisonLevelauthoring-structure: normalized typed members, not source lines or an execution/equivalence verdict.
executableBeforeAvailable, executableAfterAvailableWhether each snapshot binds executably; structural review does not require binding.
completeWhether every comparison section is complete.
hasSemanticChangetrue 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.
sectionsEach section’s complete flag and unavailable reasons. An empty incomplete section never means no change.
limitsComparison exclusions and fallback rules.
pagerevision, totalCount, offset, items, nextOffset.

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

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

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

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

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

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

semantic-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:

FormSource
{ "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.

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.

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.

ResourceLimit
Request / JSON depth33,554,432 characters / 256 levels
Pure structured result1 MiB UTF-8; effect/recovery verdicts are never hidden by this limit
Source population512 files; 8 MiB combined; 2 MiB per file
Source lines20,000/file; 8,192 characters/line; 128 leading whitespace characters
Traversal32,768 entries; 16 directory levels
Reopening envelope16 MiB UTF-8, also bounded by encoded request size
Operations/migrations256 entries per array
Page200 items or 192 KiB decoded bytes
Outstanding proposals16; 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.