Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
3400134
test: add DUnitX harness and record legacy golden files
cmgeuze Sep 3, 2026
ce9a9b1
refactor: define protocol constants once in MCPServer.Types
cmgeuze Sep 3, 2026
0773b4b
fix: make shared counters atomic and create the registry eagerly
cmgeuze Sep 3, 2026
61f479e
fix: keep stdout clean while the stdio transport runs
cmgeuze Sep 3, 2026
4c18b8b
test: add conformance, Inspector and stdio smoke scripts with the fir…
cmgeuze Sep 3, 2026
1a42cf1
docs: add CHANGELOG and document the library rules and the test tooling
cmgeuze Sep 3, 2026
95b5042
fix: repair the built-in resources and the nil dereferences found whi…
cmgeuze Sep 3, 2026
ba11f66
chore: tidy .gitignore
cmgeuze Sep 3, 2026
60a0db3
docs: describe the tests and constants without roadmap references
cmgeuze Sep 3, 2026
829e237
Merge pull request #27 from GDKsoftware/feature/test-harness
cmgeuze Sep 3, 2026
d6b20c0
feat: dual-era JSON-RPC core with server/discover and per-request _meta
cmgeuze Sep 3, 2026
bcc56bc
test: era detection, processor, capability builder and modern goldens
cmgeuze Sep 3, 2026
f358dd5
docs: describe the protocol eras and the new settings
cmgeuze Sep 3, 2026
04041e0
Merge pull request #28 from GDKsoftware/feature/protocol-core
cmgeuze Sep 3, 2026
96321c7
feat: dual-era Streamable HTTP with Origin check, status codes and mi…
cmgeuze Sep 3, 2026
b2e0013
test: in-process HTTP transport tests and header tests
cmgeuze Sep 3, 2026
77742ed
docs: migration notes and the network settings
cmgeuze Sep 3, 2026
dec7083
Merge pull request #29 from GDKsoftware/feature/http-transport
cmgeuze Sep 3, 2026
3450987
feat: tool result builder and tool/resource metadata
cmgeuze Sep 3, 2026
818c9c0
feat: strict argument validation and typed schemas
cmgeuze Sep 3, 2026
e572475
feat: era-aware tools and resources managers
cmgeuze Sep 3, 2026
9ecbb57
feat: content sample tools and resources
cmgeuze Sep 3, 2026
51b4dee
test: cover tool results, validation, schemas and managers
cmgeuze Sep 3, 2026
0e7e923
docs: describe tool results, validation and error codes
cmgeuze Sep 3, 2026
e584aec
Merge pull request #30 from GDKsoftware/feature/tools-resources
cmgeuze Sep 3, 2026
0ed445e
feat: rewrite the stdio transport for spec-correct framing and cancel…
cmgeuze Sep 3, 2026
06b977b
test: cover the rewritten stdio transport
cmgeuze Sep 3, 2026
b6d69e0
feat: add a progress-reporting sample tool
cmgeuze Sep 3, 2026
a50ac05
test: extend the stdio smoke test and re-record affected goldens
cmgeuze Sep 3, 2026
a50ccb5
docs: describe the stdio transport rewrite
cmgeuze Sep 3, 2026
2d532ba
Merge pull request #32 from GDKsoftware/feature/stdio
cmgeuze Sep 3, 2026
d005283
refactor: share content block builders between tools and prompts
cmgeuze Sep 3, 2026
46bb696
feat: add prompts and resource templates to the type system
cmgeuze Sep 3, 2026
3a21ecf
feat: add the prompts and completion managers
cmgeuze Sep 3, 2026
0a297ca
feat: resolve resources/read against registered templates
cmgeuze Sep 3, 2026
a591773
feat: add a JSON Schema validator and the remaining schema attributes
cmgeuze Sep 3, 2026
4504f89
feat: add the JSON Schema 2020-12 conformance fixture tool
cmgeuze Sep 3, 2026
7a4a3c8
feat: warn when a tool's structuredContent does not match outputSchema
cmgeuze Sep 3, 2026
623734e
build: wire the prompts, templates and validator units into both proj…
cmgeuze Sep 3, 2026
300b65f
test: cover prompts, resource templates, completion and the validator
cmgeuze Sep 3, 2026
68363e1
test: stop using prompts/list as the unknown-method example
cmgeuze Sep 3, 2026
34eb754
test: re-record goldens for the new capabilities and fixtures
cmgeuze Sep 3, 2026
d0c88b6
test: prune the conformance baselines for the newly passing scenarios
cmgeuze Sep 3, 2026
1788410
docs: describe prompts, resource templates and completion
cmgeuze Sep 3, 2026
09c2862
Merge pull request #33 from GDKsoftware/feature/prompts-completion
cmgeuze Sep 3, 2026
f8c34f6
fix: thread safety and lifetime issues found in review
cmgeuze Sep 4, 2026
cb49464
docs: record the coding conventions this library keeps
cmgeuze Sep 4, 2026
fd1aa60
Merge pull request #34 from GDKsoftware/fix/review-findings
cmgeuze Sep 4, 2026
fe169b7
feat: multi round-trip requests with signed request state
cmgeuze Sep 4, 2026
1ff1add
feat: example tools, prompt and tests for multi round-trip requests
cmgeuze Sep 4, 2026
bebf755
docs: describe multi round-trip requests and the request state settings
cmgeuze Sep 4, 2026
7352b6b
Merge pull request #35 from GDKsoftware/feature/mrtr
cmgeuze Sep 4, 2026
7183ace
feat: stream notifications on the HTTP response
cmgeuze Sep 4, 2026
04b16df
feat: logging and streaming example tools with tests
cmgeuze Sep 4, 2026
05ff032
docs: describe streamed responses and request-scoped logging
cmgeuze Sep 4, 2026
e092f4c
Merge pull request #36 from GDKsoftware/feature/http-streaming
cmgeuze Sep 4, 2026
b36e87c
feat: subscriptions/listen with change notifications
cmgeuze Sep 4, 2026
e90d17e
feat: trigger tools and tests for subscriptions
cmgeuze Sep 4, 2026
c91aef2
docs: describe subscriptions and change notifications
cmgeuze Sep 4, 2026
e0ebad9
Merge pull request #37 from GDKsoftware/feature/subscriptions
cmgeuze Sep 4, 2026
1206a69
feat: bearer authentication for the HTTP endpoint
cmgeuze Sep 4, 2026
6039c5c
test: authorizers, challenges, metadata and the HTTP authentication flow
cmgeuze Sep 4, 2026
9a1f9c6
docs: describe authentication, scopes and the protected resource meta…
cmgeuze Sep 4, 2026
f31c4a5
Merge pull request #38 from GDKsoftware/feature/authorization
cmgeuze Sep 4, 2026
359b4cd
feat: host allow-list and a switch for the diagnostics resources
cmgeuze Sep 4, 2026
1a4eb97
test: host policy, host rejection over HTTP and template removal
cmgeuze Sep 4, 2026
f06cdfb
docs: security section, host allow-list and diagnostics switch
cmgeuze Sep 4, 2026
14ea0c1
Merge pull request #39 from GDKsoftware/feature/hardening
cmgeuze Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ backup/
*.temp
~$*

# Claude Code specific

# Dependencies
node_modules/
package-lock.json

# Test and conformance output
tests/results/
results/
379 changes: 379 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

242 changes: 242 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# Migration notes

Behaviour changes that can affect an existing deployment or a project that
uses this repository as a library, with what to do about them. Everything
else in the CHANGELOG is additive.

## HTTP transport

**The server binds to loopback when `Host` is `localhost`.** It used to listen
on every interface. A server that must be reachable from other machines needs
either a `Host` that is not loopback (then it listens on every interface) or an
explicit `[Server] BindAddress`, for example `BindAddress=0.0.0.0`.

**The `Origin` header is validated on every request**, also when CORS is
disabled. Loopback origins (`localhost`, `127.0.0.1`, `[::1]`, any port) always
pass; other origins must be listed in `[Security] AllowedOrigins` or, when that
is empty, in `[CORS] AllowedOrigins`. A rejected origin gets `403` with a
JSON-RPC error body. Browser front-ends on another host must be added to the
list (`https://app.example` or `https://app.example:*`).

**GET and DELETE on the MCP endpoint answer `405`.** The old GET answered a
small JSON document with the endpoint URL; configure `[Server] EndpointInfoPath`
(for example `/info`) to keep such a document on a path of its own.

**Notifications get `202` with an empty body**, no longer an HTML body.

**Modern requests (MCP 2026-07-28) get real HTTP status codes**: `400` for
malformed `_meta`, an unsupported protocol version or a header that does not
match the body, `404` for an unknown method. Requests from `initialize`-based
clients keep `200` for every JSON-RPC error, except a `400` for an
`MCP-Protocol-Version` header naming an unknown revision.

**Modern POSTs must carry `Mcp-Method`** and, for `tools/call`,
`resources/read` and `prompts/get`, **`Mcp-Name`** (Base64 sentinel encoding
accepted). A missing or different header is `400` with error `-32020`.

**Request limits**: bodies above `[Server] MaxRequestBodyBytes` (4 MB) get
`413`, JSON nested deeper than `[Server] MaxJsonDepth` (64) gets `400`.

**No `Mcp-Session-Id` is minted.** The `initialize` result no longer carries a
`sessionId`; an `Mcp-Session-Id` a legacy client sends is echoed back.

**SSE responses have no `id:` lines** and no duplicate `Connection` header.

**Responses stream when a tool sends notifications.** A request that accepts
`text/event-stream` and whose tool reports progress or logs (see
`IMCPRequestContext.ReportProgress` and `Log`) is answered with a chunked SSE
stream: the notifications first, the JSON-RPC response as the last event.
Such a stream is `200` even when the request ends in a JSON-RPC error,
because the status line has already been sent. Requests that send no
notification, and requests without `text/event-stream` in `Accept`, are
answered as before (single JSON object, or one SSE event, with a
`Content-Length`). Closing the stream cancels the request.

**TLS 1.0 and 1.1 are disabled** on the OpenSSL 1.0.2 handler (the build
without `USE_TAURUS_TLS`).

**Request and response bodies are logged at Debug level**, with `_meta`,
`requestState`, `inputResponses` and token-like members redacted. Lower
`TLogger.MinLogLevel` to see them.

## Tools and resources

**An unknown tool is a JSON-RPC error.** `tools/call` with a name that is not
registered answers `-32602` with `data.name`; it used to answer an `isError`
result with the text "Tool not found". A missing or empty `name`, or an
`arguments` that is not an object, is `-32602` too. Modern clients get HTTP
`400` with it, initialize-based clients `200`.

**An unknown resource is a JSON-RPC error.** `resources/read` answers `-32002`
with `data.uri` for initialize-based clients and `-32602` with `data.uri` for
modern clients; it used to answer a text content "Error: Resource not found".
A read that raises is `-32603`.

**Arguments are checked against the schema.** A missing required parameter, a
wrong JSON type (a string for a number, a fraction for an integer, a string
for a boolean) or an unknown enumeration name is an `isError` result naming
the parameter, before the tool runs. A parameter that may be absent needs the
`[Optional]` attribute; without it the old behaviour (silently defaulting)
is gone. `null` counts as absent.

**Generated schemas changed.** Integer properties are `integer` (they were
`number`), `TDateTime` is a `string` with `format: date-time`, enumerations
and sets list their names, and a tool without parameters declares
`additionalProperties: false`. Clients that validate arguments against the
schema now reject `1.5` for an integer.

**Result and resource JSON changed.** Enumerations are written by name (they
were booleans), sets and dynamic arrays as arrays, `nil` objects as `null`
and `TDateTime` as an ISO 8601 string. The `logs://recent` timestamps and the
`server://status` times are strings now.

**Tools and resources are listed in registration order.** Anything that
depended on the previous dictionary order should use the names instead.

**A typed tool result also gets a text block.** `TMCPToolBase<T, R>` results
carry `structuredContent` and a text block with the same JSON; `content` is
never empty.

## stdio transport

**Non-ASCII input is no longer mangled.** stdin and stdout are read and
written as UTF-8 byte streams now instead of Text I/O; a message with `é` or
an emoji comes back unchanged. A client that worked around the old mangling
should remove that workaround.

**Requests are answered one at a time by default, still in arrival order.**
Set `[Server] MaxConcurrentRequests` above 1 for a client that issues several
requests before waiting for a reply and wants them handled in parallel.

**`notifications/cancelled` now does something.** Sending it for a request
still in flight stops that request and it gets no response, matching the
specification; previously the notification was accepted but ignored.

**A request with `_meta.progressToken` gets `notifications/progress`** from
tools that report progress (`test_tool_with_progress` is the example); this
is new traffic on stdout a client that does not expect it should tolerate,
since it was already required by the specification.

**The server exits promptly when stdin closes**, even with a request still
running: it waits `[Server] MaxConcurrentRequests`-many workers up to 2
seconds (configurable via `TMCPStdioTransport.ShutdownDrainMs` for a library
consumer), then cancels what is left rather than blocking forever.

**A duplicate request id while the first is still in flight is `-32600`**,
answered immediately, instead of being silently queued behind it.

## Prompts, resource templates and completion

**New capabilities, off unless you register the managers.** A registry that
never registers `TMCPPromptsManager` or `TMCPCompletionManager` behaves
exactly as before; the built-in `MCPServer.dpr`/stdio server registers both,
so the shipped executable now advertises `prompts` and `completions` and
answers `prompts/list`, `prompts/get`, `resources/templates/list` (with real
entries instead of an empty array) and `completion/complete`.

**A hand-written tool (`TMCPToolBase`) now validates its arguments.**
Override `DoExecute` instead of `Execute`; the base class validates
`Arguments` against `BuildSchema` first and raises `EArgumentException` (an
`isError` result) on a mismatch. `TMCPToolBase<T>` and `TMCPToolBase<T, R>`
tools are unaffected.

## Multi round-trip requests

**Server-initiated requests are replaced by `InputRequiredResult`.** A tool,
resource or prompt that needs something from the client (`elicitation/create`,
`sampling/createMessage`, `roots/list`) raises `EMCPInputRequired`
(`MCPServer.Mrtr`) with the input requests and optional state; the modern
client retries with `inputResponses` and `requestState`, which the request
context exposes as `InputResponses`, `TryGetInputResponse` and
`RequestState`. Nothing changes for tools that never ask the client for
input. A legacy client (2025-06-18, 2025-11-25) gets `-32603` from such a
request, because those revisions delivered the same thing as server-to-client
requests that this server does not send.

**`requestState` is signed.** Set `[Security] RequestStateKey` when more than
one instance serves the same clients or when tokens must survive a restart;
without it every process signs with its own random key and logs a warning
at startup. `RequestStateTtlSeconds` bounds the replay window (600 s).

**Two new settings keys** (`RequestStateKey`, `RequestStateTtlSeconds`) and
nine new example tools plus one example prompt ship with the executable;
they are only registered when their units are in the project.

## Host allow-list and diagnostics resources

**`[Security] AllowedHosts` is empty by default**, so nothing changes until it
is set; then a request whose `Host` header is not listed gets `403`.

**`[Server] ExposeDiagnosticsResources=0` drops `logs://recent`,
`logs://{level}` and `server://status`** from the shipped executable. The
default keeps them, as before. A library that registers the resources itself
uses `RemoveResource` and the new `RemoveResourceTemplate` on
`TMCPResourcesManager` to the same effect.

## Authentication

**Opt-in, and only over HTTP.** Nothing changes until `[Auth] BearerTokens`
is set or a library assigns `TMCPIdHTTPServer.Authorizer`. From then on every
request to the endpoint needs `Authorization: Bearer <token>`; `OPTIONS` and
`GET /.well-known/oauth-protected-resource[<Endpoint>]` stay open. Legacy and
modern clients get the same `401`/`403`/`400` answers with a
`WWW-Authenticate: Bearer` challenge and an id-less JSON-RPC error body.

**`[RequiresScope]` tools answer `403` without the scope**, also to legacy
clients (their JSON-RPC errors otherwise travel in `200`). The response carries
`WWW-Authenticate: Bearer error="insufficient_scope", scope="..."` and
`error.data.requiredScope`.

**`TMCPRequestContext.Create` and `TMCPTransportHints` gained `Principal` and
`Scopes`.** The request state sealer binds `requestState` tokens to the
principal now, so a token obtained by one authenticated caller is rejected
when another caller presents it.

## Subscriptions

**`subscriptions/listen` replaces `resources/subscribe` and the GET stream.**
The shipped executable registers `TMCPSubscriptionsManager` and assigns it as
`ChangeNotifier` of the tools, prompts and resources managers, so
`server/discover` now announces `tools.listChanged`, `prompts.listChanged`,
`resources.listChanged` and `resources.subscribe` to modern clients (the
`initialize` result for legacy clients still says `false`: those clients have
no stream to receive the notifications on). A library that registers the
managers itself keeps the old behaviour until it does the same.

**Adding or removing a tool, prompt or resource at run time notifies
subscribed clients.** `AddTool`, `AddPrompt`, `AddResource` and
`AddResourceTemplate` were already there; `RemoveTool`, `RemovePrompt`,
`RemoveResource`, `HasTool`, `HasPrompt` and
`TMCPResourcesManager.ResourceUpdated` are new. The managers guard their
lists with a lock now, so run-time changes are safe from any thread.

**Shutdown waits for subscriptions.** `TMCPIdHTTPServer.Stop` and the end of
stdin close the open subscriptions with a completion response before the
transport goes down (up to one second, or the stdio drain time).

## Library use

- `TMCPJsonRpcProcessor.ProcessRequest` and the manager interfaces are
unchanged. `ProcessRequestEx` returns the HTTP status your own transport
should answer with.
- `TMCPToolBase<T>` gains `ExecuteWithContext(Params, Context): TValue`;
override it to return a `TMCPToolResult` (images, audio, embedded
resources, resource links, `_meta`) or to read the request context.
`ExecuteWithParams` keeps working as before. Raise `EMCPToolError` for a
failure the model should see as an `isError` result; any other exception
is reported the same way with its message.
- `TMCPResourceBase<T>` has `FTitle`, `FSize`, `FAnnotations`, `FTtlMs` and
`FCacheScope` for the list and read results; implement `IMCPBinaryResource`
for a `blob` resource.
- `TMCPToolsManager.CallTool` raises `EMCPError` (-32602) for an unknown tool
instead of returning an error result; `TMCPResourcesManager.ReadResource`
raises `EMCPError` for an unknown URI. Both have era-aware overloads.
- `TMCPCoreManager.SessionID` returns an empty string.
- `initialize` answers the requested revision (`2025-06-18` or `2025-11-25`)
and its `capabilities` come from the registered managers; a registry with
only a tools manager no longer advertises resources.
- Batch arrays, `id: null`, a missing `method` or `jsonrpc` are answered with
`-32600`; a non-object `params` with `-32602`.
- `TMCPStdioTransport.Create` forces stderr logging.
- `USE_TAURUS_TLS` moved from `MCPServer.IdHTTPServer.pas` to
`src\MCPServer.inc`; add `src` to your include path.
Loading