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 experimentaltasks/get,tasks/cancel,tasks/result, andtasks/listroutes keyed purely on the client-suppliedtaskId, so any caller could read or cancel any task (andtasks/listreturned every task system-wide). They now scope to the caller's principal via a new owner-awareConduitMcp.TasksAPI. 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 withConduitMcp.Tasks.create/3and configure principal extraction with:task_owner_fun(defaultconn.assigns[:current_user]— return a stable scalar such assub/id, since ownership is checked by exact match). - JWKS SSRF posture and stale-key window documented in the
ConduitMcp.OAuth.KeyProvider.JWKSmoduledoc:jwks_uriis 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
ConduitMcp.Tasksowner-scoped API —create/3,get/2,cancel/2,list/2, andowner/1, plus the configurable:task_owner_fun. TheConduitMcp.Tasks.Store@moduledocdocuments the top-level"owner"convention; reference stores promote it into_map/1.- Named error codes
ConduitMcp.Errors.task_not_ready/0(-32004) andrequest_cancelled/0(-32800), delegated fromConduitMcp.Protocol, replacing magic literals. [:conduit_mcp, :cancellation, :cleanup]telemetry (measurement%{removed: count}) emitted byConduitMcp.Cancellation.cleanup/1, mirroring the session janitor.
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
:etsArgumentErroron the authentication path. The table is now owned by a supervised process (ConduitMcp.OAuth.KeyProvider.JWKS.Owner, started fromConduitMcp.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 previousjob.stateguard never fired, since the state is"executing"at exception time);Tasks.Store.cancel/1updates 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 theguides/oban_tasks.mdworker 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.
Package From To Advisories fixed plug1.19.2 1.20.3 quadratic-time decoding of nested query/body params (high, CVE-2026-54892); multipart :lengthnot charged for part headers, enabling unbounded temp-file creation (medium, CVE-2026-56814); cookie attribute injection inPlug.Conn.Cookies.encode/2(low, CVE-2026-56813)bandit1.11.1 1.12.4 quadratic CPU blow-up reassembling fragmented WebSocket messages (high, CVE-2026-65623) mint1.7.1 1.9.3 CONTINUATION/HEADERS flood (high); unbounded streamsmap growth via PUSH_PROMISE (high); request-line CRLF injection (low); Content-Length+prefix (moderate)req0.5.17 0.7.2 unbounded archive/compression extraction driven by response content-type (high); multipart header injection (moderate) The
plugandbanditadvisories 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. Themintandreqones reach this library only through the optional JWKS key provider — where the decompression advisory is genuinely reachable rather than merely present, sinceConduitMcp.OAuth.KeyProvider.JWKScaps the JWKS body at 1MB but Req decompresses before that cap applies.No declared constraint changed:
~> 1.19,~> 1.9and~> 0.5all already permitted the patched releases, so onlymix.lockmoved.bandit 1.12requiresthousand_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),hpaxandcastorefollowed as transitives. The JWKS moduledoc now recommends{:req, "~> 0.6"}.JWT algorithm allow-list —
ConduitMcp.Plugs.OAuthnow validates the token headeralgagainst an allow-list (new:algorithmsoption, default: RS/ES/PS families) before key lookup, and requires the resolved signing key to match the header algorithm's family. Staticoctkeys implicitly allow their HS algorithms, so existing HMAC setups keep working. Set:algorithmsexplicitly if you rely on a non-default algorithm.JWKS fetch hardening — the JWKS key provider now requires
https(override withallow_insecure_jwks: truefor 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
endpointevent URL can now be pinned with the new:base_urloption; theHost-header fallback is sanitized.Origin-validation startup warning — transports log a warning at init when
:allowed_originsis unset (DNS-rebinding exposure for browser-reachable servers). Passallowed_origins: "*"to opt out explicitly.
Changed
- Validation
min_length/max_lengthnow 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; staticoctkeys 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.exsaccidentally 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_originsunset logs a warning; a future major release will require an explicit Origin policy for browser-reachable transports.
Added
require_session: truesession option forConduitMcp.Transport.StreamableHTTP— rejects non-initializePOSTs without anMcp-Session-Idheader (HTTP 400), per the MCP specification.:base_urloption forConduitMcp.Transport.SSEand:algorithmsforConduitMcp.Plugs.OAuth(see Security).resources/templates/listmethod — MCP-spec-required endpoint for discovering URI-templated resources. Templated resources (URIs with{param}placeholders) now appear here instead of inresources/list. New optionalhandle_list_resource_templates/1Server callback.notifications/cancelledhandling via the newConduitMcp.Cancellationmodule. Tool authors pollCancellation.cancelled?(conn)to cooperatively abort long-running work. Handler stashes the request id inconn.assigns[:mcp_request_id]and emits[:conduit_mcp, :request, :cancelled]telemetry.- Capability advertisement for
completions,logging, andresources.subscribe— previously these features were routed but never declared oninitialize, so spec-compliant clients never used them. - Tool schema fields
title,icons,outputSchema, andexecution.taskSupport(MCP 2025-11-25). New DSL macros:title/1,icons/1,output_schema/1,task_support/1. - Tasks JSON-RPC methods —
tasks/get,tasks/cancel,tasks/result,tasks/listrouted toConduitMcp.Tasks. task/2DSL 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, andTasks.Janitor— parallel cleanup for the tasks table, which previously had no eviction at all.ConduitMcp.Tasks.Storebehaviour — task storage is now pluggable, mirroringConduitMcp.Session.Store. Configure viaconfig :conduit_mcp, :tasks_store, MyApp.MyTasksStore. The default remains the in-memoryConduitMcp.Tasks.EtsStore, so existing servers keep their current behaviour without any changes. Standardtasks/*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 theinput_requiredstate exercised via{:snooze, _}.- Object parameters actually work.
:objectparams (ConduitMcp.DSL) and:objectfields (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, anditems :objectinside 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:objectparams and fields —trueenforces 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,2inConduitMcp.Component.Schema— component-mode array fields had no way to declare an item type at all, and a barefieldinside an:arrayblock silently corrupted the parent field list.itemsis now the only thing an:arrayblock accepts, at every nesting depth.- 3-arg (and 2-arg) block forms —
param :bag, :object, "desc" do ... endandfield :bag, :object do ... endused to bind to the blockless clause ([do: ...]is a keyword list), silently discarding the block; the form thefield/4docs themselves showed was broken. Both now work in both DSLs. A block on a type that has no block form raises aCompileErrornaming the file and line. - Type coercion now follows nested objects. A nested
:integerfield 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.DSLsilently swallowed a barefieldinside an:arrayblock, anitemsoutside one, and afieldoutside any object block, and raised a bareFunctionClauseErrorforitems :string do ... end. All four are nowCompileErrors naming the file and line, matchingComponent.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__/1in DSL-mode servers without any scoped tools used to expand toMap.get(%{}, _), which Elixir 1.20's type checker flags as always returning the default. Same fix as commit0f05a9ffor 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 — socheck_min_value/3skipped a binary value, coercion then turned it into a number, and the markers had already been stripped from the schema NimbleOptions sees. Withtype_coercion: true(the default),"5"passedmin: 18and the handler received5.validator:was worse: the function was called with the uncoerced binary, and"5" > 18istruein Erlang term order. Coercion now runs first, so every constraint sees the value the handler will see. Noteenum:now matches after coercion too —enum: [1, 2, 3]accepts"1"for an:integerfield, where it previously rejected it.
Changed
resources/listnow returns only static URIs. Templated URIs move toresources/templates/listper spec. Clients that relied on templated URIs inresources/listwere already non-spec-compliant.Session.Storebehaviour gained an optionalcleanup/1callback used bySession.Janitor. Existing stores that don't implement it continue to work; the janitor logs a warning and idles.- OAuth scope rejection on
tools/callnow returns a JSON-RPC error with the request's id (previouslynil, breaking client correlation). - Validation errors now always carry a
parameter. A type mismatch used to returnparameter: niland 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:statusfield indicates:ok/:errorand whose payload includes validation failures. custom_constraint_markers/0onConduitMcp.Validation.SchemaConverter— replaced bystrip_markers/1, which does the stripping itself and recurses into nestedkeys:schemas. Callers that fetched the marker list to do their ownKeyword.drop/2should callstrip_markers/1instead; four modules in this library did exactly that and now share the one implementation.
0.9.7 - 2026-06-18
Fixed
tools/callerrors crashed when the request carried_meta—Handler.maybe_add_meta/2unconditionally wrote the request's_metainto["result", "_meta"], but error responses (%{"error" => ...}) have no"result"key. With a_metapresent (clients such as the Python MCP SDK send aprogressTokenon everytools/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._metais 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__/1and__convert_to_atom_keys__/2. Replaced single-map lookups with per-component function clauses (__scope_for_tool__(<<name>>) -> scopeand__key_map__(<<name>>) -> escaped_map) plus catch-all fallbacks.mix compile --warnings-as-errorsis now clean for read-only / tool-only endpoints.
[0.9.1] - 2026-03-24
Performance
- persistent_term validation config — replaced
Application.get_envwith:persistent_term.getfor O(1) lock-free config reads on every validated request (4–10% faster validation, 5–11% less memory) - Cached server capabilities — new
ConduitMcp.ServerMetamodule lazily caches allfunction_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__/1now returns{full_schema, clean_schema}tuples pre-stripped of constraint markers at compile time, eliminating per-requestKeyword.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
- Atom table exhaustion —
String.to_atom/1in validation replaced withString.to_existing_atom/1to prevent atom table exhaustion from malicious parameter names
Added
ConduitMcp.Validation.update_validation_config/1— public API for updating validation config at runtime (writes both Application env and persistent_term)
[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/1macro — attach arbitrary_metametadata to tool definitions (generic, future-proof)ui/1macro — shortcut for declaring_meta.ui.resourceUrion a toolapp/2macro — convenience that registers both a tool (with_meta.ui) and itsui://HTML resource in one declarationraw_resource/2helper — 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 example —
examples/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
JSONmodule 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_whilepass (1.6x faster) - Optimized marker removal — replaced 11-iteration
Enum.reducewithKeyword.drop/2(2.2x faster) - Single config fetch — validation reads
Application.get_envonce per call instead of 3 times (1.3x faster) - O(1) schema lookup in type coercion — replaced
Enum.findper parameter with pre-builtMaplookup
Added
- Benchee benchmark suite (
mix bench) with 6 benchmark files:uri_template_bench— dynamic regex vs pre-compiled vs String.splitvalidation_bench— full pipeline, key conversion, constraint passes, marker removal, config lookupshandler_bench— method dispatch,function_exported?overhead, telemetry costjson_bench— built-in JSON encode/decode at varying payload sizesprotocol_bench— request validation and response construction baselinefull_request_bench— DSL vs Manual vs Endpoint mode comparison
mix benchtask — 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.Componentbehaviour for defining tools, resources, and prompts as individual modulesConduitMcp.Component.SchemaDSL (schema do field ... end) with automatic JSON Schema and NimbleOptions generationConduitMcp.Endpointaggregator withcomponentmacro, 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/2for ergonomic pattern matching
ConduitMcp.Errorsmodule — centralized JSON-RPC 2.0 and MCP error code constantsparse_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 detection —
build_capabilities/1uses__capabilities__/0when 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 refactored —
ConduitMcp.Protocolnow delegates toConduitMcp.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-Versionresponse header on all POST responsesMCP-Session-Idheader with session creation and validation
- Protocol version negotiation in
- Pluggable session store (
ConduitMcp.Session.Storebehaviour)- Default ETS store included (
ConduitMcp.Session.EtsStore) - Documentation for Redis, PostgreSQL, and Mnesia stores
- Default ETS store included (
- Cursor-based pagination via arity-2 list callbacks (backward compatible with arity-1)
- Tool annotations DSL (
readOnlyHint,destructiveHint,idempotentHint,openWorldHint) _metafield passthrough forprogressTokensupportlistChangedcapability declarations- Origin header validation for DNS rebinding prevention
- New handler methods:
completion/complete,logging/setLevel,resources/subscribe,resources/unsubscribe audio/2helper macro for audio content typeConduitMcp.Tasksmodule for long-running operation state machineConduitMcp.Clientmodule for server-to-client requests (sampling, elicitation, roots)- OAuth 2.1 authentication (
ConduitMcp.Plugs.OAuth) with JWT validation- JWKS key provider with HTTP fetching (
ConduitMcp.OAuth.KeyProvider.JWKS) - Static key provider (
ConduitMcp.OAuth.KeyProvider.Static) - Resource metadata endpoint (
ConduitMcp.OAuth.ResourceMetadata) - Tool-level scope enforcement
- JWKS key provider with HTTP fetching (
- 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_versionviaconn.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
idfield) - 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-Afterheader 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_limittransport 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 formatto 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_matchotherwise - Full implementation in
extract_uri_params/2(internal) - Resolves TODO from previous versions
- Extracts parameters from URI templates (e.g.,
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
- Optional integration via
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.tarandconduit_mcp-0.4.6.tar
- Deleted
- Updated test badge count (193 → 229 passing)
Breaking Changes
None - This release is fully backward compatible.
[0.4.7] - 2025-11-19
Added
raw/1helper 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,resourcemacros 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.Authwith 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
:authoption - 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
- Removed
- 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.Serverbehaviour- StreamableHTTP and SSE transports
- Tools, resources, and prompts support
- Basic authentication
- Phoenix integration example