Releases: Epistates/turbomcp
Release list
v3.1.5
[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 policy —
VisibilityLayernow supports
exact allowed/disabled/hidden rules for tools, resources, resource templates,
and prompts, plus a strict read-only tool profile. Consumers can load a
VisibilityConfigfrom application config to reducetools/listcontext
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 —
VisibilityConfigandVisibilityLayerexposewith_allowed_*,
with_disabled_*, andwith_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
reservesdata: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 aVisibilityLayerbefore 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
- Fix/windows unix cfg guards by @ForrestThump in #13
New Contributors
- @ForrestThump made their first contribution in #13
Full Changelog: v3.1.4...v3.1.5
v3.1.4
[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 anMcpSessionto 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/listsupport 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-Idresolve 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 metadata —
ResourceTemplate
responses retainuriTemplate,name,title,description,mimeType,
icons,annotations, and_metainstead of reducing templates to strings. - Proxy resource template forwarding preserves backend data — proxy
introspection andresources/templates/listforwarding now keep template
metadata intact and emit the spec field nameresourceTemplates.
Full Changelog: v3.1.3...v3.1.4
v3.1.3
[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
whosejsonrpcmember 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 honorServerConfig.max_message_size,
WebSocket upgrade paths validateOrigin, 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 privatedmember are rejected, while interoperable public EC
JWKs that omituseare accepted as signature keys. - WASM streamable HTTP session headers validate without panicking — invalid
Mcp-Session-Idheaders now return400 Bad Requestinstead of constructing
unchecked session IDs.
Changed
- Proxy frontends migrated to supported server transports — runtime and CLI
serving paths now useturbomcp-serverHTTP/WebSocket plumbing instead of the
removedturbomcp-transport::axumcompatibility 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, includingreqwest
0.13.3,tower-http0.6.9,metrics0.24.5,
metrics-exporter-prometheus0.18.3,serde_with3.19,redis1.2.1,
signature3.0,utoipa5.5,wasm-bindgen0.2.120, and
js-sys/web-sys0.3.97.cargo outdated --workspace --root-deps-only
reports all direct dependencies current after the refresh.
Removed
- Deprecated
turbomcp-transport::axumsubtree deleted — callers should use
turbomcp_server::transport::http,McpServerExt::run_http, or
McpServerExt::into_axum_router. - Deprecated transport and client shims removed —
StreamingTransport,
TransportType::Grpc/TransportType::Quic, no-op WebSocket compression/TLS
builders, the no-op gRPC TLS client config field, and
turbomcp-client::sampling::ServerInfoare gone.
Fixed
- SSE and streaming decoders handle edge framing cases — CRLF events,
whitespace-only lines, trailing newlines, emptydata:events, sticky
overflow states, andlast_event_idpropagation now behave consistently. - SSE incremental parsing handles split UTF-8 sequences — partial multibyte
code points are buffered acrossfeed()calls instead of being dropped or
corrupting subsequent events. - JSON-RPC error classification and capping tightened —
standard_kind()
only reports the five JSON-RPC standard errors, while errordatacapping now
recurses through arrays as well as objects. - JSON-RPC response payload validation is presence-aware —
result: null
is now accepted as a valid success response, while responses with both
resultanderroror 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: nullremains compatible with absent params — explicit JSON
nullparams deserialize asNonerather than a structured parameter value.- Spec optional fields are represented accurately — cancellation
notifications accept missingrequestId, 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
URLElicitationRequiredErroruses the same-32042code as the core error
taxonomy. alloc-only builds keep compiling — missingallocimports 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
[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
[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 creation —
crates/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-typesis now the sole canonical home for MCP types — completes the consolidation started in 3.1.0. Duplicate definitions previously living inturbomcp-protocolandturbomcp-corehave been removed; downstream crates re-export fromturbomcp-types. No behavior change for consumers using theturbomcpprelude.RequestContextunified with bidirectional session state —crates/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
StreamableHttpClientTransportdoc example handles initialization errors —crates/turbomcp-http/src/transport.rs. The rustdoc example now propagates theResultreturned bynew()(the 3.1.0 API change) instead of.unwrap()-ing.- WASM cleanup —
crates/turbomcp-wasm. Removed unnecessary.into()calls and tightened test assertions.
Chore
MemoryTokenStoredeprecation 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 updated —
deny.tomlnarrowed 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
[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_expiredis no longer a no-op —crates/turbomcp-auth/src/oauth2/client.rs:626. Pre-3.1 the checkexpires_in == 0treated a relative duration as a countdown clock; it never returnedtrue, so OAuth callers silently forwarded expired bearer tokens forever.TokenInfonow carriesissued_at: Option<SystemTime>(serde-default for back-compat with cached v3.0 token entries), populated byOAuth2Client::token_response_to_token_info. NewTokenInfo::expires_at,is_expired, andis_expired_with_skew(Duration)helpers;is_token_expireddelegates to them with a 60s clock skew. -
DPoP
athclaim is now enforced at the resource server (RFC 9449 §4.3) —crates/turbomcp-dpop/src/proof.rs. New publicProofContext { TokenEndpoint, ResourceServer }enum threaded throughvalidate_proof/parse_and_validate_jwt(BREAKING — see Migration). At a resource server, presenting an access token alongside a proof withoutathnow returnsDpopError::AccessTokenHashFailed. Pre-3.1 a stolen DPoP proof could be paired with a separately-issued access token, defeating sender-constraint binding. New regression testtest_resource_server_requires_ath_when_token_present. -
TLS certificate validation CVEs resolved —
Cargo.lockupdates foraws-lc-sys 0.38.0 → 0.40.0(RUSTSEC-2026-0044, RUSTSEC-2026-0048) andrustls-webpki 0.103.9 → 0.103.12(RUSTSEC-2026-0049, RUSTSEC-2026-0098, RUSTSEC-2026-0099). Affected every outbound HTTPS inturbomcp-auth(OIDC discovery, JWKS),turbomcp-transport, andturbomcp-client.cargo auditnow reports zero open advisories beyond the documentedpaste(compile-time-only) /proc-macro-error/randlow-impact entries. -
JwtValidator::newandMultiIssuerValidator::add_issuernow apply SSRF protection by default —crates/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 anSsrfValidator::default()policy (blocks loopback, RFC 1918, link-local, cloud metadata). Newnew_unchecked/add_issuer_uncheckedopt-outs for test/dev against private OIDC providers. -
DPoP nonce tracker has a bounded capacity and inline cleanup —
crates/turbomcp-dpop/src/proof.rs.MemoryNonceTrackernow supportswith_capacity(usize)(default 1,000,000) with time-ordered eviction triggered at 80% high-water insidetrack_nonce. Pre-3.1 the map was unbounded with no automatic cleanup, andis_nonce_useddid 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.0—crates/turbomcp-auth/src/oauth2/client.rsandcrates/turbomcp-auth/src/oauth2/resource.rs.0.0.0.0is 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 exactly127.0.0.1,[::1], andlocalhost. -
API keys are no longer stored plaintext in memory —
crates/turbomcp-auth/src/providers/api_key.rs.ApiKeyProvidernow stores BLAKE3 digests as the map key; plaintext values are dropped at the end ofadd_api_keyand never retained. Lookup is O(1) over digests with constant-time hashing of the input.add_api_keynow returnsMcpResult<()>and rejects keys shorter thanMIN_API_KEY_LENGTHat insertion.list_api_keysremoved (digests can't be inverted to plaintext); replaced byapi_key_count(). -
PKCE verifier returned as
secrecy::SecretString—crates/turbomcp-auth/src/oauth2/client.rs:425.authorization_code_flownow returns(String, SecretString)instead of(String, String)so the verifier zeroes on drop and won't leak throughDebug/ log accidentally. (BREAKING — see Migration.) -
OAuth
statevalidation no longer leaks length through timing —crates/turbomcp-auth/src/oauth2/validation.rs.validate_oauth_statenow compares fixed-length SHA-256 digests withsubtle::ConstantTimeEq. Pre-3.1 raw strings were compared, andct_eqshort-circuits on length mismatch — a small length oracle.
Transport
-
HTTP server has graceful shutdown —
crates/turbomcp-server/src/transport/http.rs. Newrun_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
Resultinstead of panicking —crates/turbomcp-http/src/transport.rs:303.StreamableHttpClientTransport::newnow returnsTransportResult<Self>, propagating the underlyingreqwest::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
oneshotinstead of a 500 mssleep—crates/turbomcp-http/src/transport.rs.connect()now awaits anendpoint_readyoneshot fired by the SSE task on the firstendpointevent, with a timeout bounded byconfig.timeout. Pre-3.1 a fixed 500 ms wait raced on slow networks / cold caches and the firstsend()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. NewWS_OUTBOUND_CAPACITY = 1024constant; both handler paths usempsc::channel(...)instead ofmpsc::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 boundedSenderandawaitssend. Pong replies usetry_sendso a saturated buffer closes the connection rather than stalling the receive loop. -
STDIO no longer silently drops messages under backpressure —
crates/turbomcp-stdio/src/transport.rs:476. The reader task nowsend().awaits on the bounded message channel rather thantry_send-and-drop-on-full. Pre-3.1 a slow consumer caused silent message loss with only awarn!log; request/response correlation broke under load. -
TCP connections set
TCP_NODELAYafter accept and connect —crates/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 viatokio::select!(biased toward responses). ComplementsTransport::receive, which is non-blocking by contract;receivedocs now call this out explicitly so client code picks the right primitive. -
SSE chunk reads are timeout-guarded —
crates/turbomcp-http/src/transport.rs.StreamableHttpClientConfig::sse_read_timeout(default 5 minutes) wraps eachstream.next()intokio::time::timeoutso 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-version —crates/turbomcp-server/src/config.rs. The defaultsupported_versionsis nowProtocolVersion::STABLE.to_vec()instead of[LATEST]. Older clients (e.g. on 2025-06-18) are accepted and routed through the existingVersionAdapterinfrastructure. UseProtocolConfig::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 validated —
crates/turbomcp-protocol/src/jsonrpc.rs.JsonRpcError::newnow logs atracing::warn!for codes outside the JSON-RPC 2.0 server-error range (-32099..=-32000) and the standardized codes (-32700, -32600, -32601, -32602, -32603). NewJsonRpcError::with_validated_codeconstructor returnsErrfor out-of-range codes. Pre-3.1 anyi32was silently accepted, risking collision with future spec assignments. -
URLElicitationRequiredErrortype added —crates/turbomcp-protocol/src/types/elicitation.rs. Carriesurl,description,elicitation_idand a constantERROR_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 construction —crates/turbomcp-protocol/src/types/resources.rs. Newvalidate_uri_templatehelper rejects unbalanced braces and nested{...}. The publicuri_templatefield 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 anunwrap()—crates/turbomcp-protocol/src/versioning.rs:235. Replaced with anexpect("known_versions is non-empty by const construction")that names the contract. -
CompositeHandlerprefix matching no longer mis-splits prefixes containing_or://—crates/turbomcp-server/src/composite.rs.parse_prefixed_tool/parse_prefixed_uri/ `parse_prefi...
v3.0.14
[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_safetyincrates/turbomcp-core/src/security.rs(case-insensitive per RFC 3986 §3.1). - Updated macro injection in
crates/turbomcp-macros/src/server.rsat theread_resourcedispatch site. - Breaking Change: Error variant
InputValidationError::InvalidUriSchemerenamed toDangerousUriScheme. - Public API: Re-exported
DANGEROUS_URI_SCHEMES+check_uri_scheme_safetyinturbomcp-core/src/lib.rs(removedALLOWED_URI_SCHEMES).
- New function
- 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(requiringhttps:/data:) remains untouched.
- SSRF protection for URIs dereferenced by the SDK remains enforced via
- Testing:
- Added regression tests in
crates/turbomcp-core/src/security.rsfor 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).
- Added regression tests in
- Change: Replaced the hardcoded allowlist (
Full Changelog: v3.0.13...v3.0.14
v3.0.13
[3.0.13] - 2026-04-14
Added
-
MCP 2025-11-25 streamable HTTP transport, full lifecycle —
crates/turbomcp-server/src/transport/http.rsnow implements the complete spec shape:POST /+POST /mcpfor JSON-RPC requests,GET /+GET /mcp+GET /ssefor Server-Sent Events, andDELETE /+DELETE /mcpfor explicit session termination.handle_json_rpcemits202 Acceptedfor client JSON-RPC responses and notifications per §4, validates theMCP-Protocol-Versionheader against the per-session negotiated version, rejectsinitializerequests that already carry a session id with400, and returns404for terminated sessions. A newbuild_router()helper backs both the standalonehttp::run{,_with_config}entry points andServerBuilder::into_axum_router/into_service, so BYO-Axum deployments pick up the same spec coverage. -
JSON Schema draft-2020-12 support in
ToolInputSchema/ToolOutputSchema— Acrossturbomcp-types,turbomcp-core, andturbomcp-protocol,schema_typeis nowOption<Value>(accepts"object"or["object", "null"]),additional_propertiesis nowOption<Value>(acceptsfalseor 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 bytest_tool_schema_preserves_arbitrary_json_schema_keywords. Macros (turbomcp-macros/src/tool.rs),turbomcp-openapihandler,turbomcp-wasmregistrations,turbomcp-server/examples/manual_server.rs, andturbomcp-protocoltest helpers all updated to the new shape. -
HTTP origin validation — New
OriginValidationConfigstruct onturbomcp-server::configwithallowed_origins: HashSet<String>,allow_localhost: bool(defaulttrue), andallow_any: bool(defaultfalse). Exposed onServerConfigBuilder(origin_validation,allow_origin,allow_origins,allow_localhost_origins,allow_any_origin) and onServerBuilder(with_origin_validation,with_allowed_origin,allow_localhost_origins,allow_any_origin). The HTTP transport threads the config into every POST / GET / DELETE handler viavalidate_origin, which also extracts the client IP fromConnectInfoor forwarded headers. Covers the MCP "Servers MUST validate the Origin header" DNS-rebinding mitigation. -
Request ID deduplication per session — New shared
InitializedSessionStateinturbomcp-server/src/transport/mod.rstracksseen_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 Requesterror. Notifications (id=None) bypass the dedup check. -
Channel transport initialization lifecycle —
turbomcp-server/src/transport/channel.rsnow enforces the sameSessionState/InitializedSessionStatelifecycle already present in line and WebSocket transports: rejects non-lifecycle requests beforeinitialize, rejects duplicateinitialize, and routes post-init requests throughroute_request_versionedwith the negotiatedProtocolVersion. -
SSE primer event for resumability —
handle_ssenow yields an initialEvent::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_NAMEvalidation error —ProtocolValidator::validate_requestandvalidate_notificationnow emit the dedicatedRESERVED_METHOD_NAMEerror code for methods starting withrpc., 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.rscovers session-id handshake,notifications/initialized→202, 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 Largefor 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
-
SessionManagerroutes SSE messages to a single subscriber per session — Previously backed bytokio::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").SessionDatanow carriessubscribers: Vec<mpsc::UnboundedSender<String>>, andsend_to_session/broadcastroute each message to exactly one live subscriber per session, dropping dead senders as they go.subscribe_sessionreturns a dedicatedmpsc::UnboundedReceiver<String>. Verified by the newsend_to_session_routes_to_single_subscriberunit test. -
ServerBuilder::into_axum_router/into_servicedelegate totransport::http::build_router— Removed 180+ lines of duplicatedhandle_json_rpclogic fromcrates/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 likenamespace.v1/tool-name. The dedicatedRESERVED_METHOD_NAMEcheck still catchesrpc.*. -
HTTP client treats
405 Method Not Allowedon GET/sseas "no standalone SSE" —crates/turbomcp-http/src/transport.rsSSE 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 Large—build_routernow adds atower_http::limit::RequestBodyLimitLayerat the middleware layer, andhandle_json_rpcalso performs an early Content-Length check and inspects theto_byteserror chain forhttp_body_util::LengthLimitErroras a fallback. Previously mapped to400 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 useJsonRpcOutgoing::notification_ack()(dropped byshould_send) for WebSocket, leaving the wire silent for notifications. -
turbomcp-wasm wasm_server/server.rscompile failure under--features wasm-server— TwoToolregistrations intool()/tool_with_ctx()initializedextra_keywords: std::collections::HashMap::new(), but the underlyingturbomcp-core::types::tools::ToolInputSchema::extra_keywordsfield is typedhashbrown::HashMap<String, Value>(because the crate isno_std + alloc). Replaced withHashMap::new()so the already-importedhashbrownalias applies. Caught bycargo check --workspace --all-features. -
turbomcp-proxy::proxy::backend::convert_toolscompile failure — The introspectionToolSpec'sToolInputSchemacarries a plainschema_type: Stringand flattenedadditional: HashMap<String, Value>, whileturbomcp-protocol::types::tools::ToolInputSchemamigrated toOption<Value>for bothschema_typeandadditional_properties.convert_toolsnow extracts the schema-type string with an"object"fallback, copiesadditional_propertiesstraight intoadditional, and propagatesextra_keywordsso arbitrary JSON Schema keywords survive proxy introspection.
Full Changelog: v3.0.12...v3.0.13
v3.0.12
[3.0.12] - 2026-04-13
Added
-
Draft
extensionscapability — BothClientCapabilitiesandServerCapabilitiesnow carry an optionalextensions: HashMap<String, Value>map for opt-in key/value capability settings, available acrossturbomcp-types,turbomcp-core, andturbomcp-protocol. New builder helpers (with_extensions,add_extension) plus acompletionsserver capability round out the stable 2025-11-25 shape.CapabilityMatchertreats extensions as mutually opt-in: negotiation requires both sides to declare an extension and silently disables mismatches rather than failing the session. -
Handler-driven
initializeresponse — NewMcpHandler::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_resultnow serializes the handler's fullServerCapabilitiesandServerInfoviaserde_json::to_value, sodescription,title,websiteUrl, andiconssurvive the initialize response instead of being silently dropped. -
Client::initialize_with_request()— New entry point that accepts a caller-builtInitializeRequest, giving applications a stable way to opt into draft protocol versions or explicit capability shapes. The ergonomicClient::initialize()default path and auto-connect logic now share the same implementation. -
RequiredCapabilities/ClientCapabilitiesextensions field (turbomcp-server::config) — Newextensions: HashSet<String>field with builder, validation, andfrom_paramsparsing, letting deployments require specific draft extensions from clients. -
MCP 2025-11-25 icons plural migration in
turbomcp-core(SEP-973) — Replacesicon: Option<Icon>withicons: Option<Vec<Icon>>onImplementation,Tool,Resource,ResourceTemplate, andPrompt. Addsdescriptionandwebsite_urltoImplementationwith builder helpers.turbomcp-typesandturbomcp-protocolalready carried the plural shape; this catchesturbomcp-core's parallel types and every literal constructor acrossturbomcp-grpcandturbomcp-wasmup to the spec. -
gRPC capability + metadata parity —
mcp.protogainsEmptyCapability, elicitation, client/server task capability trees,CompletionCapability, andExtensionsCapabilitiesmessages, wired intoClientCapabilitiesandServerCapabilities.repeated Icon iconsreplaces singularicononImplementation,Tool,Resource,ResourceTemplate, andPrompt, with newtitle,description,website_url, andsizefields per spec.convert.rsbidirectional conversions preserve extensions, elicitation, tasks, completions, and experimental capabilities via newencode_json_map/decode_json_map/empty_capability_from_maphelpers, covered by round-trip tests forImplementationmetadata and extensions + task tools.
Changed
- Version adapters strip draft
extensions—V2025_11_25AdapterandV2025_06_18Adapternow strip the draftextensionsfield fromfilter_capabilitiesand frominitializeresults;DraftAdapterpasses it through. Regression tests cover both stripping and draft passthrough.
Fixed
-
wasm-serverfeature compiles again — Thewasm-server-gated filesturbomcp-wasm/src/wasm_server/server.rsandcomposite.rscarried two latent build breaks introduced during the icons + extensions work (they are skipped by defaultcargo check --workspace --all-targets). Removes spuriouswebsite_url: Nonefrom eightTool/Resource/ResourceTemplate/Promptliterals (onlyImplementationcarrieswebsite_url), and addscompletions: None+extensions: Noneto the twoServerCapabilitiesconstructors to match the new core shape. Verified withcargo check,cargo clippy -- -D warnings, andcargo test --lib -p turbomcp-wasm --features wasm-server(119 tests passing). -
Stdio backend spawn tests no longer depend on Python (
turbomcp-proxy) — Tests hardcodedpython server.py/python -c '...', which fail on systems where onlypython3is onPATH(current macOS default) and race becausepython -cexits beforewait_for_readycan 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.11 —
turbomcp-macros,turbomcp-proxy,turbomcp-server, andturbomcp-telemetrystill pinned internal deps to the stale 3.0.7 version while the rest of the tree had moved on. Switched toworkspace = trueso they inherit the workspace-declared version in one place;turbomcp's remaining explicit path deps (which must keepdefault-features = false) bumped to match.
Full Changelog: v3.0.11...v3.0.12
v3.0.11
[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 pushnotifications/tools/list_changed, progress events, and other fire-and-forget messages over bidirectional transports (channel, WebSocket, SSE). Acceptsimpl AsRef<str>for ergonomic method names. -
Client::trigger_tool_list_changed()— Programmatically invokes the registeredToolListChangedHandler, returningHandlerResult<()>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 existinghas_roots_handler(),has_elicitation_handler(), etc. -
HandlerRegistry::has_tool_list_changed_handler()— Properhas_*predicate on the registry, avoiding unnecessaryArcclone throughget_*().is_some().
Full Changelog: v3.0.10...v3.0.11