Skip to content

Releases: Epistates/turbomcp

v3.1.5

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 19 May 14:35

[3.1.5] - 2026-05-11

Patch release: Streamable HTTP interoperability hardening for RMCP/Codex
clients, plus dogfood coverage for two-way Rust SDK compatibility.

Added

  • AI-friendly server visibility policyVisibilityLayer now supports
    exact allowed/disabled/hidden rules for tools, resources, resource templates,
    and prompts, plus a strict read-only tool profile. Consumers can load a
    VisibilityConfig from application config to reduce tools/list context
    load, keep niche tools callable but unlisted, and block disabled calls,
    including unlisted dynamic calls, as not found.
  • Registry-backed visibility dispatch — visibility authorization now uses a
    cached component registry populated by list responses or lazily on first
    direct use, with explicit refresh/clear hooks for dynamic servers whose
    advertised components change at runtime.
  • TurboMCP <-> RMCP Streamable HTTP dogfood interop checks — the dogfood
    benchmark suite now validates RMCP client to TurboMCP HTTP server and TurboMCP
    client to RMCP HTTP server flows, including initialization, tools, resources,
    prompts, and Codex-compatible standalone SSE startup framing.
  • RMCP comparison diligence notes — added a focused comparison of
    TurboMCP against the official Rust MCP SDK covering implementation strengths,
    feature gaps, compliance risks, and SOTA follow-ups.

Changed

  • Visibility config APIs now use explicit replacement semantics
    VisibilityConfig and VisibilityLayer expose with_allowed_*,
    with_disabled_*, and with_hidden_* builders for exact-name policy.
    Disabled components are rejected on direct use, hidden components are omitted
    from list responses but remain directly usable, and disabled rules win over
    hidden or allowed rules.
  • HTTP clients open SSE only after session establishment — Streamable HTTP
    clients now defer the GET SSE connection until the server has issued an
    Mcp-Session-Id, matching RMCP server expectations for session-scoped
    streams.
  • Release-facing metadata now targets 3.1.5 — workspace manifests, internal
    crate dependency pins, lockfile entries, and Cargo-facing demo snippets
    identify the patch release consistently.

Fixed

  • Standalone Streamable HTTP SSE startup no longer emits an empty data
    event
    — TurboMCP server now opens GET streams with an SSE comment and
    reserves data: events for real JSON-RPC messages, avoiding rmcp/Codex
    startup failures on clients that parse empty primers as payloads.
  • POST SSE primer events no longer break the TurboMCP HTTP client — empty or
    whitespace-only POST-SSE events are ignored instead of being parsed as
    JSON-RPC payloads.
  • Post-initialize Streamable HTTP requests tolerate missing protocol-version
    headers
    — once a session has negotiated a protocol version, TurboMCP keeps
    using that session version for later requests that omit
    MCP-Protocol-Version, matching tolerant rmcp/Codex startup behavior.
  • Hidden-only visibility profiles still advertise operation capabilities
    hidden-but-callable tools, resources, and prompts no longer disappear from the
    initialize capability surface just because they are omitted from list
    responses, including when visibility layers are wrapped by middleware or
    mounted into composite handlers.
  • Visibility clone/build patterns no longer share tag filter mutations
    cloning a VisibilityLayer before applying global tag filters now behaves
    like exact-name rules: builder mutations on the clone do not mutate the
    original layer's visibility profile.
  • Registry-backed dispatch preserves first-listed duplicate metadata
    malformed handlers that advertise duplicate component identifiers keep the
    previous first-match authorization behavior instead of letting the registry's
    map representation silently prefer the last duplicate.

What's Changed Summary

New Contributors

Full Changelog: v3.1.4...v3.1.5

v3.1.4

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 09 May 12:04

[3.1.4] - 2026-05-08

Patch release: MCP compliance hardening focused on the 2025-11-25 and
2025-06-18 stable protocol surfaces, Streamable HTTP/SSE bidirectionality,
resource template fidelity, version adaptation, and regression coverage.

Added

  • Streamable HTTP/SSE server-initiated request support — initialized HTTP
    sessions now attach an McpSession to request contexts, so
    ctx.sample(...), ctx.elicit_form(...), ctx.elicit_url(...), and
    ctx.notify_client(...) can send JSON-RPC requests or notifications over SSE
    and correlate client POSTed responses back to the original handler.
  • resources/templates/list support across server, client, macros, and
    proxy paths
    — handlers, routing, generated servers, backend introspection,
    and proxy forwarding now expose resource templates as first-class MCP
    resources rather than dropping or flattening them.
  • Stable schema/method parity regression coverage — server tests now compare
    supported version-adapter methods directly against the embedded stable MCP
    schemas to catch missing method wiring during future spec updates.

Changed

  • Protocol negotiation is explicit for stable versions — 2025-11-25 remains
    the preferred stable version, while 2025-06-18 compatibility is retained
    through version adapters that strip newer response fields where required.
  • Release-facing metadata now targets 3.1.4 — workspace manifests, internal
    crate dependency pins, lockfile entries, install snippets, README examples,
    and package-facing migration docs identify the patch release consistently.

Fixed

  • HTTP/SSE sampling, elicitation, and roots flows no longer time out after
    valid client responses
    — client JSON-RPC responses posted with the active
    Mcp-Session-Id resolve pending server-initiated requests immediately,
    including rejected sampling responses.
  • Normal POST-only HTTP clients remain compatible — ordinary client-to-server
    JSON-RPC requests continue to work without requiring an SSE subscriber or
    server-initiated request handling.
  • Client resource template APIs preserve full metadataResourceTemplate
    responses retain uriTemplate, name, title, description, mimeType,
    icons, annotations, and _meta instead of reducing templates to strings.
  • Proxy resource template forwarding preserves backend data — proxy
    introspection and resources/templates/list forwarding now keep template
    metadata intact and emit the spec field name resourceTemplates.

Full Changelog: v3.1.3...v3.1.4

v3.1.3

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 07 May 10:56

[3.1.3]

Patch release: follow-on exhaustive audit of lower-to-upper layers, focusing on
JSON-RPC correctness, stream framing robustness, deprecated transport surface
removal, dependency freshness, and active documentation/scaffold alignment.

Security

  • JSON-RPC version validation is now strict on inbound core routing — requests
    whose jsonrpc member is missing or not exactly "2.0" are rejected before
    handler dispatch.
  • OpenAPI URL fetches validate before network access — URL inputs are parsed
    and SSRF-checked before the HTTP request is built, with a bounded request
    timeout.
  • Server/proxy HTTP and WebSocket frontends enforce configured limits and
    origin checks
    — HTTP request bodies honor ServerConfig.max_message_size,
    WebSocket upgrade paths validate Origin, and proxy browser frontends retain
    auth/origin enforcement after migrating off deprecated adapters.
  • DPoP replay detection now rejects duplicates before cache eviction — the
    in-memory nonce tracker checks for replay while holding the write lock, so a
    saturated cache cannot evict the duplicate before detection. Redis-backed
    duplicate nonce detection now maps to the dedicated replay error as well.
  • DPoP JWK validation rejects private key material — public JWK headers
    containing a private d member are rejected, while interoperable public EC
    JWKs that omit use are accepted as signature keys.
  • WASM streamable HTTP session headers validate without panicking — invalid
    Mcp-Session-Id headers now return 400 Bad Request instead of constructing
    unchecked session IDs.

Changed

  • Proxy frontends migrated to supported server transports — runtime and CLI
    serving paths now use turbomcp-server HTTP/WebSocket plumbing instead of the
    removed turbomcp-transport::axum compatibility layer.
  • CLI scaffolds and active docs now target current public APIs — generated
    examples use the current crate version and supported resource/tool patterns,
    and active README/migration guidance no longer points new users at deprecated
    surfaces.
  • Release-facing metadata now targets 3.1.3 — workspace manifests, internal
    crate dependency pins, lockfile entries, install snippets, README examples,
    and package-facing migration docs identify the patch release consistently.
  • Declared dependencies refreshed to latest available releases — workspace
    manifests now target current direct dependency versions, including reqwest
    0.13.3, tower-http 0.6.9, metrics 0.24.5,
    metrics-exporter-prometheus 0.18.3, serde_with 3.19, redis 1.2.1,
    signature 3.0, utoipa 5.5, wasm-bindgen 0.2.120, and
    js-sys/web-sys 0.3.97. cargo outdated --workspace --root-deps-only
    reports all direct dependencies current after the refresh.

Removed

  • Deprecated turbomcp-transport::axum subtree deleted — callers should use
    turbomcp_server::transport::http, McpServerExt::run_http, or
    McpServerExt::into_axum_router.
  • Deprecated transport and client shims removedStreamingTransport,
    TransportType::Grpc / TransportType::Quic, no-op WebSocket compression/TLS
    builders, the no-op gRPC TLS client config field, and
    turbomcp-client::sampling::ServerInfo are gone.

Fixed

  • SSE and streaming decoders handle edge framing cases — CRLF events,
    whitespace-only lines, trailing newlines, empty data: events, sticky
    overflow states, and last_event_id propagation now behave consistently.
  • SSE incremental parsing handles split UTF-8 sequences — partial multibyte
    code points are buffered across feed() calls instead of being dropped or
    corrupting subsequent events.
  • JSON-RPC error classification and capping tightenedstandard_kind()
    only reports the five JSON-RPC standard errors, while error data capping now
    recurses through arrays as well as objects.
  • JSON-RPC response payload validation is presence-awareresult: null
    is now accepted as a valid success response, while responses with both
    result and error or neither member are rejected.
  • JSON-RPC request parsing validates IDs and preserves notification
    semantics
    — inbound request IDs must be strings or integer numbers; null,
    fractional, boolean, array, and object IDs are rejected as invalid requests.
    Successful method notifications no longer produce JSON-RPC success responses,
    while parse/invalid-request errors with unknown IDs still serialize correctly.
  • params: null remains compatible with absent params — explicit JSON
    null params deserialize as None rather than a structured parameter value.
  • Spec optional fields are represented accurately — cancellation
    notifications accept missing requestId, resource subscribe/unsubscribe/update
    notifications include optional _meta, and client logging no longer assumes a
    cancellation id is always present.
  • Elicitation mode parsing is strict and URL error codes are consistent
    unknown or non-string modes now fail deserialization, and
    URLElicitationRequiredError uses the same -32042 code as the core error
    taxonomy.
  • alloc-only builds keep compiling — missing alloc imports in the
    bottom-level type crates were restored and checked under no-default-features
    configurations.
  • The transports demo is warning-free across feature sets — transport list
    construction no longer requires a mutable binding when only the base transport
    is enabled.
  • Validation utilities avoid duplicate work and retry edge cases
    validation dispatch no longer runs duplicate checks for the same value, and
    retry_with_backoff(max_attempts = 0) now performs the initial attempt.
  • Active API docs and rustdoc links align with the current public surface
    core/gRPC references no longer advertise removed imports or client methods,
    proxy README package links no longer target ignored local notes, and rustdoc
    now passes with warnings denied.
  • Examples and demo documentation match current runnable APIs — STDIO
    examples now show complete MCP initialization flows, transport examples use
    the required client features, a Unix server/client pair is documented and
    compiled, proxy schema export has a no-backend mock mode, and stale unsupported
    file-based config examples were removed.

Full Changelog: v3.1.2...v3.1.3

v3.1.2

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 27 Apr 22:08

[3.1.2]

  • MCP 2025-11-25 gaps closed: resources/{subscribe,unsubscribe}, logging/setLevel, completion/complete now route; notifications/cancelled actually cancels in-flight handlers (server) and is auto-emitted on
    timeout (client); Tasks API (SEP-1686) wired into typed routing; ProgressNotification aligned to string|number token + f64 values.
  • Security hardening across frontends: HTTP bearer-token redirect leak, SSE/decompression bombs, WebSocket max_message_size, origin-validation starts_with bypass, X-Forwarded-For spoofing behind proxies,
    telemetry PII/cardinality leaks, Prometheus binding to 0.0.0.0, OAuth token-store silently in-memory on WASM, proxy bearer-token logged at INFO, proxy frontends missing Origin allowlist.
  • Hand-rolled → battle-tested: governor (rate limiting), backon (retry/backoff), serde_norway (YAML), which (cross-platform binary lookup).
  • Bug fix: bidirectional correlation was matching on a fresh local UUID instead of the JSON-RPC id — every server-initiated request was timing out on healthy connections.
  • Deprecated: turbomcp-transport::axum subtree (use turbomcp-server::transport::http), WebSocket enable_compression/tls_config no-op fields.
  • Dep refresh: tokio 1.52, axum 0.8.9, hyper 1.9, tokio-tungstenite 0.29, sha2 0.11, msgpacker 0.7, cryptoki 0.10, getrandom 0.4.

One public-API break to know: RichContextExt::report_progress* now takes f64 instead of u64 — callers pass 50.0 instead of 50.

Full Changelog: v3.1.1...v3.1.2

v3.1.1

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 21 Apr 18:34

[3.1.1] - 2026-04-21

Patch release consolidating canonical types, hardening filesystem path handling,
and cleaning up deprecated surfaces left over from 3.1.0.

Security

  • Symlink-based path escapes rejected at file creationcrates/turbomcp-cli/src/path_security.rs. The CLI's write path now canonicalizes each ancestor of a target before creating a file, so a symlink planted inside an allowed root that points outside it is caught instead of being followed. Pre-3.1.1 only the final component was checked, leaving a TOCTOU-adjacent escape for CLI-driven writes.

Types / Context

  • turbomcp-types is now the sole canonical home for MCP types — completes the consolidation started in 3.1.0. Duplicate definitions previously living in turbomcp-protocol and turbomcp-core have been removed; downstream crates re-export from turbomcp-types. No behavior change for consumers using the turbomcp prelude.
  • RequestContext unified with bidirectional session statecrates/turbomcp-types / turbomcp-core. Server-initiated requests (elicitation, sampling, roots) and inbound request handling now share one context type instead of two parallel shapes.

Fixes

  • StreamableHttpClientTransport doc example handles initialization errorscrates/turbomcp-http/src/transport.rs. The rustdoc example now propagates the Result returned by new() (the 3.1.0 API change) instead of .unwrap()-ing.
  • WASM cleanupcrates/turbomcp-wasm. Removed unnecessary .into() calls and tightened test assertions.

Chore

  • MemoryTokenStore deprecation removed — the deprecation attribute and migration shim are gone; the store is a first-class in-memory backend again. Callers that were silencing the deprecation warning can drop the #[allow(deprecated)] annotations.
  • Dependency audit configuration updateddeny.toml narrowed to the advisories still applicable post-3.1.0 TLS CVE fixes.
  • Clippy cleanup across the workspace.

Full Changelog: v3.1.0...v3.1.1

v3.1.0

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 20 Apr 11:16

[3.1.0] - 2026-04-17

This release lands the remediation pass from the v3.0.13 audit.
Five categories of fix: security correctness, transport correctness, protocol/macro correctness, CI/test coverage, extension-crate honesty markers.

Security

  • is_token_expired is no longer a no-opcrates/turbomcp-auth/src/oauth2/client.rs:626. Pre-3.1 the check expires_in == 0 treated a relative duration as a countdown clock; it never returned true, so OAuth callers silently forwarded expired bearer tokens forever. TokenInfo now carries issued_at: Option<SystemTime> (serde-default for back-compat with cached v3.0 token entries), populated by OAuth2Client::token_response_to_token_info. New TokenInfo::expires_at, is_expired, and is_expired_with_skew(Duration) helpers; is_token_expired delegates to them with a 60s clock skew.

  • DPoP ath claim is now enforced at the resource server (RFC 9449 §4.3)crates/turbomcp-dpop/src/proof.rs. New public ProofContext { TokenEndpoint, ResourceServer } enum threaded through validate_proof / parse_and_validate_jwt (BREAKING — see Migration). At a resource server, presenting an access token alongside a proof without ath now returns DpopError::AccessTokenHashFailed. Pre-3.1 a stolen DPoP proof could be paired with a separately-issued access token, defeating sender-constraint binding. New regression test test_resource_server_requires_ath_when_token_present.

  • TLS certificate validation CVEs resolvedCargo.lock updates for aws-lc-sys 0.38.0 → 0.40.0 (RUSTSEC-2026-0044, RUSTSEC-2026-0048) and rustls-webpki 0.103.9 → 0.103.12 (RUSTSEC-2026-0049, RUSTSEC-2026-0098, RUSTSEC-2026-0099). Affected every outbound HTTPS in turbomcp-auth (OIDC discovery, JWKS), turbomcp-transport, and turbomcp-client. cargo audit now reports zero open advisories beyond the documented paste (compile-time-only) / proc-macro-error / rand low-impact entries.

  • JwtValidator::new and MultiIssuerValidator::add_issuer now apply SSRF protection by defaultcrates/turbomcp-auth/src/jwt/validator.rs. Pre-3.1 these constructors performed unguarded HTTP fetches to the issuer-derived OIDC discovery URL. In multi-issuer setups where the issuer string comes from an attacker-controllable JWT payload, that was an SSRF. The default constructors now wrap an SsrfValidator::default() policy (blocks loopback, RFC 1918, link-local, cloud metadata). New new_unchecked / add_issuer_unchecked opt-outs for test/dev against private OIDC providers.

  • DPoP nonce tracker has a bounded capacity and inline cleanupcrates/turbomcp-dpop/src/proof.rs. MemoryNonceTracker now supports with_capacity(usize) (default 1,000,000) with time-ordered eviction triggered at 80% high-water inside track_nonce. Pre-3.1 the map was unbounded with no automatic cleanup, and is_nonce_used did an O(n) constant-time scan — both compound CPU+memory DoS vectors via unique-JTI flooding. The lookup is now O(1) hashed (server-generated nonces have no per-character secret to leak through hashmap timing).

  • OAuth redirect URI no longer accepts 0.0.0.0crates/turbomcp-auth/src/oauth2/client.rs and crates/turbomcp-auth/src/oauth2/resource.rs. 0.0.0.0 is the bind-all unspecified address, not loopback, so a callback sent to it can be intercepted by any process on any interface (RFC 8252 §7.3 violation). Allowed loopback hosts are now exactly 127.0.0.1, [::1], and localhost.

  • API keys are no longer stored plaintext in memorycrates/turbomcp-auth/src/providers/api_key.rs. ApiKeyProvider now stores BLAKE3 digests as the map key; plaintext values are dropped at the end of add_api_key and never retained. Lookup is O(1) over digests with constant-time hashing of the input. add_api_key now returns McpResult<()> and rejects keys shorter than MIN_API_KEY_LENGTH at insertion. list_api_keys removed (digests can't be inverted to plaintext); replaced by api_key_count().

  • PKCE verifier returned as secrecy::SecretStringcrates/turbomcp-auth/src/oauth2/client.rs:425. authorization_code_flow now returns (String, SecretString) instead of (String, String) so the verifier zeroes on drop and won't leak through Debug / log accidentally. (BREAKING — see Migration.)

  • OAuth state validation no longer leaks length through timingcrates/turbomcp-auth/src/oauth2/validation.rs. validate_oauth_state now compares fixed-length SHA-256 digests with subtle::ConstantTimeEq. Pre-3.1 raw strings were compared, and ct_eq short-circuits on length mismatch — a small length oracle.

Transport

  • HTTP server has graceful shutdowncrates/turbomcp-server/src/transport/http.rs. New run_with_shutdown(handler, addr, config, graceful_shutdown) entry point; axum::serve(...).with_graceful_shutdown(shutdown_signal(...)) waits for SIGINT and, on Unix, SIGTERM, then drains in-flight requests up to the configured timeout (max 60s). ServerBuilder::with_graceful_shutdown(Duration) is now actually wired through; pre-3.1 it was a stored-but-ignored knob and SIGTERM aborted in-flight responses.

  • HTTP client constructor returns Result instead of panickingcrates/turbomcp-http/src/transport.rs:303. StreamableHttpClientTransport::new now returns TransportResult<Self>, propagating the underlying reqwest::Client::build() failure. (BREAKING — see Migration.) Pre-3.1 a bad TLS configuration (e.g., a malformed custom CA cert byte slice) would panic the calling process.

  • HTTP endpoint discovery synchronizes via oneshot instead of a 500 ms sleepcrates/turbomcp-http/src/transport.rs. connect() now awaits an endpoint_ready oneshot fired by the SSE task on the first endpoint event, with a timeout bounded by config.timeout. Pre-3.1 a fixed 500 ms wait raced on slow networks / cold caches and the first send() could be routed to a stale endpoint.

  • WebSocket outbound channels are bounded (DoS fix)crates/turbomcp-transport/src/axum/handlers/websocket.rs, axum/websocket_factory.rs. New WS_OUTBOUND_CAPACITY = 1024 constant; both handler paths use mpsc::channel(...) instead of mpsc::unbounded_channel(). A slow / hostile client can no longer drive the server out of memory by reading slower than messages arrive. The bidirectional dispatcher (websocket_bidirectional.rs::WebSocketDispatcher) takes a bounded Sender and awaits send. Pong replies use try_send so a saturated buffer closes the connection rather than stalling the receive loop.

  • STDIO no longer silently drops messages under backpressurecrates/turbomcp-stdio/src/transport.rs:476. The reader task now send().awaits on the bounded message channel rather than try_send-and-drop-on-full. Pre-3.1 a slow consumer caused silent message loss with only a warn! log; request/response correlation broke under load.

  • TCP connections set TCP_NODELAY after accept and connectcrates/turbomcp-tcp/src/transport.rs. MCP messages are typically small and latency-sensitive; without disabling Nagle, each frame could wait up to 200 ms for coalescing.

  • HTTP client exposes async recv_async()crates/turbomcp-http/src/transport.rs. New inherent method that awaits on both the POST response queue and the SSE stream via tokio::select! (biased toward responses). Complements Transport::receive, which is non-blocking by contract; receive docs now call this out explicitly so client code picks the right primitive.

  • SSE chunk reads are timeout-guardedcrates/turbomcp-http/src/transport.rs. StreamableHttpClientConfig::sse_read_timeout (default 5 minutes) wraps each stream.next() in tokio::time::timeout so a silent TCP half-open breaks the SSE task and lets the reconnect loop take over instead of stalling forever.

Protocol

  • ProtocolConfig::default() is now multi-versioncrates/turbomcp-server/src/config.rs. The default supported_versions is now ProtocolVersion::STABLE.to_vec() instead of [LATEST]. Older clients (e.g. on 2025-06-18) are accepted and routed through the existing VersionAdapter infrastructure. Use ProtocolConfig::strict(version) to restore exact-match behavior. Pre-3.1 the default rejected every client not on the latest spec, even though the adapters existed.

  • JSON-RPC error code range validatedcrates/turbomcp-protocol/src/jsonrpc.rs. JsonRpcError::new now logs a tracing::warn! for codes outside the JSON-RPC 2.0 server-error range (-32099..=-32000) and the standardized codes (-32700, -32600, -32601, -32602, -32603). New JsonRpcError::with_validated_code constructor returns Err for out-of-range codes. Pre-3.1 any i32 was silently accepted, risking collision with future spec assignments.

  • URLElicitationRequiredError type addedcrates/turbomcp-protocol/src/types/elicitation.rs. Carries url, description, elicitation_id and a constant ERROR_CODE = -32001. Servers that need URL-mode elicitation but receive a form-mode request can now signal it spec-conformantly.

  • ResourceTemplate::new(name, uri_template) validates RFC 6570 structure at constructioncrates/turbomcp-protocol/src/types/resources.rs. New validate_uri_template helper rejects unbalanced braces and nested {...}. The public uri_template field stays writable so wire-format deserialization still round-trips, but server-side construction now catches typos at build-time.

  • VersionManager::with_default_versions() no longer hides an unwrap()crates/turbomcp-protocol/src/versioning.rs:235. Replaced with an expect("known_versions is non-empty by const construction") that names the contract.

  • CompositeHandler prefix matching no longer mis-splits prefixes containing _ or ://crates/turbomcp-server/src/composite.rs. parse_prefixed_tool / parse_prefixed_uri / `parse_prefi...

Read more

v3.0.14

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 15 Apr 16:45

[3.0.14] - 2026-04-15

Fixed

  • Custom URI schemes now reach registered resource handlers
    • Change: Replaced the hardcoded allowlist (["file", "http", "https", "data", "mcp"]) with a narrow denylist (DANGEROUS_URI_SCHEMES = ["javascript", "vbscript"]).
    • Impact: Custom schemes (e.g., apple-doc://, notion://, slack://) are now correctly dispatched to user-defined handlers instead of being silently rejected.
    • Spec Compliance: Aligns with MCP 2025-11-25 spec (server/resources.mdx), which states: "The protocol defines several standard URI schemes. This list is not exhaustive — implementations are always free to use additional, custom URI schemes."
    • Implementation Details:
      • New function check_uri_scheme_safety in crates/turbomcp-core/src/security.rs (case-insensitive per RFC 3986 §3.1).
      • Updated macro injection in crates/turbomcp-macros/src/server.rs at the read_resource dispatch site.
      • Breaking Change: Error variant InputValidationError::InvalidUriScheme renamed to DangerousUriScheme.
      • Public API: Re-exported DANGEROUS_URI_SCHEMES + check_uri_scheme_safety in turbomcp-core/src/lib.rs (removed ALLOWED_URI_SCHEMES).
    • Security Context:
      • SSRF protection for URIs dereferenced by the SDK remains enforced via turbomcp-proxy's per-deployment scheme config.
      • Icon-URI check in turbomcp-protocol (requiring https: / data:) remains untouched.
    • Testing:
      • Added regression tests in crates/turbomcp-core/src/security.rs for acceptance (weather://, custom+scheme://) and rejection (JavaScript:, VBScript:).
      • Added E2E tests in crates/turbomcp/tests/v3_audit.rs (custom_uri_schemes_reach_registered_handlers, dangerous_uri_schemes_are_still_rejected).

Full Changelog: v3.0.13...v3.0.14

v3.0.13

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 14 Apr 18:28

[3.0.13] - 2026-04-14

Added

  • MCP 2025-11-25 streamable HTTP transport, full lifecyclecrates/turbomcp-server/src/transport/http.rs now implements the complete spec shape: POST / + POST /mcp for JSON-RPC requests, GET / + GET /mcp + GET /sse for Server-Sent Events, and DELETE / + DELETE /mcp for explicit session termination. handle_json_rpc emits 202 Accepted for client JSON-RPC responses and notifications per §4, validates the MCP-Protocol-Version header against the per-session negotiated version, rejects initialize requests that already carry a session id with 400, and returns 404 for terminated sessions. A new build_router() helper backs both the standalone http::run{,_with_config} entry points and ServerBuilder::into_axum_router / into_service, so BYO-Axum deployments pick up the same spec coverage.

  • JSON Schema draft-2020-12 support in ToolInputSchema / ToolOutputSchema — Across turbomcp-types, turbomcp-core, and turbomcp-protocol, schema_type is now Option<Value> (accepts "object" or ["object", "null"]), additional_properties is now Option<Value> (accepts false or a sub-schema like {"type": "string"}), and a new #[serde(flatten)] extra_keywords: HashMap<String, Value> field preserves arbitrary JSON Schema keywords (oneOf, $schema, $defs, description, ...) losslessly through round-trip. Covered by test_tool_schema_preserves_arbitrary_json_schema_keywords. Macros (turbomcp-macros/src/tool.rs), turbomcp-openapi handler, turbomcp-wasm registrations, turbomcp-server/examples/manual_server.rs, and turbomcp-protocol test helpers all updated to the new shape.

  • HTTP origin validation — New OriginValidationConfig struct on turbomcp-server::config with allowed_origins: HashSet<String>, allow_localhost: bool (default true), and allow_any: bool (default false). Exposed on ServerConfigBuilder (origin_validation, allow_origin, allow_origins, allow_localhost_origins, allow_any_origin) and on ServerBuilder (with_origin_validation, with_allowed_origin, allow_localhost_origins, allow_any_origin). The HTTP transport threads the config into every POST / GET / DELETE handler via validate_origin, which also extracts the client IP from ConnectInfo or forwarded headers. Covers the MCP "Servers MUST validate the Origin header" DNS-rebinding mitigation.

  • Request ID deduplication per session — New shared InitializedSessionState in turbomcp-server/src/transport/mod.rs tracks seen_request_ids: HashSet<String>, enforcing the MCP spec requirement that "The request ID MUST NOT have been previously used by the requestor within the same session." HTTP, channel, line (stdio/tcp/unix), and WebSocket transports all wire duplicate requests to a -32600 Invalid Request error. Notifications (id=None) bypass the dedup check.

  • Channel transport initialization lifecycleturbomcp-server/src/transport/channel.rs now enforces the same SessionState / InitializedSessionState lifecycle already present in line and WebSocket transports: rejects non-lifecycle requests before initialize, rejects duplicate initialize, and routes post-init requests through route_request_versioned with the negotiated ProtocolVersion.

  • SSE primer event for resumabilityhandle_sse now yields an initial Event::default().id("<session>-0").data("") before draining the per-subscriber channel, satisfying the MCP spec's "server SHOULD immediately send an SSE event consisting of an event ID and an empty data field in order to prime the client to reconnect (using that event ID as Last-Event-ID)."

  • RESERVED_METHOD_NAME validation errorProtocolValidator::validate_request and validate_notification now emit the dedicated RESERVED_METHOD_NAME error code for methods starting with rpc., per JSON-RPC 2.0 §6 ("Method names that begin with the word rpc followed by a period character are reserved for rpc-internal methods and extensions"). Both request and notification paths covered by new tests.

  • Comprehensive HTTP spec-compliance integration tests — New crates/turbomcp-server/tests/http_transport_spec.rs covers session-id handshake, notifications/initialized202, client JSON-RPC response POST → 202, GET/DELETE session termination on the same endpoint, untrusted origin rejection, configured origin allowance, duplicate request-id rejection, 413 Payload Too Large for oversized bodies (raw-TCP test, race-free), and SSE primer event emission. Nine tests, all green.

  • Channel transport duplicate request-id and silent-notification tests (test_channel_transport_rejects_duplicate_request_ids, test_channel_transport_silent_on_notification_before_init) plus line transport silent-notification test (test_line_transport_silent_on_notification_before_init).

Changed

  • SessionManager routes SSE messages to a single subscriber per session — Previously backed by tokio::sync::broadcast, which delivered every outbound message to every active subscriber for a session. That violated MCP Streamable HTTP §Multiple Connections ("The server MUST send each of its JSON-RPC messages on only one of the connected streams; that is, it MUST NOT broadcast the same message across multiple streams"). SessionData now carries subscribers: Vec<mpsc::UnboundedSender<String>>, and send_to_session / broadcast route each message to exactly one live subscriber per session, dropping dead senders as they go. subscribe_session returns a dedicated mpsc::UnboundedReceiver<String>. Verified by the new send_to_session_routes_to_single_subscriber unit test.

  • ServerBuilder::into_axum_router / into_service delegate to transport::http::build_router — Removed 180+ lines of duplicated handle_json_rpc logic from crates/turbomcp-server/src/builder.rs. BYO-Axum integrations now automatically inherit the full streamable HTTP spec coverage (session management, origin validation, request-id dedup, 413/413 body limits, SSE primer) rather than running a reduced fork.

  • Protocol method-name regex relaxed — From ^[a-zA-Z][a-zA-Z0-9_/]*$ to ^[^\s\x00-\x1F]+$, matching MCP's "just a string" rule and allowing extension-friendly names like namespace.v1/tool-name. The dedicated RESERVED_METHOD_NAME check still catches rpc.*.

  • HTTP client treats 405 Method Not Allowed on GET /sse as "no standalone SSE"crates/turbomcp-http/src/transport.rs SSE connection task now logs and breaks its reconnection loop when the server replies 405, matching MCP spec-compliant servers that do not offer a standalone SSE stream.

  • Oversized HTTP bodies return 413 Payload Too Largebuild_router now adds a tower_http::limit::RequestBodyLimitLayer at the middleware layer, and handle_json_rpc also performs an early Content-Length check and inspects the to_bytes error chain for http_body_util::LengthLimitError as a fallback. Previously mapped to 400 Bad Request.

Fixed

  • JSON-RPC 2.0 §4.1: transports no longer respond to notifications with errors — Line (stdio/tcp/unix), channel, and WebSocket transports previously emitted a JSON-RPC error back to the peer when a notification was rejected (uninitialized session, duplicate request id, ...). Per spec, "Notifications are not confirmable by definition, since they do not have a Response object to be returned." The rejection paths now gate on request.id.is_some() for line and channel transports and use JsonRpcOutgoing::notification_ack() (dropped by should_send) for WebSocket, leaving the wire silent for notifications.

  • turbomcp-wasm wasm_server/server.rs compile failure under --features wasm-server — Two Tool registrations in tool() / tool_with_ctx() initialized extra_keywords: std::collections::HashMap::new(), but the underlying turbomcp-core::types::tools::ToolInputSchema::extra_keywords field is typed hashbrown::HashMap<String, Value> (because the crate is no_std + alloc). Replaced with HashMap::new() so the already-imported hashbrown alias applies. Caught by cargo check --workspace --all-features.

  • turbomcp-proxy::proxy::backend::convert_tools compile failure — The introspection ToolSpec's ToolInputSchema carries a plain schema_type: String and flattened additional: HashMap<String, Value>, while turbomcp-protocol::types::tools::ToolInputSchema migrated to Option<Value> for both schema_type and additional_properties. convert_tools now extracts the schema-type string with an "object" fallback, copies additional_properties straight into additional, and propagates extra_keywords so arbitrary JSON Schema keywords survive proxy introspection.

Full Changelog: v3.0.12...v3.0.13

v3.0.12

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 14 Apr 01:14

[3.0.12] - 2026-04-13

Added

  • Draft extensions capability — Both ClientCapabilities and ServerCapabilities now carry an optional extensions: HashMap<String, Value> map for opt-in key/value capability settings, available across turbomcp-types, turbomcp-core, and turbomcp-protocol. New builder helpers (with_extensions, add_extension) plus a completions server capability round out the stable 2025-11-25 shape. CapabilityMatcher treats extensions as mutually opt-in: negotiation requires both sides to declare an extension and silently disables mismatches rather than failing the session.

  • Handler-driven initialize response — New McpHandler::server_capabilities() trait method with a default that derives tools/resources/prompts from existing listings, letting handlers override to advertise tasks, logging, completions, or draft extensions without forking the router. build_initialize_result now serializes the handler's full ServerCapabilities and ServerInfo via serde_json::to_value, so description, title, websiteUrl, and icons survive the initialize response instead of being silently dropped.

  • Client::initialize_with_request() — New entry point that accepts a caller-built InitializeRequest, giving applications a stable way to opt into draft protocol versions or explicit capability shapes. The ergonomic Client::initialize() default path and auto-connect logic now share the same implementation.

  • RequiredCapabilities / ClientCapabilities extensions field (turbomcp-server::config) — New extensions: HashSet<String> field with builder, validation, and from_params parsing, letting deployments require specific draft extensions from clients.

  • MCP 2025-11-25 icons plural migration in turbomcp-core (SEP-973) — Replaces icon: Option<Icon> with icons: Option<Vec<Icon>> on Implementation, Tool, Resource, ResourceTemplate, and Prompt. Adds description and website_url to Implementation with builder helpers. turbomcp-types and turbomcp-protocol already carried the plural shape; this catches turbomcp-core's parallel types and every literal constructor across turbomcp-grpc and turbomcp-wasm up to the spec.

  • gRPC capability + metadata paritymcp.proto gains EmptyCapability, elicitation, client/server task capability trees, CompletionCapability, and ExtensionsCapabilities messages, wired into ClientCapabilities and ServerCapabilities. repeated Icon icons replaces singular icon on Implementation, Tool, Resource, ResourceTemplate, and Prompt, with new title, description, website_url, and size fields per spec. convert.rs bidirectional conversions preserve extensions, elicitation, tasks, completions, and experimental capabilities via new encode_json_map / decode_json_map / empty_capability_from_map helpers, covered by round-trip tests for Implementation metadata and extensions + task tools.

Changed

  • Version adapters strip draft extensionsV2025_11_25Adapter and V2025_06_18Adapter now strip the draft extensions field from filter_capabilities and from initialize results; DraftAdapter passes it through. Regression tests cover both stripping and draft passthrough.

Fixed

  • wasm-server feature compiles again — The wasm-server-gated files turbomcp-wasm/src/wasm_server/server.rs and composite.rs carried two latent build breaks introduced during the icons + extensions work (they are skipped by default cargo check --workspace --all-targets). Removes spurious website_url: None from eight Tool/Resource/ResourceTemplate/Prompt literals (only Implementation carries website_url), and adds completions: None + extensions: None to the two ServerCapabilities constructors to match the new core shape. Verified with cargo check, cargo clippy -- -D warnings, and cargo test --lib -p turbomcp-wasm --features wasm-server (119 tests passing).

  • Stdio backend spawn tests no longer depend on Python (turbomcp-proxy) — Tests hardcoded python server.py / python -c '...', which fail on systems where only python3 is on PATH (current macOS default) and race because python -c exits before wait_for_ready can observe a running child. Replaced with /bin/cat, gated #[cfg(unix)] since Windows handles subprocess spawning differently.

  • Workspace internal dep versions unified at 3.0.11turbomcp-macros, turbomcp-proxy, turbomcp-server, and turbomcp-telemetry still pinned internal deps to the stale 3.0.7 version while the rest of the tree had moved on. Switched to workspace = true so they inherit the workspace-declared version in one place; turbomcp's remaining explicit path deps (which must keep default-features = false) bumped to match.

Full Changelog: v3.0.11...v3.0.12

v3.0.11

Choose a tag to compare

@nicholasjpaterno nicholasjpaterno released this 07 Apr 23:49

[3.0.11] - 2026-04-02

Added

  • RequestContext::notify_client() — New method on the server-side request context for sending JSON-RPC notifications to connected clients. Enables server handlers to push notifications/tools/list_changed, progress events, and other fire-and-forget messages over bidirectional transports (channel, WebSocket, SSE). Accepts impl AsRef<str> for ergonomic method names.

  • Client::trigger_tool_list_changed() — Programmatically invokes the registered ToolListChangedHandler, returning HandlerResult<()> so callers can observe failures. Designed for testing and external notification integration scenarios.

  • Client::has_tool_list_changed_handler() — Check whether a tool list changed handler is registered, consistent with existing has_roots_handler(), has_elicitation_handler(), etc.

  • HandlerRegistry::has_tool_list_changed_handler() — Proper has_* predicate on the registry, avoiding unnecessary Arc clone through get_*().is_some().

Full Changelog: v3.0.10...v3.0.11