Skip to main content

CHANGELOG.md

# 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