Repository navigation
Description
Slice 4 of #789. Blocked on #788 and slice 3 (#806).
Read tools for the production side: flocks, stock / egg lots, daily entries.
Domain questions, not route mirrors
A 1:1 tool-per-route surface would be pass-through methods with the same arguments and a renamed result. That is the red flag exactly. The caller is a model that holds names, not the SPA that already holds GUIDs.
So tools answer the question a caller actually asks. They hide name-to-id resolution, paging, and error mapping. They contain no validation rule, no authorization decision, and no tenant or flock-scope predicate. Each already exists one layer down and is already guarded.
Bounded results are required, not optional
Reusing a repository does not inherit the HTTP adapter's limits. Every read tool states a bound, and each reply carries explicit completeness and continuation fields. A silently truncated first page is worse than an error, because it is an apparently complete wrong answer and the model cannot tell.
Two separate guards are needed, and they are not the same. One checks that the reply is bounded. The other checks that the query is bounded. An implementation can fetch every page in a loop and cap the reply. That satisfies the first guard and violates the second.
Name resolution needs an ambiguity contract
Duplicate flock names are legal. FlockRepository says so, and its ThenBy(f => f.Id) tie-break exists because of it. Resolution means exactly one accessible match. Zero matches is not-found. Two or more is an ambiguity error listing candidate ids and distinguishing fields.
Done when
Each tool returns bounded results that are complete or continuable. A cross-farm bearer reads nothing. A Worker sees only assigned flocks.
Design
docs/plans/770-mcp-server/01-design.md, guards rows 25-32.
Activity
mforce commentedon Sep 14, 2026
Note from #514's re-plan: this slice's repository injection is a known, accepted crossing
Recorded 2026-09-14 while re-planning #514 (modular monolith). No change is requested here. This
is context so nobody rediscovers the interaction at review.
These tools inject repositories directly, per
docs/plans/770-mcp-server/01-design.md:112(
WaterTools(McpCallContext call, IWaterUsageRepository water)), and that is this design's sanctionedpath. #514's proposed adapter rule says the opposite: an adapter "may call a module contract or
approved workflow, not handlers/repositories/context" (its design §8 rule 4).
Both cannot hold at once, so #514 is resolving it on its side rather than asking this slice to
change. It declares
Mcp/an adapter tier withDirectRepositoryprivilege. That is a named, datedexemption carrying a review issue, instead of a guard that everyone later waters down.
What this means for you: nothing changes in how you write these tools. The only ask is ordering.
#514 wants that tier row committed before this slice merges, so its guard is not red on merged code
the day it is written. That is 1-2 days of work with no production code, and the planning is under way now.
Related and more consequential: #809 carries a separate note about flock name resolution, which
does affect what gets built.
mforce commentedon Sep 14, 2026
Follow-up: the
Mcp/adapter-tier row described above is now filed as #843. Nothing changes for this slice. #514 absorbs the crossing on its side.mforce commentedon Oct 6, 2026
Updated in the 2026-10-06 re-evaluation. Size unchanged.
Tools may call module contracts only (CW1004), and every read here has one:
IFlockModule.SearchAsync,IFlockLookup.GetAsync/GetDisplayNamesAsync. Birds alive is not a field; derive it fromInitialCountandGetBirdsRemovedAsync.IEggOperationsModule.GetStockByGradeAsync.ListLotsAsync,ListLotMovementsAsync.ListDailyEntriesAsync,GetDailyEntryAsync.The page caps still live in the endpoints (
StockEndpoints.cs,DailyEntryEndpoints.cs), not the contracts, so each tool sets its own bound and completeness fields. The "known, accepted crossing" note from 2026-09-14 is out of date: injecting a repository is now a build error.mforce commentedon Oct 10, 2026
Re-check after milestone 10, 2026-10-10. Every tool here requires the
farm:readscope (OAuthScopes.cs:7) as well as its role policy. #806 adds the named scope policies. Nothing else in this slice changed.mforce commentedon Oct 10, 2026
Note from #806 (PR #1195). Guard rows 13 to 15 in
docs/plans/770-mcp-server/02-guards.md(route parity:[MirrorsRoute]or[MirrorsNoRoute]on every tool, tool roles a subset of the mirrored route's, route names that resolve) did not land with #806. No tool existed, so there was no route to mirror. They land with this slice's first tool.What #806 did leave in place for the first tool:
/mcpis mapped, stateless, OAuth only, andWithToolsFromAssemblypicks up any[McpServerToolType]inCluckwork.Api.[Authorize]and a scope[Authorize(Policy = AuthPolicies.FarmReadScope)](orDailyEntriesWriteScope), and never[AllowAnonymous].McpToolSurfaceTests.RealTree_EveryToolCarriesARolePolicyAndAScopePolicyfails otherwise.AdapterClaimrow inRealModuleLedger.Adapters(tests/Cluckwork.Application.Tests/Architecture/RealModuleLedger.Adapters.cs). MCP slice 3: map /mcp, wire RBAC and tools/list filtering #806 added none because there were no tool classes.McpAuthorizationTestsshows the pattern for driving a tool through the real OAuth flow and the SDK client (McpConnectionin the same folder).test(mcp): add the #807 mutants to mutation-check.sh
Merge origin/main (#1209 read tools) into feat/809-mcp-record-daily-e…