Repository navigation
Add the tool and resource conventions with the first read-only tools - #2508
Merged
Merged
Conversation
3 of 4 tasks
pournasserian
added a commit
that referenced
this pull request
Oct 18, 2026
Closes #2509 ## What The write tools of F-MCP (#2423) for content types and change sets, on the conventions from #2508. An agent proposes schema changes and submits them, and a person reviews and publishes them. - **Change sets**: `open_change_set` (rationale, sources), `submit_change_set` (open to checks running) and `reopen_change_set` (checks failed to open), for the agent's own sets only (#2503). - **Content types**: `create_content_type` (name, slug, definition) and `revise_content_type` (slug, base version, definition). - Each stores the new version, unpublished (#2482), and adds the matching schema operation to a change set in one unit of work, so either both are written or neither is. - The set is a `change_set_id` from `open_change_set`, or a `session_id` with a rationale and sources, which opens the session's set on its first call (CHG-02). - Each write result carries the content type, the change set and a `next` sentence that says what comes next. - **Core**: - `IChangeSetDirectory.GetSessionSetAsync` reads the caller's open set for a session id. - `IAgentPermissions.CheckChangeSetSizeAsync` takes the number of operations a write is about to add. - `ToolArgumentsReader` reads lists of strings, objects, optional ids and required whole numbers. - **CONTRIBUTING.md**: the read and write tools and what is live when. ## Why MCP-01 asks for tools for every capability, and the governance model needs everything an agent writes to wait in a change set for a person. With #2482, a stored content type version is not published until its set is. With this PR, every version an agent stores is also in one of its change sets, and no tool approves, rejects or publishes (MCP-06). Choices worth a look: - **One unit of work for both writes.** Each directory finds its expected failures before its own unit of work, so the tool's outer one undoes the version when the change set refuses the operation, for example because of the item's version, a cap or ownership. - **Caps are checked before the write too.** A refused cap inside the outer unit of work would roll back its own security event (SEC-08, the limit noted in #2503). The tool therefore first reads the target set and asks `IAgentPermissions` with the operation it is about to add, or asks for a new set when the session has none. That refusal commits its event, and nothing is stored. The set checks again on the add, which settles a race. - **`GetSessionSetAsync` is a small read on the change set directory**, the one place that knows a session's open set. The pre-check needs it. - **No operation scope yet**: an agent with any key can propose. Type, field and operation scopes are the credentials lane's. - **Create and revise of one type in one set** is refused by the set (one operation per item), with the set's own message. - **Submit and reopen are separate tools**, so each description says exactly when to call it. - **Not in this PR**: approve, reject, publish and revert (never for agents), bulk operations with progress and the MCP-01 coverage test (next slice), and entry and entity writes (their lanes). ## Tests Integration, over HTTP: - On SQLite and PostgreSQL, an agent opens a set, creates `category`, revises a person's `product` from version 1 and submits the set. The set holds both operations in order, is in checks running and names the agent, and the tool call log has all four calls, succeeded. - On SQLite and PostgreSQL, two creates with one `session_id` land in one set with the first call's rationale. - On SQLite and PostgreSQL, these store no version, while the agent's own valid create is stored: - a definition with an invalid slug, an unknown field type and a bad field name returns every error at once - a create past a cap of one item is refused, and `agent.items_per_change_set_exceeded` stays in the log - a create into a person's set and a submit of it are refused as not the agent's own - a create without a target names the missing argument - A revision from a stale base version returns `version_conflict` with a hint to read the current version, and the type stays at version 2. - The conformance tests now cover the write tools: names, described schemas with valid examples, and unknown arguments refused before any service is read. Unit tests cover the new reader methods. The full suite ran locally against a `postgres:17` container as well as SQLite, and markdownlint passes. No new package and no new project reference. ## Checklist - [ ] Linked to an issue a maintainer has confirmed - [x] Targets `v2` - [x] Tests or docs updated where behaviour changed - [x] No tool-generated co-author trailers or footers
This was referenced Oct 19, 2026
pournasserian
added a commit
that referenced
this pull request
Oct 19, 2026
Closes #2591 ## What The sixth slice of F-DSK (#2422): read-only desk tools for agents (MCP-01, "DSK (read-only for review decisions)"), on the tool conventions from #2508. It fills the DSK row of `McpCoverageTests`. - **`DeskTools`** (`FluentCMS.Core.Builtin.Tools`), three read-only tools: - `get_desk_counts`: how many items wait in each queue. - `list_desk_queue`: one page of a queue, ordered as the desk orders it. - The queues are `awaiting_review`, `checks_failed`, `stale_facts`, `recently_published` and `reverted`. Awaiting me is a person's queue, so it is not offered. - `page` and `limit` are optional (1 to 200, 50 by default), and the result has the total. - A set comes with its id, state, rationale, initiator, item count, the set it reverts and its times. A stale fact is shown as `list_stale_facts` shows it. - `get_change_set_review`: the state and since when, the rejection reason, the set it reverts, and every move in order with who made it and when. - Each tool reads its arguments before any service. An unknown queue, a page below 1 and a limit above 200 are `argument_invalid`. An unknown set is `change_set_not_found` with a hint. - **Agents see their own sets only.** #2580 made an agent read only the change sets it opened (`CheckChangeSetReadAsync`). - `ChangeSetListQuery` gains `OpenedBy`, which `ChangeSetLists` filters on. - `DeskQueues` sets it when the caller is an agent, so an agent's queues and counts hold its own sets. A person's view is unchanged. - `get_change_set_review` reads through `GetAsync`, so another party's set is refused with `agent_not_change_set_owner`. - **`ChangeSetMoves`** (`FluentCMS.Core.ChangeSets`) names the open and move operations once. `ChangeSetLists`, the set view and the review tool used to keep their own copies. - `DeskToolsConformanceTests`, and the DSK rows of `McpCoverageTests` in place of the missing one. - CONTRIBUTING names the desk tools in the MCP paragraph. ## Why - **Scoping in `DeskQueues`, not in each tool.** The queues are defined once for the desk and the tools. Applying the read rule where the queries are built keeps the counts and lists consistent, and a later tool cannot forget it. The rule itself stays in `AgentPermissions`. The list only follows it, as `GetAsync` does. - **`get_change_set_review` next to `get_change_set`.** `get_change_set` returns the set's content. The review status is the decisions: who moved it, when, and why it was rejected. An agent needs that to follow up on a rejection. - **No awaiting me for agents.** An agent never reviews (MCP-06), so the queue would only repeat awaiting review. - **Not in this PR**: writing decisions (people only, MCP-06), and security events for agents (SEC-08 puts them on the desk for people). ## Tests Unit: - `DeskQueuesTests`: an agent's lists and counts carry `OpenedBy` as the agent, and a person's carry none. - `ToolProviderConformanceTests` for `DeskTools`, and `McpCoverageTests` with the new rows. Integration over MCP, each on SQLite and PostgreSQL: - An agent opens, writes and submits three sets through its tools, which are moved to awaiting review and checks failed. The counts and the queues list those sets in the desk's order with paging and totals. A person's set awaiting review is left out, and nothing is appended to the audit log. - The agent reads the review of its rejected set: the reason, its own moves and the person's rejection, with `stateSince` at the rejection. Another party's set is refused with `agent_not_change_set_owner`. - An unknown queue (`awaiting_me`), a limit of 500, a page of 0 and an unknown set are refused, the last with a hint. `DeskQueueTests` now reads the queues as a person, since the scenario's default caller is an agent. `McpAgents.BuiltInTools` lists the three new tools. `dotnet build -warnaserror`, the full suite with the browser tests, and markdownlint pass locally against a `postgres:17` container as well as SQLite. No new package and no new project reference. ## Checklist - [x] Linked to an issue a maintainer has confirmed - [x] Targets `v2` - [x] Tests or docs updated where behaviour changed - [x] No tool-generated co-author trailers or footers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2504
What
The tool and resource conventions of F-MCP (#2423) that every capability epic builds its tools on (MCP-02, MCP-03, UX-03), with the first read-only tools for content types and change sets.
FluentCMS.Core.Tools):[ExtensionPoint] IToolProviderandIResourceProvider, which built-ins and plugins implement alike (EXT-01, EXT-02).AgentTool: a snake_case name, a title, a description written for a model, a JSON Schema for its input, a read-only flag and a handler.ToolCallpasses the arguments and the call's own service scope.ToolResult: the value as JSON, or the errors of aResultwith an optional hint that names the call that finds a valid value.ToolArgumentsReader: reports a missing, malformed or unknown argument by name, asFieldSettingsReaderdoes for settings.ToolSchemasbuilds input schemas that describe every argument with an example and refuse others.AgentResource: a fixed URI or a level 1 URI template, matched one path segment per parameter.CoreToolsSetupputs every provider's tools and resources on the server on both transports. A name used twice stops the host at startup.isError) with{"errors": [{"code", "message"}], "hint"}, never a protocol error.FluentCMS.Core.Builtin.Tools):list_content_typesget_content_type(slug, optional version)get_schema_document(optional slug)get_change_set(id)get_change_set_diff(id)fluentcms://schemaandfluentcms://schema/{slug}, the schema documents from Add the schema document with field descriptions and examples #2473.Why
MCP-02 asks for descriptions, input schemas and errors written for an LLM, and MCP-03 for resources. F-EXT makes tool providers an extension point that lands with its first consumer. These read-only tools are that consumer, and the write tools follow on the same conventions.
Choices worth a look:
AgentToolto an SDKMcpServerTooland eachAgentResourceto anMcpServerResource, so a plugin's tool needs nothing from the transport.list_content_types, and an unknown version says to leave it out.ValidateOnStartbuilds the server options when the host starts, so a duplicate tool name fails there, not at the first call.ResourceNotFound), because a resource read has no error result. The message is the service's.Tests
Unit:
ToolProviderConformanceTests, derived forSchemaToolsandChangeSetTools:argument_unknownbefore any service is readToolArgumentsReader: values read, then missing, malformed and unknown arguments by name in order; no arguments,null, and a value that is not an object.ToolResultJSON with errors and hint. URI template matching and unescaping. Tool name rules.Integration:
tools/listreturns the five tools with descriptions, object schemas and the read-only annotation.list_content_typesandget_content_typereturn the current version and version 1.get_schema_documentreturns the same JSON asISchemaDocuments, for the site and for one type.get_change_setandget_change_set_diffon a stored set show its state, initiator and operation, and the added field with its after value.application/schema+json, and an unknown slug is refused with the service's message.The full suite ran locally against a
postgres:17container as well as SQLite, and markdownlint passes. No new package and no new project reference.Checklist
v2