Repository navigation
Add the schema document with field descriptions and examples - #2473
Merged
Merged
Conversation
# Conflicts: # CONTRIBUTING.md # src/FluentCMS.Core/Builtin/FieldTypes/ListFieldType.cs # src/FluentCMS.Core/Builtin/FieldTypes/ReferenceFieldType.cs # src/FluentCMS.Core/Schema/ContentTypeDefinition.cs # src/FluentCMS.Core/Schema/FactMetadata.cs # src/FluentCMS.Core/Schema/FieldTypeRegistry.cs # src/FluentCMS.Core/Schema/IFieldType.cs # src/FluentCMS.Core/Schema/SchemaErrorCodes.cs # tests/FluentCMS.IntegrationTests/Schema/ContentTypeDirectoryTests.cs # tests/FluentCMS.IntegrationTests/Schema/SchemaScenario.cs # tests/FluentCMS.Tests/Schema/ContentTypeDefinitionTests.cs # tests/FluentCMS.Tests/Schema/FactMetadataTests.cs # tests/FluentCMS.Tests/Schema/FieldTypeConformanceTests.cs # tests/FluentCMS.Tests/Schema/PluginFieldTypeTests.cs
pournasserian
added a commit
that referenced
this pull request
Oct 18, 2026
…2508) 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. - **Core** (`FluentCMS.Core.Tools`): - `[ExtensionPoint] IToolProvider` and `IResourceProvider`, 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. `ToolCall` passes the arguments and the call's own service scope. - `ToolResult`: the value as JSON, or the errors of a `Result` with an optional hint that names the call that finds a valid value. - `ToolArgumentsReader`: reports a missing, malformed or unknown argument by name, as `FieldSettingsReader` does for settings. `ToolSchemas` builds 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. - **Mcp**: `CoreToolsSetup` puts every provider's tools and resources on the server on both transports. A name used twice stops the host at startup. - Tools carry the read-only annotation. - A success returns the JSON as structured content and as text. - A failure is a tool error (`isError`) with `{"errors": [{"code", "message"}], "hint"}`, never a protocol error. - **Built-in tools** (`FluentCMS.Core.Builtin.Tools`): - `list_content_types` - `get_content_type` (slug, optional version) - `get_schema_document` (optional slug) - `get_change_set` (id) - `get_change_set_diff` (id) - **Built-in resources**: `fluentcms://schema` and `fluentcms://schema/{slug}`, the schema documents from #2473. - **CONTRIBUTING.md**: the conventions under "Extension points". ## 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: - **Core owns the tool model, not the SDK.** Core references no MCP package (ADR-0004). Mcp adapts each `AgentTool` to an SDK `McpServerTool` and each `AgentResource` to an `McpServerResource`, so a plugin's tool needs nothing from the transport. - **Arguments first**: every tool reads its arguments before any service. A conformance test calls each tool with a service provider that throws, so a tool that skips the check fails. An unknown argument is an error rather than ignored, which catches an agent's typo. - **Errors carry a hint** where one call finds the valid value. An unknown slug points to `list_content_types`, and an unknown version says to leave it out. - **Text and structured content carry the same JSON.** Clients that read only text get everything. The text keeps quotes and apostrophes unescaped, because a model reads it, not a browser. - **`ValidateOnStart`** builds the server options when the host starts, so a duplicate tool name fails there, not at the first call. - **The explicit tools capability is gone**: the tool collection now always exists, so the SDK advertises it. - **Resources fail as protocol errors** (`ResourceNotFound`), because a resource read has no error result. The message is the service's. - **Reads are not scoped yet**: an agent with a key reads every type and set. Type, field and operation scopes belong to the credentials lane of #2423. - **Not in this PR**: write tools and session grouping (next slice), bulk operations and the MCP-01 coverage test, entry and entity tools (their lanes, on these conventions), and rule hints (F-RUL). ## Tests Unit: - `ToolProviderConformanceTests`, derived for `SchemaTools` and `ChangeSetTools`: - unique snake_case names - a title and a description for every tool and resource - input schemas that are objects refusing other arguments, with a description on every argument and examples that pass an independent validator (NJsonSchema) - an unknown argument returns `argument_unknown` before any service is read - every resource URI has a scheme - `ToolArgumentsReader`: values read, then missing, malformed and unknown arguments by name in order; no arguments, `null`, and a value that is not an object. - `ToolResult` JSON with errors and hint. URI template matching and unescaping. Tool name rules. Integration: - Over HTTP on SQLite and PostgreSQL: - `tools/list` returns the five tools with descriptions, object schemas and the read-only annotation. - `list_content_types` and `get_content_type` return the current version and version 1. - `get_schema_document` returns the same JSON as `ISchemaDocuments`, for the site and for one type. - `get_change_set` and `get_change_set_diff` on a stored set show its state, initiator and operation, and the added field with its after value. - An unknown slug, version and change set each return their service code, with a hint where there is one. A malformed and an unknown argument are reported together. - Resources list the site document and the type template. Reading returns `application/schema+json`, and an unknown slug is refused with the service's message. - Over stdio in-process on both providers, the tools answer the same way. - A second provider with a built-in tool's name stops the web host at startup. - The #2481 and #2487 tests now expect the built-in tools in the list. 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 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 #2470
What
The fourth slice of F-SCH (#2415): the schema in a form an agent can write entries from without seeing a screen (SCH-06).
descriptionfor the type and for each field, versioned with the rest of the definition. A blank one, or one over 1000 characters, is rejected; leave it out instead.IFieldType.DescribeValuereturns aFieldValueDescription: the JSON Schema of one value, an example that satisfies it, and a sentence for agents. Every built-in implements it.referencesays that a field reference stores the entity, not a copy of its value.listwraps its item type's description. A field type that is no longer installed is described as any value, so a stored definition that uses one still has a document.SchemaDocument: the JSON Schema (draft 2020-12) for one content type, withtitle,description,properties,required,additionalProperties: false, per-fieldexamplesand one example entry.ForSiteputs every type under$defsby slug.x-fluentcmscarries the slug, version and entity flag.valueplussource,owner,validFrom,validUntilandlastVerified. All must be present; all but the value may be null, so an agent states that a fact has no source yet rather than leaving it out (ENT-02). A source is{"kind": "url", "reference": "..."}.ISchemaDocuments: the document for one type or for the whole site, read throughIContentTypeDirectory.Why
SCH-06 and the epic's goal: an agent can write a valid entry without ever seeing a screen. The document is JSON Schema because that is what agent tooling already validates against, and it fixes the value shapes that F-CNT and F-ENT will check entries against, including the fact envelope.
Choices worth a look:
New dependency
NJsonSchema 11.6.1 (MIT), test projects only. The tests validate the documents with an independent validator, so they do not grade a schema with the logic that wrote it. No runtime project references it and nothing ships with it.
JsonSchema.Net, the usual choice and the only one with full 2020-12 support, was rejected: from 9.x it is published under a commercial EULA withrequireLicenseAcceptance, which does not belong in an open-source repository's build. NJsonSchema is MIT and validates draft 7, which is why the documents stay inside the keyword set both dialects share. The alternatives wereLateApexEarlySpeed.Json.Schema(BSD-3-Clause) andCorvus.Json.Validator(Apache-2.0, but a code generator and a much larger dependency tree).No migration, no pending model change and no new runtime package.
Tests
Conformance: every field type describes its values with a schema its own example satisfies, for every settings sample the base accepts, and has a non-blank description. All ten built-ins and both sample plugin types pass.
Unit: descriptions round-trip through the stored document, a non-string description is reported with its path, and a blank or too long one fails validation. A content type document describes every field, names its required fields and refuses unknown ones, and its example entry validates. An entity type document wraps each field in the fact envelope. A fact may state that it has no source. Eight broken entries each fail: a missing required field, an unknown field, a date that is not a date, an id that is not a uuid, a value outside an enum, a fact without its source, a fact with a bare value, and a source of an unknown kind. The site document holds every type under its slug, and every type's example entry satisfies its own schema. An uninstalled field type is described as any value.
Integration on SQLite and PostgreSQL: every type of the reference scenario model has a document its example entry satisfies, with the entity flag where expected and the author's description in front of the field type's sentence. The site document lists every stored type. A revision shows in the document's
$idand descriptions, and an unknown slug fails withcontent_type_not_found.The reference scenario model moved into
SchemaScenarioso the directory tests and the document tests define it once.Checklist
v2