Public API diff: v1.0.0-rc.5 to 1.0 candidate

Copy Markdown View Source

This report records the public Elixir API compatibility gate for the MCP 2026-07-28 migration. The comparison is between:

  • baseline: v1.0.0-rc.5 (a2464c3423ee48ba825402cc2d17be8042f1451a);
  • candidate: 1.0.0-rc.7 on master (current soak candidate).

The original BEAM snapshot was taken against the v1.0.0-rc.6 release commit. An independent rc.7 census (2026-08-13) updated the candidate totals below. There are zero removals versus rc.5.

Post-rc.6 note (2026-08-13): 1.0.0-rc.7 is the current 1.0 soak candidate. Public deltas after the rc.6 snapshot are summarized in Updates for 1.0.0-rc.7.

Method

The rc.5 tag and the candidate were compiled independently. As a conservative package-level check, the audit included every BEAM module whose compile source is under lib/, even when the module is hidden from HexDocs. It normalized and compared:

  • module names;
  • exported functions and arities, excluding module_info/* and __info__/1;
  • callbacks, via Code.Typespec.fetch_callbacks/1 and Code.Typespec.spec_to_quoted/2;
  • public and opaque types, via Code.Typespec.fetch_types/1 and Code.Typespec.type_to_quoted/1;
  • struct field names.

The snapshot totals were:

Surfacerc.51.0.0-rc.7Removed
Modules2363000
Exported functions2,2882,7530
Callbacks931150
Struct fields3635570
Types4856000 named types

The type count treats a changed definition as one removed textual row and one added textual row. Those changes were inspected individually as described below. Public compatibility characterization tests remain the runtime check for return shapes and retained deprecated modules; a BEAM snapshot cannot prove behavioral equivalence by itself.

Result

No public module, exported function/arity, callback, named type, or struct field present in rc.5 was removed.

Three existing handler callbacks were widened to admit MRTR input-required results while preserving every rc.5 return:

  • ExMCP.Server.Handler.handle_call_tool/3;
  • ExMCP.Server.Handler.handle_get_prompt/3;
  • ExMCP.Server.Handler.handle_read_resource/2.

Existing structs only gained fields. The additions on rc.5 structs are:

The changed typespec definitions are additive or corrective except for the OAuth state hardening called out below:

  • authorization configuration types add issuer, metadata-fetch, client registration, credential-store, protocol-version, and HTTP adapter options;
  • capability types add the standard extensions map;
  • JSON Schema types now admit all map-shaped schemas, including boolean schemas where the receiving API supports them;
  • tool_result/0 widens structuredContent from string-keyed maps to any JSON value, and result/task/transport structs describe their added fields;
  • ClientRegistration.registration_request/0 now declares application_type, which its rc.5 documentation and runtime validation already required.

OAuthFlow.auth_params/0 no longer advertises caller-supplied :state. start_authorization_flow/1 now rejects it and generates a high-entropy, single-use state bound to the transaction store. This is an intentional security behavior change, not a removed function or return shape. Applications that supplied :state must retain the returned transaction and use its generated state_param instead.

Additive public API

The candidate adds 64 modules and 465 exports. The main supported additions are grouped here rather than listing internal plumbing:

Existing modules gain additive entry points such as ExMCP.Client.discover/2, listen/3, get_task/3, update_task/4, and cancel_task/3, plus schema compilation, version-registry, version-aware error, stream-management, MRTR result helpers, and option-aware OAuth metadata URL validation.

Protocol-driven behavior changes

These are wire or security changes rather than public Elixir symbol removals:

  • modern-preferred connections negotiate MCP 2026-07-28 and use server/discover, stateless Streamable HTTP routing headers, result envelopes, MRTR, and subscriptions/listen;
  • legacy revisions and deprecated HTTP+SSE remain selectable throughout 1.x;
  • Tasks are advertised only when the extension is configured and supported;
  • result cache metadata is validated, but ExMCP 1.0 does not store or reuse responses;
  • OAuth state is generated and consumed by ExMCP as described above.

The wire migration is intentionally landing before stable 1.0. These changes would have required an ExMCP 2.0 release if 1.0 had already been published; the retained rc.5 public surface lets existing Elixir applications migrate without a simultaneous package-API rewrite.

Release decision

The public-API gate is satisfied on the rc.7 census: there are no unresolved rc.5 symbol, callback, named-type, struct-field, or return-shape removals. Stable 1.0 still depends on the conformance, security, interoperability, load, rollback, and minimum seven-day modern-preferred RC soak gates in the migration plan.

Updates for 1.0.0-rc.7

This section is the public delta for changes that landed after the rc.6 BEAM snapshot. The table above is the rc.7 census (modules, exports, callbacks, struct fields, and named types).

Additive public API

  • ExMCP.SessionManager.append_event/3 — atomically appends a legacy SSE event with a store-owned, monotonically increasing ID. Persist-before-delivery uses this entry point so events remain replayable when a write races a disconnect. Existing store_event/2 and replay_events_after/2,3 remain available.

Protocol and security behavior (no symbol removals)

These are wire, lifecycle, or configuration-behavior changes rather than removed Elixir symbols:

  • Server-issued legacy sessions — Streamable HTTP no longer accepts caller-selected session IDs. Sessions are created by initialization, exposed only after a successful initialize response, identity-bound for their lifetime, initialize-once, and capacity-capped. Streamable GET/POST require a successfully initialized session.
  • Duplicate request-ID rejection — JSON-RPC request IDs are atomically claimed per session/process and rejected before dispatch for the bounded session lifetime.
  • Subprocess environment isolation — stdio MCP and ACP adapter subprocesses default to environment_policy: :isolated. Full inheritance requires an explicit :inherit opt-in.
  • Outbound network policy options — public client/server configuration now documents and enforces allowed_private_hosts, pinned DNS/TLS destination checks, and related OAuth metadata fetch bounds. Escape hatches such as legacy_unbound_tokens, trusted_hosts, and Codex trusted_mcp_servers: :all remain available but are documented as deliberate weakenings.
  • Resource bounds — session, request-ID, replay-byte, frame, queue, and handshake limits are enforced fail-closed with slow-consumer handling.

Compatibility statement

No rc.5 public module, exported function/arity, callback, named type, or struct field known from the rc.6 audit was removed for rc.7. Applications that minted or supplied their own legacy Mcp-Session-Id values, or that relied on disconnect deleting session/replay state, must adopt server-issued sessions and size TTL/replay bounds as documented in the migration and configuration guides.

Updates for 1.0.0-rc.7 (API census addendum)

  • ExMCP.ACP.Client.HandlerRunner.session_update/3 is retained as a compatibility wrapper over session_update/5. The /3 form applies the same default update queue bounds used by ExMCP.ACP.Client (max_update_queue: 32, max_update_queue_bytes: 8_388_608) but always returns :ok, matching the rc.6 contract even when bounded /5 delivery drops.
  • Callers that need explicit bounds and a :dropped result should use session_update/5.