# Changelog
All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
This release makes the server enforce the MCP specification by default. Several behaviors
that were previously absent or lenient are now always on; see Changed for the breaking
details and how to adapt.
### Added
- Session lifecycle limits: `:max_sessions` (reject new sessions with `503` past a cap —
enforced atomically, before the server's `init/1` runs, so a rejected session pays no
init cost and the cap holds under concurrent initializes), `:session_idle_timeout`
(terminate after inactivity; a session serving a request is not reaped) and
`:session_max_lifetime` (terminate a fixed time after creation). All default to `nil`
(unlimited) and are validated as positive integers at startup. Session processes are now
`restart: :temporary` so an ended session is never resurrected under its old id.
- Declarative tool scopes: `tool "name", scopes: ["files:write"], ...` enforces the scopes
against `ctx.auth` before the handler runs, failing closed when the request carries no
authorization.
- `:expose_internal_errors` transport option (default `false`). Unexpected exceptions and
malformed handler returns are now logged in full but return a generic message to the
client; enable the option to surface the detail in development. Deliberate `Urchin.Error`
values and `{:error, message}` returns are never redacted — their `message`/`data` reach the
client unchanged.
- Capability guards: `Urchin.Context.create_message/3`, `elicit/3` and `list_roots/2`
return an error without contacting the client when it did not advertise the matching
`sampling`/`elicitation`/`roots` capability.
- `415 Unsupported Media Type` for POST requests whose `Content-Type` is not
`application/json`.
- `SECURITY.md` with a threat model, deployment checklist and vulnerability reporting.
- `:sse_buffer_limit` transport option (default `nil`, preserving the session's internal
default of `100`) forwarding the per-session GET-stream replay buffer size to the session;
previously only configurable on `Urchin.Session` directly.
### Changed
The following are now enforced by default, with no opt-out, for MCP spec compliance. They are
breaking relative to `0.2.0`.
- A DSL tool's `tools/call` arguments are validated against its `input_schema` before the
handler runs; a mismatch is returned as a `CallToolResult` with `isError: true` so the model
can self-correct. A tool that declares no `input_schema` now defaults to an object that
accepts no properties (`additionalProperties: false`), so unexpected arguments are rejected —
declare an explicit `input_schema` to accept arbitrary fields. A non-object `arguments` value
is a malformed request and remains a JSON-RPC `invalid_params` error. Servers that implement
`call_tool/3` by hand validate their own arguments. `Urchin.Schema` implements the supported
(minimal) JSON Schema subset.
- Operation requests received before the client sends `notifications/initialized` are rejected
with `invalid_request`; only `ping` is allowed before initialization (the lifecycle's
pings-and-logging exception is for the server's own requests, not the client's
`logging/setLevel`). Clients must complete the lifecycle handshake before issuing other
requests.
- A `tools/call` handler's `{:error, message}` (string) is returned as a `CallToolResult` with
`isError: true` so the model can self-correct. A protocol error returned as
`{:error, %Urchin.Error{}}` is always a JSON-RPC error. (Previously a string handler error
became a JSON-RPC internal error.)
- Duplicate literal tool names within a server are rejected at compile time (a silently shadowed
duplicate was previously accepted, with the last declaration winning); non-literal names (a
variable or expression) cannot be compared statically and are not checked. Urchin enforces no
tool-name pattern (the MCP schema imposes none); servers should still follow the MCP naming
recommendations.
- `initialize` requires `protocolVersion` (string), `capabilities` (object) and `clientInfo`
(with a string `name` and `version`); a missing or mistyped field is an `invalid_params`
error rather than a silently-defaulted value. The server's `serverInfo` must likewise carry a
string `name` and `version`.
- The `MCP-Protocol-Version` header is validated on `DELETE`, matching `POST` and `GET`.
- `completion/complete` request params are validated (`ref` as a `ref/prompt`/`ref/resource`
union, `argument.name`/`value` as strings, `context.arguments` values as strings) and return
`invalid_params` when malformed. Results are capped at 100 values — a handler returning more is
truncated to the top 100 (already ranked by relevance) with `hasMore` set — and a
non-conforming result shape (non-string `values`, etc.) is an internal error.
- A tool's `input_schema` and `output_schema` must be JSON Schema objects whose root `type` is
`"object"` (per the MCP tools spec); the DSL rejects a non-conforming schema at compile time.
- `logging/setLevel` is now a library builtin: when the server advertises the `logging`
capability (via `use Urchin.Server, logging: true`) it succeeds and applies the level to the
session even without a `set_log_level/2` callback. The level is validated against the MCP log
levels (`invalid_params` otherwise), an exported `set_log_level/2` still runs as a hook, and
the session level is updated only after the hook succeeds. Servers that do not advertise
`logging` return `method_not_found`.
### Fixed
- README no longer claims unqualified "resumable SSE streams"; resumption is scoped to the
GET stream, matching the implementation.
## [0.2.0] - 2026-06-05
### Added
- Optional OAuth 2.1 authorization (`Urchin.Auth`). Urchin can act as an OAuth 2.1
resource server: RFC 9728 Protected Resource Metadata discovery (served at the
well-known URI and advertised via `WWW-Authenticate: resource_metadata`), pluggable
token validation (`Urchin.Auth.TokenValidator`), RFC 8707 audience binding, scope
enforcement and `401`/`403`/`400` challenges. Enable it with the `:auth` option on the
transport, `Urchin.Endpoint` or `Urchin.start_link/2`, or compose the
`Urchin.Auth.Plug` and `Urchin.Auth.Metadata` plugs. Validated claims reach handlers as
`ctx.auth`. Authorization remains off by default.
## [0.1.0] - 2026-06-04
Initial release: a Model Context Protocol (MCP) server library implementing the
`2025-11-25` specification over the Streamable HTTP transport.
### Added
- Server authoring via the `Urchin.Server` behaviour and a
`tool`/`resource`/`resource_template`/`prompt` DSL with automatic capability
derivation.
- Tools, resources (plus templates and subscriptions), prompts, completion and
logging.
- Server-initiated requests over SSE: sampling, elicitation and roots.
- Progress notifications, cancellation, pagination and resumable SSE streams.
- A mountable `Plug` (`Urchin.Transport.StreamableHTTP`) and a standalone Bandit
endpoint (`Urchin.Endpoint`, `Urchin.start_link/2`), plus `Urchin.broadcast/2`
for fan-out notifications.
[0.2.0]: https://github.com/urth-inc/urchin/releases/tag/v0.2.0
[0.1.0]: https://github.com/urth-inc/urchin/releases/tag/v0.1.0