Skip to content

Add the schema document with field descriptions and examples - #2473

Merged
pournasserian merged 8 commits into
v2from
2470-schema-document
Oct 18, 2026
Merged

pournasserian merged 8 commits into
v2from
2470-schema-document

Conversation

@pournasserian

@pournasserian pournasserian commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

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

  • Descriptions: a definition takes an optional description for 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.
  • Value descriptions: IFieldType.DescribeValue returns a FieldValueDescription: the JSON Schema of one value, an example that satisfies it, and a sentence for agents. Every built-in implements it. reference says that a field reference stores the entity, not a copy of its value. list wraps 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, with title, description, properties, required, additionalProperties: false, per-field examples and one example entry. ForSite puts every type under $defs by slug. x-fluentcms carries the slug, version and entity flag.
  • Facts: on an entity type every field is an object with value plus source, owner, validFrom, validUntil and lastVerified. 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 through IContentTypeDirectory.

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:

  • Examples come from the field types, never from the author. They are therefore always valid, which the conformance base asserts. Author-supplied examples need value validation, which arrives with entries.
  • Descriptions are two sentences joined: the author's description, then the field type's. The field type's sentence is always there, so a document is useful even for a type nobody described.
  • A field reference's value is the entity id, not the field value. The description says so, so an agent does not try to write the price into the page.
  • Only keywords that mean the same in draft 7 and 2020-12 are used, so the documents validate identically under either dialect. That matters for the test validator below.

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 with requireLicenseAcceptance, 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 were LateApexEarlySpeed.Json.Schema (BSD-3-Clause) and Corvus.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 $id and descriptions, and an unknown slug fails with content_type_not_found.

The reference scenario model moved into SchemaScenario so the directory tests and the document tests define it once.

Checklist

  • Linked to an issue a maintainer has confirmed
  • Targets v2
  • Tests or docs updated where behaviour changed
  • No tool-generated co-author trailers or footers

@pournasserian pournasserian added this to the M1 Governance core milestone Oct 18, 2026
@pournasserian pournasserian added type:feature New capability v2 FluentCMS v2 (v2 branch) area:schema Content types and entity types (SCH) labels Oct 18, 2026
@pournasserian
pournasserian changed the base branch from 2461-entity-types to v2 October 18, 2026 18:54
@pournasserian pournasserian reopened this Oct 18, 2026
# 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
pournasserian merged commit 3b6eaeb into v2 Oct 18, 2026
2 checks passed
@pournasserian
pournasserian deleted the 2470-schema-document branch October 18, 2026 19:07
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:schema Content types and entity types (SCH) type:feature New capability v2 FluentCMS v2 (v2 branch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schema document: JSON Schema per content type with field descriptions, value schemas and examples

1 participant