All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

0.10.1 - 2026-08-05

Follow-up hardening from a re-review of the 0.9.4–0.9.7 changes (PRs #13–#17).

Security

  • Owner-scoped tasks/* routes (IDOR/BOLA). The experimental tasks/get, tasks/cancel, tasks/result, and tasks/list routes keyed purely on the client-supplied taskId, so any caller could read or cancel any task (and tasks/list returned every task system-wide). They now scope to the caller's principal via a new owner-aware ConduitMcp.Tasks API. Scoping is opt-in and back-compatible: a request with no principal, or a task created without an owner, behaves exactly as before; a principal mismatch returns {:error, :not_found} without leaking the task's existence. Stamp ownership with ConduitMcp.Tasks.create/3 and configure principal extraction with :task_owner_fun (default conn.assigns[:current_user] — return a stable scalar such as sub/id, since ownership is checked by exact match).
  • JWKS SSRF posture and stale-key window documented in the ConduitMcp.OAuth.KeyProvider.JWKS moduledoc: jwks_uri is trusted operator config (private/link-local addresses are still fetched, not blocked — harden egress if the URI is less-trusted), and during a JWKS outage cached keys keep validating until :stale_max_age (default 24h) before failing closed.

Added

Fixed

  • JWKS ETS cache could crash a concurrent request. The cache table was created by whichever request first fetched keys and destroyed when that process exited, so a concurrent request could hit an :ets ArgumentError on the authentication path. The table is now owned by a supervised process (ConduitMcp.OAuth.KeyProvider.JWKS.Owner, started from ConduitMcp.Application), which also lets the cache persist across requests as intended. Complements the 0.10.0 fix that tolerates losing the check-then-create race.

Changed

  • Example app (examples/oban_tasks_server/) and guide hardening — the Oban exception→telemetry bridge now marks a task "failed" on its final attempt (the previous job.state guard never fired, since the state is "executing" at exception time); Tasks.Store.cancel/1 updates the row before cancelling the Oban job (so a failed write can't strand a "working" row); client-supplied durations are validated; the Oban dep is pinned to ~> 2.22.0 (raw-SQL coupling to internal tables); a logger note warns that crash metadata can leak job args; and the guides/oban_tasks.md worker example is corrected.

0.10.0 - 2026-08-05

Security

  • Patched every dependency carrying a published advisory. Verified against OSV across all 47 locked packages, which now report none.

    PackageFromToAdvisories fixed
    plug1.19.21.20.3quadratic-time decoding of nested query/body params (high, CVE-2026-54892); multipart :length not charged for part headers, enabling unbounded temp-file creation (medium, CVE-2026-56814); cookie attribute injection in Plug.Conn.Cookies.encode/2 (low, CVE-2026-56813)
    bandit1.11.11.12.4quadratic CPU blow-up reassembling fragmented WebSocket messages (high, CVE-2026-65623)
    mint1.7.11.9.3CONTINUATION/HEADERS flood (high); unbounded streams map growth via PUSH_PROMISE (high); request-line CRLF injection (low); Content-Length + prefix (moderate)
    req0.5.170.7.2unbounded archive/compression extraction driven by response content-type (high); multipart header injection (moderate)

    The plug and bandit advisories are the ones that matter for a deployed server: both are remote denial-of-service reachable through the transports on any request, with no configuration required. The mint and req ones reach this library only through the optional JWKS key provider — where the decompression advisory is genuinely reachable rather than merely present, since ConduitMcp.OAuth.KeyProvider.JWKS caps the JWKS body at 1MB but Req decompresses before that cap applies.

    No declared constraint changed: ~> 1.19, ~> 1.9 and ~> 0.5 all already permitted the patched releases, so only mix.lock moved. bandit 1.12 requires thousand_island ~> 1.5, so that moved too (1.4.3 -> 1.5.0); plug_crypto (2.1.1 -> 2.2.0), finch (0.20.0 -> 0.23.0), hpax and castore followed as transitives. The JWKS moduledoc now recommends {:req, "~> 0.6"}.

  • JWT algorithm allow-listConduitMcp.Plugs.OAuth now validates the token header alg against an allow-list (new :algorithms option, default: RS/ES/PS families) before key lookup, and requires the resolved signing key to match the header algorithm's family. Static oct keys implicitly allow their HS algorithms, so existing HMAC setups keep working. Set :algorithms explicitly if you rely on a non-default algorithm.

  • JWKS fetch hardening — the JWKS key provider now requires https (override with allow_insecure_jwks: true for dev), no longer follows redirects, applies connect/receive timeouts, and caps responses at 1MB. A failed refresh now serves previously cached keys with a logged warning instead of failing.

  • WWW-Authenticate header hygiene — config-sourced values (resource URI, scopes) are stripped of CR/LF/quotes before header interpolation. The SSE endpoint event URL can now be pinned with the new :base_url option; the Host-header fallback is sanitized.

  • Origin-validation startup warning — transports log a warning at init when :allowed_origins is unset (DNS-rebinding exposure for browser-reachable servers). Pass allowed_origins: "*" to opt out explicitly.

Changed

  • Validation min_length/max_length now count graphemes (String.length/1) instead of bytes — multi-byte UTF-8 input is no longer over-counted. Behavior change for non-ASCII parameter values.
  • Custom-constraint validation reports all violations at once instead of halting at the first failing parameter — clients fixing bad input no longer need one round-trip per error.

Fixed

  • HS (HMAC) token verification crashed — the OAuth plug passed the oct JWK map to Joken.Signer.create/2, which requires the raw binary secret; static oct keys now work. Malformed signing keys now yield a 401 instead of crashing the request process.
  • ETS table-creation races in the JWKS cache and session store could crash a request under concurrent cold start; both now tolerate losing the check-then-create race (matching the tasks/cancellation stores).
  • Unknown JWK key types silently fell back to RS256, masking misconfiguration with a confusing signature error; they are now rejected with a logged warning.
  • .credo.exs accidentally disabled nearly the whole default check suite (enabled: replaces the suite; extra: adjusts it). The full suite now runs locally and in CI.

Deprecated

  • Leaving :allowed_origins unset logs a warning; a future major release will require an explicit Origin policy for browser-reachable transports.

Added

  • require_session: true session option for ConduitMcp.Transport.StreamableHTTP — rejects non-initialize POSTs without an Mcp-Session-Id header (HTTP 400), per the MCP specification.
  • :base_url option for ConduitMcp.Transport.SSE and :algorithms for ConduitMcp.Plugs.OAuth (see Security).
  • resources/templates/list method — MCP-spec-required endpoint for discovering URI-templated resources. Templated resources (URIs with {param} placeholders) now appear here instead of in resources/list. New optional handle_list_resource_templates/1 Server callback.
  • notifications/cancelled handling via the new ConduitMcp.Cancellation module. Tool authors poll Cancellation.cancelled?(conn) to cooperatively abort long-running work. Handler stashes the request id in conn.assigns[:mcp_request_id] and emits [:conduit_mcp, :request, :cancelled] telemetry.
  • Capability advertisement for completions, logging, and resources.subscribe — previously these features were routed but never declared on initialize, so spec-compliant clients never used them.
  • Tool schema fields title, icons, outputSchema, and execution.taskSupport (MCP 2025-11-25). New DSL macros: title/1, icons/1, output_schema/1, task_support/1.
  • Tasks JSON-RPC methodstasks/get, tasks/cancel, tasks/result, tasks/list routed to ConduitMcp.Tasks.
  • task/2 DSL helper for returning a task id from a long-running tool invocation.
  • ConduitMcp.Session.Janitor — opt-in GenServer that periodically prunes expired sessions from the ETS-backed store. Add to your supervision tree to bound memory growth on public-facing servers.
  • ConduitMcp.Tasks.delete/1, cleanup/1, and Tasks.Janitor — parallel cleanup for the tasks table, which previously had no eviction at all.
  • ConduitMcp.Tasks.Store behaviour — task storage is now pluggable, mirroring ConduitMcp.Session.Store. Configure via config :conduit_mcp, :tasks_store, MyApp.MyTasksStore. The default remains the in-memory ConduitMcp.Tasks.EtsStore, so existing servers keep their current behaviour without any changes. Standard tasks/* JSON-RPC routes dispatch through the configured store, so swapping in a durable backend (e.g., Oban + SQLite or Postgres) requires no handler changes.
  • examples/async_tasks_server/ — runnable example demonstrating the MCP 2025-11-25 tasks lifecycle (in-memory, ETS-backed).
  • examples/oban_tasks_server/ — runnable example demonstrating the same lifecycle backed by Oban + SQLite for durability, with the input_required state exercised via {:snooze, _}.
  • Object parameters actually work. :object params (ConduitMcp.DSL) and :object fields (ConduitMcp.Component.Schema) were dead code: every declaration form crashed at compile time, and the one shape that compiled was rejected at runtime by the validator. All forms now compile and validate — blockless (open) objects, block objects with declared fields, objects nested in objects, and items :object inside an :array.
  • Nested runtime validation. A declared object's nested fields are now enforced by NimbleOptions to any depth: required fields, types, and the custom constraints (enum, min/max, length limits, validator). Errors name the full path (bag.inner.city). Undeclared nested keys are rejected with an actionable message instead of NimbleOptions' expected atom, got: "zzz". Nested keys are atomised only when they match a declared field name, so client input can never mint an atom.
  • additional_properties: option for :object params and fields — true enforces the declared fields and passes undeclared keys through to the handler; false (the default once fields are declared) rejects them. It also drives "additionalProperties" in the generated JSON Schema, which is now always emitted for objects so the published schema matches what the server enforces.
  • items/1,2 in ConduitMcp.Component.Schema — component-mode array fields had no way to declare an item type at all, and a bare field inside an :array block silently corrupted the parent field list. items is now the only thing an :array block accepts, at every nesting depth.
  • 3-arg (and 2-arg) block formsparam :bag, :object, "desc" do ... end and field :bag, :object do ... end used to bind to the blockless clause ([do: ...] is a keyword list), silently discarding the block; the form the field/4 docs themselves showed was broken. Both now work in both DSLs. A block on a type that has no block form raises a CompileError naming the file and line.
  • Type coercion now follows nested objects. A nested :integer field accepts the same "30" the top level does; previously coercion stopped at depth 0 while nested type checking did not, so the two disagreed.
  • The two DSL front ends now accept the same programs. ConduitMcp.DSL silently swallowed a bare field inside an :array block, an items outside one, and a field outside any object block, and raised a bare FunctionClauseError for items :string do ... end. All four are now CompileErrors naming the file and line, matching Component.Schema. The compile-time scope plumbing both DSLs share moved into one place.

Fixed

  • DSL empty-map type warning under Elixir 1.20__scope_for_tool__/1 in DSL-mode servers without any scoped tools used to expand to Map.get(%{}, _), which Elixir 1.20's type checker flags as always returning the default. Same fix as commit 0f05a9f for Endpoint mode: replace the single-map lookup with per-tool function clauses plus a catch-all. Servers that never declared OAuth scopes now compile cleanly under --warnings-as-errors.
  • min:/max:/validator: were bypassable by sending a number as a string. Custom constraints are checked by this library rather than NimbleOptions, and the checks ran before type coercion — so check_min_value/3 skipped a binary value, coercion then turned it into a number, and the markers had already been stripped from the schema NimbleOptions sees. With type_coercion: true (the default), "5" passed min: 18 and the handler received 5. validator: was worse: the function was called with the uncoerced binary, and "5" > 18 is true in Erlang term order. Coercion now runs first, so every constraint sees the value the handler will see. Note enum: now matches after coercion too — enum: [1, 2, 3] accepts "1" for an :integer field, where it previously rejected it.

Changed

  • resources/list now returns only static URIs. Templated URIs move to resources/templates/list per spec. Clients that relied on templated URIs in resources/list were already non-spec-compliant.
  • Session.Store behaviour gained an optional cleanup/1 callback used by Session.Janitor. Existing stores that don't implement it continue to work; the janitor logs a warning and idles.
  • OAuth scope rejection on tools/call now returns a JSON-RPC error with the request's id (previously nil, breaking client correlation).
  • Validation errors now always carry a parameter. A type mismatch used to return parameter: nil and NimbleOptions' raw prose, while a missing required field returned the field name — so a client author had to special-case the failure kind to locate the field. Every error now names its parameter, dotted for nested fields (bag.inner.city). Type-error messages no longer carry NimbleOptions' internal (in options [:bag]) suffix, because the parameter says it.
  • Undeclared parameters are rejected with a proper error. Previously left to NimbleOptions, which reported no parameter name and — for a name that did not already exist as an atom — raised out of validation entirely, so the same mistake surfaced as either a validation error or an internal error depending on the VM's atom table. Now always %{"parameter" => name, "message" => "unknown parameter \"name\""}.

Removed

  • Validation telemetry events [:conduit_mcp, :validation, :started], [:conduit_mcp, :validation, :success], and [:conduit_mcp, :validation, :failed] are no longer emitted. They fired on every validated request (three per call, even with no handler attached) and were redundant with the existing [:conduit_mcp, :tool, :execute] event. Migrate attached handlers to [:conduit_mcp, :tool, :execute] whose metadata's :status field indicates :ok / :error and whose payload includes validation failures.
  • custom_constraint_markers/0 on ConduitMcp.Validation.SchemaConverter — replaced by strip_markers/1, which does the stripping itself and recurses into nested keys: schemas. Callers that fetched the marker list to do their own Keyword.drop/2 should call strip_markers/1 instead; four modules in this library did exactly that and now share the one implementation.

0.9.7 - 2026-06-18

Fixed

  • tools/call errors crashed when the request carried _metaHandler.maybe_add_meta/2 unconditionally wrote the request's _meta into ["result", "_meta"], but error responses (%{"error" => ...}) have no "result" key. With a _meta present (clients such as the Python MCP SDK send a progressToken on every tools/call), this raised "could not put/update key \"_meta\" on a nil value", which the handler masked as a generic "Internal server error" — so every tool that returned an error surfaced the wrong message. _meta is now only merged into responses that have a "result"; error responses pass through untouched.

[0.9.3] - 2026-04-18

Fixed

  • Empty-map type warnings under Elixir 1.20 — endpoints with no scoped tools and/or no prompts no longer trigger Map.get/2,3 "will always return default" warnings from @before_compile-generated __scope_for_tool__/1 and __convert_to_atom_keys__/2. Replaced single-map lookups with per-component function clauses (__scope_for_tool__(<<name>>) -> scope and __key_map__(<<name>>) -> escaped_map) plus catch-all fallbacks. mix compile --warnings-as-errors is now clean for read-only / tool-only endpoints.

[0.9.1] - 2026-03-24

Performance

  • persistent_term validation config — replaced Application.get_env with :persistent_term.get for O(1) lock-free config reads on every validated request (4–10% faster validation, 5–11% less memory)
  • Cached server capabilities — new ConduitMcp.ServerMeta module lazily caches all function_exported? results in persistent_term, eliminating 9 repeated BIF calls per request (2–7% faster handler dispatch)
  • Pre-computed clean schemas__validation_schema_for_tool__/1 now returns {full_schema, clean_schema} tuples pre-stripped of constraint markers at compile time, eliminating per-request Keyword.drop
  • Static resource URI dispatch — resources with no {param} placeholders now generate direct pattern-match clauses (O(1)) instead of linear regex scan (O(n))

Fixed

Added

[0.9.0] - 2026-03-22

Added

  • MCP Apps support — first-class support for the MCP Apps extension, enabling tools to return interactive UI components rendered as sandboxed iframes in host clients
    • meta/1 macro — attach arbitrary _meta metadata to tool definitions (generic, future-proof)
    • ui/1 macro — shortcut for declaring _meta.ui.resourceUri on a tool
    • app/2 macro — convenience that registers both a tool (with _meta.ui) and its ui:// HTML resource in one declaration
    • raw_resource/2 helper — return raw content with a MIME type from resource handlers
    • Component mode ui: option — use ConduitMcp.Component, type: :tool, ui: "ui://..."
  • MCP Apps guide — new HexDocs guide covering DSL, Component, and app macro usage with client-side build workflow
  • MCP Apps exampleexamples/mcp_apps_demo/ with a server health dashboard demonstrating the full tool → UI resource → iframe pattern

0.8.5 - 2026-03-22

Changed

  • Removed Jason dependency — replaced with Elixir 1.18+ built-in JSON module across all lib, test, and transport code (one fewer dependency)

Performance

  • Pre-compiled URI template regex — resource URI matching regex is now compiled once at compile time instead of rebuilt on every request (2.4x faster resource reads in DSL mode, 1.7x in Endpoint mode)
  • Single-pass constraint validation — merged 4 separate schema traversals (enum, numeric, string length, custom) into a single Enum.reduce_while pass (1.6x faster)
  • Optimized marker removal — replaced 11-iteration Enum.reduce with Keyword.drop/2 (2.2x faster)
  • Single config fetch — validation reads Application.get_env once per call instead of 3 times (1.3x faster)
  • O(1) schema lookup in type coercion — replaced Enum.find per parameter with pre-built Map lookup

Added

  • Benchee benchmark suite (mix bench) with 6 benchmark files:
    • uri_template_bench — dynamic regex vs pre-compiled vs String.split
    • validation_bench — full pipeline, key conversion, constraint passes, marker removal, config lookups
    • handler_bench — method dispatch, function_exported? overhead, telemetry cost
    • json_bench — built-in JSON encode/decode at varying payload sizes
    • protocol_bench — request validation and response construction baseline
    • full_request_bench — DSL vs Manual vs Endpoint mode comparison
  • mix bench task — run all benchmarks, run specific (mix bench validation), or list (mix bench --list)
  • HTML benchmark reports generated in bench/output/

0.8.0 - 2026-03-22

Added

  • Endpoint + Component mode — third way to define MCP servers alongside DSL and Manual modes
    • ConduitMcp.Component behaviour for defining tools, resources, and prompts as individual modules
    • ConduitMcp.Component.Schema DSL (schema do field ... end) with automatic JSON Schema and NimbleOptions generation
    • ConduitMcp.Endpoint aggregator with component macro, declarative rate_limit/message_rate_limit/auth config
    • Auto-detected capabilities from registered component types
    • Compile-time validation (duplicate names, invalid modules, missing callbacks)
    • Atom-keyed params in execute/2 for ergonomic pattern matching
  • ConduitMcp.Errors module — centralized JSON-RPC 2.0 and MCP error code constants
    • parse_error/0, invalid_request/0, method_not_found/0, invalid_params/0, internal_error/0, server_error/0, resource_not_found/0
    • Replaces hardcoded magic numbers across the codebase
  • Transport auto-extraction — StreamableHTTP and SSE transports auto-read endpoint config (name, version, rate_limit, auth) as fallback defaults
  • Handler capability detectionbuild_capabilities/1 uses __capabilities__/0 when available for selective capability advertisement
  • 6 new documentation guides — choosing_a_mode, endpoint_mode, dsl_mode, manual_mode, authentication, rate_limiting

Improved

  • Test coverage expanded to 503 tests (up from 405)
  • README restructured with all 3 server modes, responses reference, MCP spec coverage table
  • Error codes refactoredConduitMcp.Protocol now delegates to ConduitMcp.Errors

0.7.0 - 2026-03-21

Added

  • MCP spec 2025-11-25 support with backward compatibility for 2025-06-18
    • Protocol version negotiation in initialize (supports both versions)
    • MCP-Protocol-Version response header on all POST responses
    • MCP-Session-Id header with session creation and validation
  • Pluggable session store (ConduitMcp.Session.Store behaviour)
  • Cursor-based pagination via arity-2 list callbacks (backward compatible with arity-1)
  • Tool annotations DSL (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
  • _meta field passthrough for progressToken support
  • listChanged capability declarations
  • Origin header validation for DNS rebinding prevention
  • New handler methods: completion/complete, logging/setLevel, resources/subscribe, resources/unsubscribe
  • audio/2 helper macro for audio content type
  • ConduitMcp.Tasks module for long-running operation state machine
  • ConduitMcp.Client module for server-to-client requests (sampling, elicitation, roots)
  • OAuth 2.1 authentication (ConduitMcp.Plugs.OAuth) with JWT validation
  • New error code -32002 (resource not found)
  • CI/CD pipeline with compile, format, credo, test, dialyzer, and hex publish jobs
  • Configurable initialize response (server_name, server_version via conn.private)

Improved

  • Test coverage expanded to 405 tests (up from 309)
  • Dependencies updated: bandit 1.10.3, credo 1.7.17, ex_doc 0.40.1, telemetry 1.4.1
  • Handler refactored to reduce cyclomatic complexity with extracted helper functions

Fixed

  • Version mismatch where handler returned hardcoded version instead of app version
  • Hardcoded protocol version in transport (now uses Protocol.protocol_version/0)
  • Flaky telemetry tests caused by async race conditions
  • Flaky StreamableHTTP tests caused by shared ETS state in async mode
  • Elixir 1.20 compilation warnings

0.6.5 - 2026-02-07

Added

  • Message-level rate limiting (ConduitMcp.Plugs.MessageRateLimit)
    • Second rate limiting layer that limits MCP method calls per time window
    • POST-only: GET/OPTIONS pass through automatically
    • Skips JSON-RPC notifications (no id field)
    • Configurable excluded methods (e.g., ["initialize", "ping"])
    • User-aware default key function (uses conn.assigns[:current_user] from Auth plug)
    • "msg:" key prefix prevents Hammer counter collision with HTTP rate limiter
    • HTTP 429 response with Retry-After header and JSON-RPC error (code -32000)
    • Telemetry event: [:conduit_mcp, :message_rate_limit, :check]
    • PromEx metrics: message_rate_limit_check_total, message_rate_limit_check_duration_milliseconds
    • Configurable via :message_rate_limit transport option
    • Works alongside existing HTTP-level rate limiting

Improved

  • Test coverage expanded to 309 tests
  • README updated with Rate Limiting documentation (HTTP + message-level)
  • Telemetry documentation updated with message rate limit events
  • PromEx plugin updated with message rate limit metrics
  • Applied mix format to all files in the codebase

[0.5.0] - 2025-11-24

Added

  • Resource URI parameter extraction - Complete implementation

    • Extracts parameters from URI templates (e.g., "user://{id}""user://123"%{"id" => "123"})
    • Supports multiple parameters (e.g., "user://{id}/posts/{post_id}")
    • Uses proper regex escaping with placeholder tokens
    • Returns {:ok, params} on match or :no_match otherwise
    • Full implementation in extract_uri_params/2 (internal)
    • Resolves TODO from previous versions
  • PromEx plugin for Prometheus monitoring

    • Optional integration via {:prom_ex, "~> 1.11", optional: true}
    • Conditional compilation (only loads if PromEx available)
    • 10 production-ready metrics (5 counters + 5 histograms)
    • Monitors all ConduitMCP operations: requests, tools, resources, prompts, auth
    • Optimized histogram buckets per operation type
    • Low cardinality design with string normalization
    • Comprehensive documentation with PromQL query examples
    • Alert rule examples included
    • Zero runtime overhead when not enabled

Improved

  • Test coverage expanded significantly

    • 33 new tests added (21 for core features, 12 for PromEx)
    • Resource URI parameter extraction: 11 new tests
    • Prompt functionality: 4 new tests
    • Tool functionality: 6 new tests
    • PromEx plugin: 12 new tests
    • Total: 229 tests, all passing
  • Documentation enhanced

    • Added comprehensive Prometheus Metrics section to README
    • 190+ lines of PromEx plugin documentation
    • PromQL query cookbook with examples
    • Alert rule templates
    • Complete metric reference

Fixed

  • Version consistency across all files (updated from 0.4.6 to 0.4.7, now 0.5.0)
  • Removed repository artifacts:
    • Deleted erl_crash.dump (4.9 MB)
    • Deleted conduit_mcp-0.4.0.tar and conduit_mcp-0.4.6.tar
  • Updated test badge count (193 → 229 passing)

Breaking Changes

None - This release is fully backward compatible.

[0.4.7] - 2025-11-19

Added

  • raw/1 helper macro for direct JSON output without MCP content wrapping
    • Bypasses standard MCP content structure for debugging purposes
    • Returns {:ok, data} directly instead of wrapped content array
    • Supports maps, strings, lists, and all data types
    • Includes comprehensive documentation with MCP compatibility warnings
    • Full test coverage with 3 test cases

Documentation

  • Updated README.md helper functions list to include raw/1
  • Added detailed module documentation with usage examples and warnings

0.4.6 - 2025-01-16

Changed

  • Streamlined README and CHANGELOG for clarity
  • Focused documentation on essential features
  • Reduced README by 53% (634 → 298 lines)
  • Reduced CHANGELOG by 48% (190 → 99 lines)

Improved

  • README now highlights DSL as primary approach
  • Removed outdated migration guides
  • Cleaner examples and better organization
  • Added version and test badges

0.4.5 - 2025-01-16

Added

  • Clean DSL for defining MCP servers
    • tool, prompt, resource macros for declarative definitions
    • Automatic JSON Schema generation from parameters
    • Helper functions: text(), json(), error(), system(), user(), assistant()
    • Support for inline functions, MFA handlers, and function captures
    • Parameter features: enums, defaults, required fields, type validation
  • Flexible authentication system
    • ConduitMcp.Plugs.Auth with 5 strategies
    • Bearer token, API key, custom function, MFA, database lookup
    • CORS preflight bypass, configurable assign key
    • Case-insensitive bearer token support
  • Extended telemetry
    • [:conduit_mcp, :resource, :read] - Resource operations
    • [:conduit_mcp, :prompt, :get] - Prompt operations
    • [:conduit_mcp, :auth, :verify] - Authentication
    • Complete observability for all MCP operations

Changed

  • Examples updated to use DSL (simple_tools_server, phoenix_mcp)
  • Transport modules support :auth option
  • Auth configured per-transport (no separate pipeline needed)
  • Documentation streamlined to focus on DSL

Tests

  • 36 DSL tests (tools, prompts, resources, helpers, schema builder)
  • 26 auth plug tests (all strategies, error handling, CORS)
  • 16 telemetry tests
  • 193 total tests, all passing

0.4.0 - 2025-01-16

Changed (Breaking)

  • Pure stateless architecture
    • Removed GenServer and Agent - zero process overhead
    • Server is just a module with pure functions
    • No supervision tree required
    • Maximum concurrency (limited only by Bandit)
  • Simplified callback API
    • Removed mcp_init/1
    • Changed {:reply, result, state}{:ok, result}
    • Callbacks receive conn (Plug.Conn) as first parameter
    • No more state passing/returning
    • Error maps use string keys
  • Handler updates
    • Calls module functions directly (no GenServer.call)
    • Transport layers pass Plug.Conn for request context

Performance

  • Zero process overhead - pure function calls
  • Full concurrent request processing
  • No serialization bottleneck

0.3.0 - 2025-10-28

Added

  • Comprehensive test suite (109 tests, 82% coverage)
  • Test infrastructure (TestServer, TelemetryTestHelper)
  • ExCoveralls integration

Changed

  • Simplified and professionalized README

0.2.0 - 2025-10-09

Added

  • Telemetry events ([:conduit_mcp, :request, :stop], [:conduit_mcp, :tool, :execute])
  • Configurable CORS headers
  • Enhanced logging

Fixed

  • SSE buffering with nginx proxies

0.1.0 - 2025-10-08

Added

  • Initial release
  • MCP specification 2025-06-18 implementation
  • ConduitMcp.Server behaviour
  • StreamableHTTP and SSE transports
  • Tools, resources, and prompts support
  • Basic authentication
  • Phoenix integration example