You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Amended 2026-10-06. This body predates #514's module contracts. Since it was written, #787 closed, #804 closed with its work split into #806 and #809, and #793 and #794 moved to the OAuth milestone. Read the re-evaluation before picking up a slice. The body below is kept for history.
Note
Picking this up? Start here.
Blocked on #788 for most of it. Slices 1-2 can start now. Slices 3-6 cannot, because no client can
reach a tool until OAuth exists. Slice 6 also needs #787.
The design is finished and merged. Read docs/plans/770-mcp-server/
in full before writing anything, especially 02-guards.md and 05-adversarial-review.md. The
design went through three rounds of adversarial review. They found six guards that could not go red
on their stated mutations, three of them in fixes for earlier rounds. The corrections are the
valuable part.
Two things will bite immediately if skipped:
IdempotencyMiddleware400s every MCP request, initialize included, because MCP sends
everything as POST and no client knows this app's Idempotency-Key contract. Nothing works
until /mcp is exempted. That is slice 1, and it blocks even a read-only demo.
BodyReadingEndpointTests starts applying the moment /mcp is mapped. It needs ReadsRequestBodyAttribute or the suite goes red.
Do not re-derive the guard table from scratch. Six of its rows exist because a mutation was
stated, run, and found not to go red. Rewriting them from first principles loses that.
Expose a curated Cluckwork tool surface over the Model Context Protocol, so an MCP client can drive farm operations under the same account, role and flock-scope model a human uses.
FlockScopeGuard's fail-open branch grants account-wide flock access to an unresolved actor #787, FlockScopeGuard fail-open. Blocks slice 6 only (the write tool). Slices 1-5 may proceed. RecordDailyEntry consults that guard and the read path does not. See the comment thread for why it is a genuine prerequisite for the write rather than defence-in-depth, and why it must not gate the reads.
Build an OAuth 2.1 authorization server (OpenIddict) so MCP clients can authenticate #788, token lifetime / OAuth. MCP clients expect OAuth discovery and a refreshable token. Cluckwork's refresh is an HttpOnly cookie and the access token is short-lived. This is a prerequisite for the epic, not a follow-up. It does not change the design, because the identity bridge reads whatever ClaimsPrincipal the pipeline produces. But it decides whether anyone can practically connect, and therefore whether the tool work is worth doing yet. Decide before sizing the slices below.
Scope decisions already taken
Transport: HTTP streamable on /mcp, inside the existing middleware pipeline. SessionMode.Stateless.
Tool surface: flock, stock and daily-entry reads; customer, order and payment reads; and one write, record daily entry.
The identity bridge design in docs/plans/770-mcp-server/still holds unchanged. McpCallContext reads the resolved ClaimsPrincipal and the scoped tenant, actor and flock-scope services. It does not care whether the caller presented a hand-rolled JWT or an OAuth reference token. The change is upstream of it. The pipeline must authenticate an OAuth token. The checks CredentialEpochMiddleware performs for JWTs (disabled user, suspended farm, must-change-password) must also run for OAuth callers, since that middleware sits on the JWT path.
Scopes are new: role ∩ scope, where scope can only subtract. There are two at launch, read farm data and record daily entries. They map onto the 6 read tools and 1 write tool below.
Slices
Ordered by dependency. Three L, four M, one S.
Wave 1: startable now. Neither slice depends on #788. One is pipeline work, the other is the identity
bridge, and neither cares how a caller authenticated.
MCP slice 1: unblock /mcp and extract the idempotency protocol #804 · L Unblock /mcp and extract the idempotency protocol. IdempotencyMiddleware
400s every MCP request including initialize, so nothing works until /mcp is exempted. Largest risk in the epic, and it is not tool code.
ModelContextProtocol.AspNetCore 2.2.0 ships a native net10.0 target (verified). The new package goes in Directory.Packages.props with a bare PackageReference. Commit the regenerated packages.lock.json files in the same commit, or CI fails NU1004 (#684 / #146).
Decide before the tool schemas ship
Decide how farm-controlled free text is presented to a model before the MCP tool surface exists #792, how farm-controlled free text is presented to a model. It is not a hard blocker on any one slice. But the mitigations are shape decisions (structured fields vs prose, whether free-text is returned by default, delimiting, tool ReadOnly annotations), and shape is expensive to change once clients depend on a published schema. Note the privilege gradient: Customer.Note is writable at SalesFlow (which includes a plain Worker) and readable through tools gated at AdminOnly. Decide during slices 4-5, not after.
Important
Amended 2026-10-06. This body predates #514's module contracts. Since it was written, #787 closed, #804 closed with its work split into #806 and #809, and #793 and #794 moved to the OAuth milestone. Read the re-evaluation before picking up a slice. The body below is kept for history.
Note
Picking this up? Start here.
Blocked on #788 for most of it. Slices 1-2 can start now. Slices 3-6 cannot, because no client can
reach a tool until OAuth exists. Slice 6 also needs #787.
The design is finished and merged. Read
docs/plans/770-mcp-server/in full before writing anything, especially
02-guards.mdand05-adversarial-review.md. Thedesign went through three rounds of adversarial review. They found six guards that could not go red
on their stated mutations, three of them in fixes for earlier rounds. The corrections are the
valuable part.
Two things will bite immediately if skipped:
IdempotencyMiddleware400s every MCP request,initializeincluded, because MCP sendseverything as POST and no client knows this app's
Idempotency-Keycontract. Nothing worksuntil
/mcpis exempted. That is slice 1, and it blocks even a read-only demo.BodyReadingEndpointTestsstarts applying the moment/mcpis mapped. It needsReadsRequestBodyAttributeor the suite goes red.Do not re-derive the guard table from scratch. Six of its rows exist because a mutation was
stated, run, and found not to go red. Rewriting them from first principles loses that.
Expose a curated Cluckwork tool surface over the Model Context Protocol, so an MCP client can drive farm operations under the same account, role and flock-scope model a human uses.
Design:
docs/plans/770-mcp-server/. #785 landed it and closed spike #770. Read01-design.mdbefore picking up any slice.02-guards.mdhas the 28 guards with their red mutations.Blocked on
FlockScopeGuardfail-open. Blocks slice 6 only (the write tool). Slices 1-5 may proceed.RecordDailyEntryconsults that guard and the read path does not. See the comment thread for why it is a genuine prerequisite for the write rather than defence-in-depth, and why it must not gate the reads.ClaimsPrincipalthe pipeline produces. But it decides whether anyone can practically connect, and therefore whether the tool work is worth doing yet. Decide before sizing the slices below.Scope decisions already taken
/mcp, inside the existing middleware pipeline.SessionMode.Stateless.What #788 changed about this epic
The identity bridge design in
docs/plans/770-mcp-server/still holds unchanged.McpCallContextreads the resolvedClaimsPrincipaland the scoped tenant, actor and flock-scope services. It does not care whether the caller presented a hand-rolled JWT or an OAuth reference token. The change is upstream of it. The pipeline must authenticate an OAuth token. The checksCredentialEpochMiddlewareperforms for JWTs (disabled user, suspended farm, must-change-password) must also run for OAuth callers, since that middleware sits on the JWT path.Scopes are new:
role ∩ scope, where scope can only subtract. There are two at launch, read farm data and record daily entries. They map onto the 6 read tools and 1 write tool below.Slices
Ordered by dependency. Three L, four M, one S.
Wave 1: startable now. Neither slice depends on #788. One is pipeline work, the other is the identity
bridge, and neither cares how a caller authenticated.
/mcpand extract the idempotency protocol.IdempotencyMiddleware400s every MCP request including
initialize, so nothing works until/mcpis exempted.Largest risk in the epic, and it is not tool code.
McpCallContext) and the dependency-graph walk. Testablewith no tools and no
MapMcpat all.Wave 2: needs OAuth
/mcp, wire RBAC andtools/listfiltering. Needs Build an OAuth 2.1 authorization server (OpenIddict) so MCP clients can authenticate #788, MCP slice 2: the identity bridge (McpCallContext) and the dependency-graph walk #805.Wave 3: the tools, reads in parallel
Needs MCP slice 3: map /mcp, wire RBAC and tools/list filtering #806.
write path's fail-open branch.
Wave 4: closes the surface
[MirrorsRoute]parity guard, so tool RBAC cannot drift from its route.Needs MCP slice 3: map /mcp, wire RBAC and tools/list filtering #806-MCP slice 6: write tool — record daily entry #809.
Packaging
ModelContextProtocol.AspNetCore2.2.0 ships a nativenet10.0target (verified). The new package goes inDirectory.Packages.propswith a barePackageReference. Commit the regeneratedpackages.lock.jsonfiles in the same commit, or CI failsNU1004(#684 / #146).Decide before the tool schemas ship
ReadOnlyannotations), and shape is expensive to change once clients depend on a published schema. Note the privilege gradient:Customer.Noteis writable atSalesFlow(which includes a plain Worker) and readable through tools gated atAdminOnly. Decide during slices 4-5, not after.Related, not blocking
FlockScopeGuard's fail-open branch. The design makes it unreachable from MCP rather than fixing it, deliberately. Independent.WithHttpTransportregisters three singletons and a hosted service, inert under Stateless. Independent.