Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,11 @@ Haskell-specific — any MCP server implementation that reproduces the small
reference server described in the corpus README can replay the requests and
diff the responses. Contributions of new cases are welcome.

## Roadmap

Design decisions and planned work live as ADRs under
[`specs/`](specs/README.md), ordered by [`specs/ROADMAP.md`](specs/ROADMAP.md).

## Examples

The library includes several examples:
Expand Down
2 changes: 2 additions & 0 deletions specs/SPEC.md → specs/ADR_0001_original_spec.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
> **ADR_0001 — Historical.** This document predates the ADR convention: it is preserved as-written (a working note from the time), renumbered for the index. New ADRs follow the format described in [README.md](README.md).


# NOTICE

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
> **ADR_0002 — Historical.** This document predates the ADR convention: it is preserved as-written (a working note from the time), renumbered for the index. New ADRs follow the format described in [README.md](README.md).

# Support for better parameter types

Currently, the mcp-server library supports defining Tools, Prompts and Resources like the following:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
> **ADR_0003 — Historical.** This document predates the ADR convention: it is preserved as-written (a working note from the time), renumbered for the index. New ADRs follow the format described in [README.md](README.md).

# Test Suite Improvement Recommendations

## Current State Analysis
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
> **ADR_0004 — Historical.** This document predates the ADR convention: it is preserved as-written (a working note from the time), renumbered for the index. New ADRs follow the format described in [README.md](README.md).

Update the MCP Protocol version from: 2025-03-26 to 2025-06-18.

**BREAKING CHANGE**: Remove backward compatibility. Server will ONLY support 2025-06-18.
Expand Down
48 changes: 48 additions & 0 deletions specs/ADR_0005_derived_output_schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ADR 0005: Derived output schemas and structured content

- **Status**: Proposed
- **Date**: 2026-08-01
- **Depends on**: —

## Context

Since 0.2.0 the protocol layer carries `outputSchema` on `ToolDefinition`
and `structuredContent` on tool results, but only manual handlers can
populate them — the Template Haskell derivation always emits
`toolDefinitionOutputSchema = Nothing`. Meanwhile the library's entire
thesis is that schemas fall out of ADTs: `inputSchema` is already derived
from the tool constructor's fields.

The spec (2025-06-18+) also recommends that when a result carries
`structuredContent`, the serialized JSON is additionally included as a text
content block for clients that predate structured output.

## Decision

Extend the derive layer so a tool handler can return a typed result and get
the full structured-output story for free:

- A new derivation (working name `deriveToolHandlerWithOutput`, or an
output-type annotation on the existing one) maps the handler's result
record to a generated `outputSchema` using the same `Schema` machinery as
input derivation (primitives, `Maybe`, lists, all-nullary enums, nested
records).
- At call time the typed result is serialized into `structuredContent`, and
the library also appends the spec-recommended text block containing the
same JSON, unless the handler supplied its own content.
- Plain `ToToolResult` handlers are untouched — this is opt-in per tool.

## Consequences

- Completes the typed core end-to-end (typed in, typed out); no other MCP
library derives both sides declaratively.
- New exports → PVP minor bump; can join the pending 0.2.1.x line.
- The schema generator is shared with input derivation, so schema-feature
work (e.g. richer JSON Schema keywords) benefits both directions.
- Golden corpus gains structured-output cases in both eras.

## References

- MCP 2025-06-18 changelog (structured tool output, `outputSchema`)
- 2026-07-28 loosened `outputSchema` to any JSON Schema 2020-12 keywords
(SEP-2106)
47 changes: 47 additions & 0 deletions specs/ADR_0006_definition_metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# ADR 0006: Tool annotations, icons, content annotations

- **Status**: Proposed
- **Date**: 2026-08-01
- **Depends on**: —

## Context

Three metadata features from recent protocol revisions are missing from our
definition and content types:

- **Tool annotations** (2025-03-26): `readOnlyHint`, `destructiveHint`,
`idempotentHint`, `openWorldHint` on tool definitions. Clients use these
for permission UX (e.g. auto-approving read-only tools), so their absence
degrades how our servers are treated.
- **Icons** (2025-11-25): optional icon lists on tool/prompt/resource
definitions.
- **Content annotations** (2025-03-26): `audience`, `priority`,
`lastModified` on content blocks. The 0.2.0 plan named these ("Content
grows audio/resource-link/annotations") but the annotations part did not
land.

## Decision

- Add an `Annotations` record to `Content` variants (optional field,
omitted from JSON when absent) and `ToolAnnotations`/icon fields to the
definition types, serialized per spec.
- Extend the derive customization API: alongside the existing
`[("Constructor", "Description")]` mechanism, accept per-constructor
options (a small record — working name `ToolOptions` — carrying
annotations, icon, title) via a `WithOptions` variant. The plain
string-pair API stays as-is.

## Consequences

- Cheap, additive, PVP minor; a natural companion to ADR_0005 in the same
release batch.
- The `WithOptions` design decides the shape of all future per-constructor
customization — worth a moment of API design so we don't accrete N
`WithX` variants.
- Legacy golden fixtures are unaffected (new fields are omitted when
unset).

## References

- MCP 2025-03-26 changelog (ToolAnnotations, content annotations)
- MCP 2025-11-25 changelog (icons)
49 changes: 49 additions & 0 deletions specs/ADR_0007_progress_notifications.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# ADR 0007: Progress notifications and per-request SSE

- **Status**: Proposed
- **Date**: 2026-08-01
- **Depends on**: —

## Context

A client may attach a `progressToken` to any request's `_meta`; the server
may then emit `notifications/progress` (progress, optional total, optional
message) while the request runs. We support none of this: handlers have no
way to report progress, and on HTTP every request is answered with a single
JSON object — whereas the Streamable HTTP spec expects request-scoped
notifications to flow on an SSE response stream for that request, which
clients MUST support. (Per-request `notifications/message` logging via the
`io.modelcontextprotocol/logLevel` `_meta` key rides the same mechanism and
can come along nearly for free.)

## Decision

- `ClientContext` gains a `reportProgress` action (progress → maybe total →
maybe message → IO ()). It is a no-op when the request carried no
`progressToken`, so handlers can call it unconditionally.
- **stdio**: progress notifications are written to the shared stdout
channel (under the existing write lock), interleaved before the response
— the spec's normal shape for stdio.
- **HTTP**: when a request carries a `progressToken`, the transport answers
with `Content-Type: text/event-stream` and streams progress notifications
followed by the final response, reusing the SSE plumbing built for
`subscriptions/listen` (`X-Accel-Buffering: no`, keep-alive comments).
Requests without a token keep the single-JSON-object response.
- Optionally in the same change: honor `io.modelcontextprotocol/logLevel`
by giving `ClientContext` a client-log action with the same delivery
rules (never emitted when the key is absent, per spec MUST NOT).

## Consequences

- Handler execution on HTTP moves inside a streaming response body; this is
the structural change that ADR_0008 (cancellation) builds on — do this
first.
- `ClientContext` grows; `anonymousContext` keeps construction stable.
- Golden corpus stays single-message per case; progress ordering gets its
own streaming tests (like the live subscription driver).

## References

- MCP 2026-07-28 Streamable HTTP: per-request SSE response streams
- Progress pattern: /specification/2026-07-28/basic/patterns/progress
- Logging via `_meta` logLevel: 2026-07-28 changelog (SEP-2575)
46 changes: 46 additions & 0 deletions specs/ADR_0008_request_cancellation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ADR 0008: Cancellation of in-flight requests

- **Status**: Proposed
- **Date**: 2026-08-01
- **Depends on**: ADR_0007

## Context

Handlers currently run synchronously to completion. `notifications/cancelled`
on stdio is honored only for subscription streams; a cancelled long-running
tool call still burns to the end, and on HTTP a client closing the response
stream (the modern cancellation signal — servers SHOULD stop work and MUST
NOT send further messages for the request) goes unnoticed.

Alone this is plumbing without payoff: until ADR_0007 moves handler
execution into an async/streaming model, there is nothing to interrupt. It
only makes sense on top of that structure — which is why it is a separate
ADR sequenced after it.

## Decision

- Run request handlers in `async` tasks (new dependency or plain
`forkIO`+`MVar`).
- **stdio**: track in-flight requests by id; `notifications/cancelled`
cancels the async, and nothing further is written for that id.
- **HTTP**: detect response-stream closure (WAI write failure / Warp
connection teardown) and cancel the handler's async.
- Handlers opt into cooperative interruption simply by being interruptible
Haskell code (blocking calls, `IO`); we document that `killThread`-style
async exceptions are the mechanism, and that handlers needing cleanup
should use `bracket`.

## Consequences

- Concurrency surface grows: responses may now interleave differently on
stdio; the write lock and per-request bookkeeping need the same care as
the subscription registry.
- Async-exception safety becomes part of the handler contract and must be
documented prominently.
- Spec compliance: "SHOULD stop work as soon as practical, MUST NOT send
any further messages for it" becomes true instead of aspirational.

## References

- Cancellation pattern: /specification/2026-07-28/basic/patterns/cancellation
- Streamable HTTP: stream closure as cancellation (2026-07-28)
50 changes: 50 additions & 0 deletions specs/ADR_0009_mrtr_input_required.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# ADR 0009: MRTR — input_required results (elicitation)

- **Status**: Proposed
- **Date**: 2026-08-01
- **Depends on**: —

## Context

Revision 2026-07-28 replaced server-initiated requests (elicitation,
sampling, roots) with the Multi Round-Trip Request pattern: a server that
needs client input returns an `InputRequiredResult`
(`resultType: "input_required"`) whose `inputRequests` carry what it needs
(e.g. an `elicitation/create` payload); the client gathers the input and
**retries the original request** with `inputResponses` attached, plus any
opaque `requestState` the server included for correlation.

We implement none of this — a tool handler cannot ask the user anything
mid-call. This is the largest missing capability class that is
spec-current. (Legacy `elicitation/create` as a server-initiated request is
deliberately out of scope: our transports do not send server-initiated
requests, and the feature is superseded.)

## Decision

- `ToolResult` (or a wrapping result type) gains an `input_required`
variant carrying typed `inputRequests` and an opaque `requestState`.
- The handler API lets a tool either complete or request input; on retry,
the handler receives the `inputResponses` and its own `requestState`
back. Because the server is stateless, all correlation state rides in
`requestState` — the library treats it as an opaque JSON value.
- Dispatch stamps `resultType: "input_required"` and the MRTR fields on the
modern envelope; legacy clients never see the variant (a handler
returning it to a legacy request gets a defined error, since legacy has
no retry semantics).
- A TH nicety can follow later: derive the elicitation input schema from a
record type, same machinery as ADR_0005.

## Consequences

- The headline feature of a future minor/major release; touches the result
types, so design it before (or with) any other result-shape change.
- Interacts with the era model: input_required is modern-only, another
place the dual-era split must stay honest.
- Golden corpus gains retry-cycle cases (initial → input_required → retry →
complete).

## References

- MRTR pattern: /specification/2026-07-28/basic/patterns/mrtr (SEP-2322)
- 2026-07-28 changelog items 7–8 (InputRequiredResult, resultType)
46 changes: 46 additions & 0 deletions specs/ADR_0010_oauth_resource_metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ADR 0010: OAuth protected-resource metadata

- **Status**: Proposed
- **Date**: 2026-08-01
- **Depends on**: —

## Context

Our HTTP auth story is a deliberately minimal callback (`httpAuthorize`
receives the bearer token, returns a principal or rejects). The MCP
authorization spec expects a compliant HTTP server to also act as an OAuth
2.0 *protected resource*: serve RFC 9728 metadata at
`/.well-known/oauth-protected-resource` naming its authorization servers,
and return `401` with a `WWW-Authenticate` header pointing at that metadata
so clients can discover how to obtain a token. This discovery layer is what
lets MCP clients (Claude, inspector tooling) drive an OAuth flow
automatically — and it is the main functional advantage dpella/mcp
currently has (servant-auth JWT support).

## Decision

- Keep `httpAuthorize` as the validation primitive (the library still never
validates tokens itself).
- Add an optional `httpResourceMetadata` config: when set (authorization
server URLs, resource id, scopes), the transport serves the RFC 9728
document at the well-known path and enriches `401` responses with the
spec-shaped `WWW-Authenticate` header.
- Token validation guidance (JWT verification against the AS's JWKS) goes
in documentation/examples rather than the library, avoiding a heavy
crypto dependency footprint; a separate optional package could offer a
ready-made JWT `httpAuthorize` later.

## Consequences

- Compliant discovery without hardcoding any particular token format; the
library stays dependency-light.
- Closes the practical gap with dpella/mcp for browser/agent-driven auth.
- Needs care with `httpEndpoint` vs the well-known path when users embed
`mcpApplication` in a larger router (document that the well-known path is
served at the root, or expose it as a second WAI app).

## References

- MCP authorization spec (2025-06-18+), RFC 9728, RFC 8414
- 2026-07-28 auth changes: RFC 9207 `iss` validation, Client ID Metadata
Documents (client-side; server impact is documentation only)
42 changes: 42 additions & 0 deletions specs/ADR_0011_protocol_completeness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# ADR 0011: Pagination and the extensions capability

- **Status**: Proposed
- **Date**: 2026-08-01
- **Depends on**: —

## Context

Two small spec features round out protocol completeness:

- **Pagination**: list operations accept a `cursor` param and may return
`nextCursor`. Clients MUST support pagination; servers MAY paginate. We
never paginate — fine for typical derived servers (a handful of tools),
a real limitation for the manual-handler user with hundreds of resources.
- **Extensions capability** (2026-07-28): `ServerCapabilities` gained an
`extensions` map (identifier → settings object) for negotiating optional
extensions. We have no way to populate it, which blocks advertising any
extension (including tasks, ADR_0012).

## Decision

- Add an opt-in page size to the list handlers' dispatch: when configured,
the library slices the handler-returned list and mints opaque cursors
(offset-based, encoded, treated as opaque by clients per spec). Handlers
keep returning full lists — pagination is a dispatch concern, keeping the
derive API untouched. (A streaming/chunked handler API is deliberately
out of scope until someone needs it.)
- Add `serverExtensions :: Map Text Value` to server configuration,
advertised in capabilities in both eras' capability objects.

## Consequences

- Additive, PVP minor; low urgency — schedule opportunistically or when a
user asks.
- Cursor stability across list changes is documented as best-effort (the
spec allows invalid-cursor errors: `-32602`).
- The `extensions` map is a prerequisite for ADR_0012.

## References

- Pagination: /specification/2026-07-28/server/utilities/pagination
- Extensions: 2026-07-28 changelog (minor change 1), versioning page
Loading