Skip to content

Rationalize feature flags for v3.0 #1067

Description

@DaleSeo

Labels:T-enhancement, T-config, T-documentation

Problem

rmcp exposes 26 feature flags (including implicit ones). They have grown organically — roughly one flag per new optional dependency — rather than from a user-facing design. Concretely:

  • Names mix four unrelated concerns. Role (client, server), protocol capability (elicitation, request-state, auth), transport (transport-io, transport-streamable-http-*), and raw dependency crate names (base64, uuid, schemars, tower, reqwest) all sit in one flat list with no way to tell them apart.
  • Internal building blocks look like public flags.client-side-sse, server-side-http, transport-streamable-http-server-session, base64, uuid all appear on docs.rs but should never be enabled directly. Meanwhile __reqwestis correctly hidden — so the convention exists but is applied to exactly one flag.
  • Common use cases need 3+ flags, and one of them is a silent trap. A Streamable HTTP client needs client + transport-streamable-http-client-reqwest + reqwest. Omit the last one and it still compiles — reqwest is pulled without any TLS backend, so every https:// request fails at runtime. Our own conformance/Cargo.toml does exactly this; it survives only because it targets http://localhost.
  • Protocol capabilities are gated inconsistently.elicitation is opt-in (only to gate the url crate), while sampling, tasks (SEP-2663), and response caching (SEP-2549) are always on. There is no principle a user can infer.
  • local violates Cargo's additivity contract. It removes Send bounds crate-wide for WASM/single-threaded use. Because Cargo features are additive, any crate anywhere in the dependency graph enabling local silently changes trait bounds for everyone. The name gives no hint of this, and it is undocumented.

Use cases today

What a user must put in Cargo.toml right now, per scenario:

Use caseRequired features todayPain
Stdio server (most common server)default (server, macros, base64) + transport-iotransport-io doesn't say "stdio"; spec's term is stdio transport
Stdio client (spawns a server)client, transport-child-process, default-features = falsedefault drags in server + schemars + pastey the client never uses
Streamable HTTP serverserver, macros, transport-streamable-http-serverOK; tower integration arrives via an internal flag
Streamable HTTP clientclient, transport-streamable-http-client-reqwest, reqwestTrap: omit reqwest → compiles, but all TLS fails at runtime
HTTP client, non-default TLSabove but reqwest-native-tls / reqwest-tls-no-providerreqwest actually means "reqwest + rustls"; segment order inconsistent (reqwest-native-tls vs reqwest-tls-no-provider)
HTTP client, custom HTTP stackclient, transport-streamable-http-clientfine
HTTP client over Unix socketclient, transport-streamable-http-client-unix-socketsix-segment name; undocumented in README
OAuth client+ auth (+ auth-client-credentials-jwt)auth pulls reqwest via __reqwest — same silent no-TLS trap
Server using elicitation+ elicitationthe only protocol capability behind a flag; exists only to gate url
Sealed request state (SEP-2322)+ request-statefine (real crypto deps), but undocumented in README
WASM / single-threadedlocal (+ transport-worker)non-additive; opaque name; undocumented

Dead remnant: transport-ws exists only as a comment in Cargo.toml, yet src/transport/ws.rs and a commented #[cfg] in transport.rs remain.

Proposal

A naming taxonomy — one rule per axis

AxisConventionExamples
Rolebare wordclient, server
Protocol capabilityspec terminology, kebab-caserequest-state, auth
Transporttransport- prefix, spec terminologytransport-stdio, transport-streamable-http-server
Ecosystem integrationcrate name (established Rust convention)tower
TLS backendtls- prefixtls-rustls, tls-native, tls-no-provider
Use-case bundle<role>-<transport>client-http, server-stdio
Internal building block__ prefix, hidden from docs__server-side-http
1. Renames (v3.0, with one-release aliases)
TodayProposedRationale
transport-iotransport-stdiomatch spec vocabulary; users search "stdio"
reqwesttls-rustlsthe flag's real meaning is the TLS choice, not the HTTP crate
reqwest-native-tlstls-nativeconsistent tls- prefix + order
reqwest-tls-no-providertls-no-providersame
localunsync (or replace with cfg(target_family = "wasm"))name should say what it does; ideally not a feature at all, since it is non-additive
which-commandfold into transport-child-process (or command-path-lookup)named after the which crate, not the behavior

Old names stay as empty forwarding aliases (transport-io = ["transport-stdio"]) for one major cycle, then are removed.

2. Un-gate capabilities that only exist to hide a trivial dep (v3.0)
TodayProposedRationale
elicitationalways ongates a core capability while nothing else (sampling/tasks/caching) is gated. Correction:url is not a trivial dep — making it mandatory adds 29 crates to a minimal build via idna → ICU4X (37 → 66). Un-gating therefore also requires the elicitation API to take URLs as plain strings, so url stays behind auth. New rule: gate a capability only when it carries a heavy dep — request-state (hmac/sha2) and auth (oauth2) stay opt-in
base64always on (already in defaults)implicit dep-named flag gating three image-content helpers
uuidhidden via dep:uuidimplicit dep-named flag; pure implementation detail
3. Hide internal building blocks (v3.0)

client-side-sse, server-side-http, transport-streamable-http-server-session get the __ prefix (matching __reqwest) and leave the docs.rs feature list. Users compose transports; they don't assemble the parts.

transport-worker was originally listed here too, but it stays public: crates/rmcp/README.md and the transport module docs both present implementing Worker as one of the ways to build a transport, so hiding the flag would make a documented extension point unreachable.

4. Use-case meta-features (non-breaking, can ship now)
server-stdio = ["server", "macros", "transport-stdio"]
server-http = ["server", "macros", "transport-streamable-http-server"]
client-stdio = ["client", "transport-child-process"]
client-http = ["client", "transport-streamable-http-client-reqwest", "tls-rustls"]

Getting-started docs become one flag per scenario. client-http bakes in rustls; the raw no-TLS combination stays legal (plain-http:// deployments need it) but becomes an opt-in, not a pitfall.

5. Housekeeping (non-breaking, can ship now)
  • README feature table documents ~13 of 26 flags; document all public ones (missing: request-state, auth-client-credentials-jwt, which-command, tower, local, unix-socket transport).
  • Delete src/transport/ws.rs and the commented transport-ws remnants (recoverable from git if WebSocket ever lands).
  • Document the TLS pitfall until the meta-features land.

Use cases after this proposal

Use caseFeatures after
Stdio serverserver-stdio (or defaults + transport-stdio)
Stdio clientclient-stdio, default-features = false
Streamable HTTP serverserver-http
Streamable HTTP clientclient-http
HTTP client, native TLSclient, transport-streamable-http-client-reqwest, tls-native
HTTP client, custom stackclient, transport-streamable-http-client
OAuth clientclient-http, auth
Server using elicitationnothing extra — always available
Sealed request state+ request-state
WASM / single-threadedunsync (or automatic via target cfg)

Rollout

Ship now (minor, non-breaking):

  • Use-case meta-features (§4)
  • README coverage + TLS-pitfall docs (§5)
  • Delete ws.rs / transport-ws remnants (§5)

v3.0 (breaking):

  • Renames with forwarding aliases (§1)
  • Un-gate elicitation / base64, hide uuid (§2)
  • __-prefix internal flags (§3)

v4.0:

  • Drop the forwarding aliases

Open questions

  1. local → renamed feature vs. cfg(target_family = "wasm")? Does anyone use local on native targets (e.g. LocalSet-based single-threaded servers)? If yes, a renamed feature stays; if wasm-only, target cfg is strictly better — it removes the additivity violation entirely.
  2. Should defaults shrink? Shrinking away from server/macros is a bigger break for the majority (server authors). This proposal leaves defaults unchanged.
  3. tls-* vs keeping reqwest-tls-*? If a second HTTP backend ever lands, tls-* generalizes better; reqwest-tls-* is more honest today. Proposal prefers tls-*.

Metadata

Metadata

Assignees

Labels

P2Medium: important but non-blocking improvementT-configConfiguration file changesT-documentationDocumentation improvementsT-enhancementNew features and enhancements

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
     blocks
    (function() {
    function addCopyButtons() {
    document.querySelectorAll('pre code').forEach(function(codeBlock) {
    if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
    codeBlock.parentElement.setAttribute('data-copy-added', 'true');
    var btn = document.createElement('button');
    btn.textContent = 'Copy';
    btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
    btn.onmouseover = function() { this.style.opacity = '1'; };
    btn.onmouseout = function() { this.style.opacity = '0.7'; };
    btn.onclick = function() {
    navigator.clipboard.writeText(codeBlock.textContent).then(function() {
    btn.textContent = 'Copied!';
    setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
    });
    };
    codeBlock.parentElement.style.position = 'relative';
    codeBlock.parentElement.appendChild(btn);
    });
    }
    addCopyButtons();
    // Re-run on dynamic content
    var observer = new MutationObserver(addCopyButtons);
    observer.observe(document.body, { childList: true, subtree: true });
    })();
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    Rationalize feature flags for v3.0 · Issue #1067 · modelcontextprotocol/rust-sdk · GitHub
    Skip to content

    Rationalize feature flags for v3.0 #1067

    Description

    @DaleSeo

    Labels:T-enhancement, T-config, T-documentation

    Problem

    rmcp exposes 26 feature flags (including implicit ones). They have grown organically — roughly one flag per new optional dependency — rather than from a user-facing design. Concretely:

    • Names mix four unrelated concerns. Role (client, server), protocol capability (elicitation, request-state, auth), transport (transport-io, transport-streamable-http-*), and raw dependency crate names (base64, uuid, schemars, tower, reqwest) all sit in one flat list with no way to tell them apart.
    • Internal building blocks look like public flags.client-side-sse, server-side-http, transport-streamable-http-server-session, base64, uuid all appear on docs.rs but should never be enabled directly. Meanwhile __reqwestis correctly hidden — so the convention exists but is applied to exactly one flag.
    • Common use cases need 3+ flags, and one of them is a silent trap. A Streamable HTTP client needs client + transport-streamable-http-client-reqwest + reqwest. Omit the last one and it still compiles — reqwest is pulled without any TLS backend, so every https:// request fails at runtime. Our own conformance/Cargo.toml does exactly this; it survives only because it targets http://localhost.
    • Protocol capabilities are gated inconsistently.elicitation is opt-in (only to gate the url crate), while sampling, tasks (SEP-2663), and response caching (SEP-2549) are always on. There is no principle a user can infer.
    • local violates Cargo's additivity contract. It removes Send bounds crate-wide for WASM/single-threaded use. Because Cargo features are additive, any crate anywhere in the dependency graph enabling local silently changes trait bounds for everyone. The name gives no hint of this, and it is undocumented.

    Use cases today

    What a user must put in Cargo.toml right now, per scenario:

    Use caseRequired features todayPain
    Stdio server (most common server)default (server, macros, base64) + transport-iotransport-io doesn't say "stdio"; spec's term is stdio transport
    Stdio client (spawns a server)client, transport-child-process, default-features = falsedefault drags in server + schemars + pastey the client never uses
    Streamable HTTP serverserver, macros, transport-streamable-http-serverOK; tower integration arrives via an internal flag
    Streamable HTTP clientclient, transport-streamable-http-client-reqwest, reqwestTrap: omit reqwest → compiles, but all TLS fails at runtime
    HTTP client, non-default TLSabove but reqwest-native-tls / reqwest-tls-no-providerreqwest actually means "reqwest + rustls"; segment order inconsistent (reqwest-native-tls vs reqwest-tls-no-provider)
    HTTP client, custom HTTP stackclient, transport-streamable-http-clientfine
    HTTP client over Unix socketclient, transport-streamable-http-client-unix-socketsix-segment name; undocumented in README
    OAuth client+ auth (+ auth-client-credentials-jwt)auth pulls reqwest via __reqwest — same silent no-TLS trap
    Server using elicitation+ elicitationthe only protocol capability behind a flag; exists only to gate url
    Sealed request state (SEP-2322)+ request-statefine (real crypto deps), but undocumented in README
    WASM / single-threadedlocal (+ transport-worker)non-additive; opaque name; undocumented

    Dead remnant: transport-ws exists only as a comment in Cargo.toml, yet src/transport/ws.rs and a commented #[cfg] in transport.rs remain.

    Proposal

    A naming taxonomy — one rule per axis

    AxisConventionExamples
    Rolebare wordclient, server
    Protocol capabilityspec terminology, kebab-caserequest-state, auth
    Transporttransport- prefix, spec terminologytransport-stdio, transport-streamable-http-server
    Ecosystem integrationcrate name (established Rust convention)tower
    TLS backendtls- prefixtls-rustls, tls-native, tls-no-provider
    Use-case bundle<role>-<transport>client-http, server-stdio
    Internal building block__ prefix, hidden from docs__server-side-http
    1. Renames (v3.0, with one-release aliases)
    TodayProposedRationale
    transport-iotransport-stdiomatch spec vocabulary; users search "stdio"
    reqwesttls-rustlsthe flag's real meaning is the TLS choice, not the HTTP crate
    reqwest-native-tlstls-nativeconsistent tls- prefix + order
    reqwest-tls-no-providertls-no-providersame
    localunsync (or replace with cfg(target_family = "wasm"))name should say what it does; ideally not a feature at all, since it is non-additive
    which-commandfold into transport-child-process (or command-path-lookup)named after the which crate, not the behavior

    Old names stay as empty forwarding aliases (transport-io = ["transport-stdio"]) for one major cycle, then are removed.

    2. Un-gate capabilities that only exist to hide a trivial dep (v3.0)
    TodayProposedRationale
    elicitationalways ongates a core capability while nothing else (sampling/tasks/caching) is gated. Correction:url is not a trivial dep — making it mandatory adds 29 crates to a minimal build via idna → ICU4X (37 → 66). Un-gating therefore also requires the elicitation API to take URLs as plain strings, so url stays behind auth. New rule: gate a capability only when it carries a heavy dep — request-state (hmac/sha2) and auth (oauth2) stay opt-in
    base64always on (already in defaults)implicit dep-named flag gating three image-content helpers
    uuidhidden via dep:uuidimplicit dep-named flag; pure implementation detail
    3. Hide internal building blocks (v3.0)

    client-side-sse, server-side-http, transport-streamable-http-server-session get the __ prefix (matching __reqwest) and leave the docs.rs feature list. Users compose transports; they don't assemble the parts.

    transport-worker was originally listed here too, but it stays public: crates/rmcp/README.md and the transport module docs both present implementing Worker as one of the ways to build a transport, so hiding the flag would make a documented extension point unreachable.

    4. Use-case meta-features (non-breaking, can ship now)
    server-stdio = ["server", "macros", "transport-stdio"]
    server-http = ["server", "macros", "transport-streamable-http-server"]
    client-stdio = ["client", "transport-child-process"]
    client-http = ["client", "transport-streamable-http-client-reqwest", "tls-rustls"]

    Getting-started docs become one flag per scenario. client-http bakes in rustls; the raw no-TLS combination stays legal (plain-http:// deployments need it) but becomes an opt-in, not a pitfall.

    5. Housekeeping (non-breaking, can ship now)
    • README feature table documents ~13 of 26 flags; document all public ones (missing: request-state, auth-client-credentials-jwt, which-command, tower, local, unix-socket transport).
    • Delete src/transport/ws.rs and the commented transport-ws remnants (recoverable from git if WebSocket ever lands).
    • Document the TLS pitfall until the meta-features land.

    Use cases after this proposal

    Use caseFeatures after
    Stdio serverserver-stdio (or defaults + transport-stdio)
    Stdio clientclient-stdio, default-features = false
    Streamable HTTP serverserver-http
    Streamable HTTP clientclient-http
    HTTP client, native TLSclient, transport-streamable-http-client-reqwest, tls-native
    HTTP client, custom stackclient, transport-streamable-http-client
    OAuth clientclient-http, auth
    Server using elicitationnothing extra — always available
    Sealed request state+ request-state
    WASM / single-threadedunsync (or automatic via target cfg)

    Rollout

    Ship now (minor, non-breaking):

    • Use-case meta-features (§4)
    • README coverage + TLS-pitfall docs (§5)
    • Delete ws.rs / transport-ws remnants (§5)

    v3.0 (breaking):

    • Renames with forwarding aliases (§1)
    • Un-gate elicitation / base64, hide uuid (§2)
    • __-prefix internal flags (§3)

    v4.0:

    • Drop the forwarding aliases

    Open questions

    1. local → renamed feature vs. cfg(target_family = "wasm")? Does anyone use local on native targets (e.g. LocalSet-based single-threaded servers)? If yes, a renamed feature stays; if wasm-only, target cfg is strictly better — it removes the additivity violation entirely.
    2. Should defaults shrink? Shrinking away from server/macros is a bigger break for the majority (server authors). This proposal leaves defaults unchanged.
    3. tls-* vs keeping reqwest-tls-*? If a second HTTP backend ever lands, tls-* generalizes better; reqwest-tls-* is more honest today. Proposal prefers tls-*.

    Metadata

    Metadata

    Assignees

    Labels

    P2Medium: important but non-blocking improvementT-configConfiguration file changesT-documentationDocumentation improvementsT-enhancementNew features and enhancements

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rationalize feature flags for v3.0 · Issue #1067 · modelcontextprotocol/rust-sdk · GitHub
      Skip to content

      Rationalize feature flags for v3.0 #1067

      Description

      @DaleSeo

      Labels:T-enhancement, T-config, T-documentation

      Problem

      rmcp exposes 26 feature flags (including implicit ones). They have grown organically — roughly one flag per new optional dependency — rather than from a user-facing design. Concretely:

      • Names mix four unrelated concerns. Role (client, server), protocol capability (elicitation, request-state, auth), transport (transport-io, transport-streamable-http-*), and raw dependency crate names (base64, uuid, schemars, tower, reqwest) all sit in one flat list with no way to tell them apart.
      • Internal building blocks look like public flags.client-side-sse, server-side-http, transport-streamable-http-server-session, base64, uuid all appear on docs.rs but should never be enabled directly. Meanwhile __reqwestis correctly hidden — so the convention exists but is applied to exactly one flag.
      • Common use cases need 3+ flags, and one of them is a silent trap. A Streamable HTTP client needs client + transport-streamable-http-client-reqwest + reqwest. Omit the last one and it still compiles — reqwest is pulled without any TLS backend, so every https:// request fails at runtime. Our own conformance/Cargo.toml does exactly this; it survives only because it targets http://localhost.
      • Protocol capabilities are gated inconsistently.elicitation is opt-in (only to gate the url crate), while sampling, tasks (SEP-2663), and response caching (SEP-2549) are always on. There is no principle a user can infer.
      • local violates Cargo's additivity contract. It removes Send bounds crate-wide for WASM/single-threaded use. Because Cargo features are additive, any crate anywhere in the dependency graph enabling local silently changes trait bounds for everyone. The name gives no hint of this, and it is undocumented.

      Use cases today

      What a user must put in Cargo.toml right now, per scenario:

      Use caseRequired features todayPain
      Stdio server (most common server)default (server, macros, base64) + transport-iotransport-io doesn't say "stdio"; spec's term is stdio transport
      Stdio client (spawns a server)client, transport-child-process, default-features = falsedefault drags in server + schemars + pastey the client never uses
      Streamable HTTP serverserver, macros, transport-streamable-http-serverOK; tower integration arrives via an internal flag
      Streamable HTTP clientclient, transport-streamable-http-client-reqwest, reqwestTrap: omit reqwest → compiles, but all TLS fails at runtime
      HTTP client, non-default TLSabove but reqwest-native-tls / reqwest-tls-no-providerreqwest actually means "reqwest + rustls"; segment order inconsistent (reqwest-native-tls vs reqwest-tls-no-provider)
      HTTP client, custom HTTP stackclient, transport-streamable-http-clientfine
      HTTP client over Unix socketclient, transport-streamable-http-client-unix-socketsix-segment name; undocumented in README
      OAuth client+ auth (+ auth-client-credentials-jwt)auth pulls reqwest via __reqwest — same silent no-TLS trap
      Server using elicitation+ elicitationthe only protocol capability behind a flag; exists only to gate url
      Sealed request state (SEP-2322)+ request-statefine (real crypto deps), but undocumented in README
      WASM / single-threadedlocal (+ transport-worker)non-additive; opaque name; undocumented

      Dead remnant: transport-ws exists only as a comment in Cargo.toml, yet src/transport/ws.rs and a commented #[cfg] in transport.rs remain.

      Proposal

      A naming taxonomy — one rule per axis

      AxisConventionExamples
      Rolebare wordclient, server
      Protocol capabilityspec terminology, kebab-caserequest-state, auth
      Transporttransport- prefix, spec terminologytransport-stdio, transport-streamable-http-server
      Ecosystem integrationcrate name (established Rust convention)tower
      TLS backendtls- prefixtls-rustls, tls-native, tls-no-provider
      Use-case bundle<role>-<transport>client-http, server-stdio
      Internal building block__ prefix, hidden from docs__server-side-http
      1. Renames (v3.0, with one-release aliases)
      TodayProposedRationale
      transport-iotransport-stdiomatch spec vocabulary; users search "stdio"
      reqwesttls-rustlsthe flag's real meaning is the TLS choice, not the HTTP crate
      reqwest-native-tlstls-nativeconsistent tls- prefix + order
      reqwest-tls-no-providertls-no-providersame
      localunsync (or replace with cfg(target_family = "wasm"))name should say what it does; ideally not a feature at all, since it is non-additive
      which-commandfold into transport-child-process (or command-path-lookup)named after the which crate, not the behavior

      Old names stay as empty forwarding aliases (transport-io = ["transport-stdio"]) for one major cycle, then are removed.

      2. Un-gate capabilities that only exist to hide a trivial dep (v3.0)
      TodayProposedRationale
      elicitationalways ongates a core capability while nothing else (sampling/tasks/caching) is gated. Correction:url is not a trivial dep — making it mandatory adds 29 crates to a minimal build via idna → ICU4X (37 → 66). Un-gating therefore also requires the elicitation API to take URLs as plain strings, so url stays behind auth. New rule: gate a capability only when it carries a heavy dep — request-state (hmac/sha2) and auth (oauth2) stay opt-in
      base64always on (already in defaults)implicit dep-named flag gating three image-content helpers
      uuidhidden via dep:uuidimplicit dep-named flag; pure implementation detail
      3. Hide internal building blocks (v3.0)

      client-side-sse, server-side-http, transport-streamable-http-server-session get the __ prefix (matching __reqwest) and leave the docs.rs feature list. Users compose transports; they don't assemble the parts.

      transport-worker was originally listed here too, but it stays public: crates/rmcp/README.md and the transport module docs both present implementing Worker as one of the ways to build a transport, so hiding the flag would make a documented extension point unreachable.

      4. Use-case meta-features (non-breaking, can ship now)
      server-stdio = ["server", "macros", "transport-stdio"]
      server-http = ["server", "macros", "transport-streamable-http-server"]
      client-stdio = ["client", "transport-child-process"]
      client-http = ["client", "transport-streamable-http-client-reqwest", "tls-rustls"]

      Getting-started docs become one flag per scenario. client-http bakes in rustls; the raw no-TLS combination stays legal (plain-http:// deployments need it) but becomes an opt-in, not a pitfall.

      5. Housekeeping (non-breaking, can ship now)
      • README feature table documents ~13 of 26 flags; document all public ones (missing: request-state, auth-client-credentials-jwt, which-command, tower, local, unix-socket transport).
      • Delete src/transport/ws.rs and the commented transport-ws remnants (recoverable from git if WebSocket ever lands).
      • Document the TLS pitfall until the meta-features land.

      Use cases after this proposal

      Use caseFeatures after
      Stdio serverserver-stdio (or defaults + transport-stdio)
      Stdio clientclient-stdio, default-features = false
      Streamable HTTP serverserver-http
      Streamable HTTP clientclient-http
      HTTP client, native TLSclient, transport-streamable-http-client-reqwest, tls-native
      HTTP client, custom stackclient, transport-streamable-http-client
      OAuth clientclient-http, auth
      Server using elicitationnothing extra — always available
      Sealed request state+ request-state
      WASM / single-threadedunsync (or automatic via target cfg)

      Rollout

      Ship now (minor, non-breaking):

      • Use-case meta-features (§4)
      • README coverage + TLS-pitfall docs (§5)
      • Delete ws.rs / transport-ws remnants (§5)

      v3.0 (breaking):

      • Renames with forwarding aliases (§1)
      • Un-gate elicitation / base64, hide uuid (§2)
      • __-prefix internal flags (§3)

      v4.0:

      • Drop the forwarding aliases

      Open questions

      1. local → renamed feature vs. cfg(target_family = "wasm")? Does anyone use local on native targets (e.g. LocalSet-based single-threaded servers)? If yes, a renamed feature stays; if wasm-only, target cfg is strictly better — it removes the additivity violation entirely.
      2. Should defaults shrink? Shrinking away from server/macros is a bigger break for the majority (server authors). This proposal leaves defaults unchanged.
      3. tls-* vs keeping reqwest-tls-*? If a second HTTP backend ever lands, tls-* generalizes better; reqwest-tls-* is more honest today. Proposal prefers tls-*.

      Metadata

      Metadata

      Assignees

      Labels

      P2Medium: important but non-blocking improvementT-configConfiguration file changesT-documentationDocumentation improvementsT-enhancementNew features and enhancements

      Type

      No type

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rationalize feature flags for v3.0 · Issue #1067 · modelcontextprotocol/rust-sdk · GitHub
        Skip to content

        Rationalize feature flags for v3.0 #1067

        Description

        @DaleSeo

        Labels:T-enhancement, T-config, T-documentation

        Problem

        rmcp exposes 26 feature flags (including implicit ones). They have grown organically — roughly one flag per new optional dependency — rather than from a user-facing design. Concretely:

        • Names mix four unrelated concerns. Role (client, server), protocol capability (elicitation, request-state, auth), transport (transport-io, transport-streamable-http-*), and raw dependency crate names (base64, uuid, schemars, tower, reqwest) all sit in one flat list with no way to tell them apart.
        • Internal building blocks look like public flags.client-side-sse, server-side-http, transport-streamable-http-server-session, base64, uuid all appear on docs.rs but should never be enabled directly. Meanwhile __reqwestis correctly hidden — so the convention exists but is applied to exactly one flag.
        • Common use cases need 3+ flags, and one of them is a silent trap. A Streamable HTTP client needs client + transport-streamable-http-client-reqwest + reqwest. Omit the last one and it still compiles — reqwest is pulled without any TLS backend, so every https:// request fails at runtime. Our own conformance/Cargo.toml does exactly this; it survives only because it targets http://localhost.
        • Protocol capabilities are gated inconsistently.elicitation is opt-in (only to gate the url crate), while sampling, tasks (SEP-2663), and response caching (SEP-2549) are always on. There is no principle a user can infer.
        • local violates Cargo's additivity contract. It removes Send bounds crate-wide for WASM/single-threaded use. Because Cargo features are additive, any crate anywhere in the dependency graph enabling local silently changes trait bounds for everyone. The name gives no hint of this, and it is undocumented.

        Use cases today

        What a user must put in Cargo.toml right now, per scenario:

        Use caseRequired features todayPain
        Stdio server (most common server)default (server, macros, base64) + transport-iotransport-io doesn't say "stdio"; spec's term is stdio transport
        Stdio client (spawns a server)client, transport-child-process, default-features = falsedefault drags in server + schemars + pastey the client never uses
        Streamable HTTP serverserver, macros, transport-streamable-http-serverOK; tower integration arrives via an internal flag
        Streamable HTTP clientclient, transport-streamable-http-client-reqwest, reqwestTrap: omit reqwest → compiles, but all TLS fails at runtime
        HTTP client, non-default TLSabove but reqwest-native-tls / reqwest-tls-no-providerreqwest actually means "reqwest + rustls"; segment order inconsistent (reqwest-native-tls vs reqwest-tls-no-provider)
        HTTP client, custom HTTP stackclient, transport-streamable-http-clientfine
        HTTP client over Unix socketclient, transport-streamable-http-client-unix-socketsix-segment name; undocumented in README
        OAuth client+ auth (+ auth-client-credentials-jwt)auth pulls reqwest via __reqwest — same silent no-TLS trap
        Server using elicitation+ elicitationthe only protocol capability behind a flag; exists only to gate url
        Sealed request state (SEP-2322)+ request-statefine (real crypto deps), but undocumented in README
        WASM / single-threadedlocal (+ transport-worker)non-additive; opaque name; undocumented

        Dead remnant: transport-ws exists only as a comment in Cargo.toml, yet src/transport/ws.rs and a commented #[cfg] in transport.rs remain.

        Proposal

        A naming taxonomy — one rule per axis

        AxisConventionExamples
        Rolebare wordclient, server
        Protocol capabilityspec terminology, kebab-caserequest-state, auth
        Transporttransport- prefix, spec terminologytransport-stdio, transport-streamable-http-server
        Ecosystem integrationcrate name (established Rust convention)tower
        TLS backendtls- prefixtls-rustls, tls-native, tls-no-provider
        Use-case bundle<role>-<transport>client-http, server-stdio
        Internal building block__ prefix, hidden from docs__server-side-http
        1. Renames (v3.0, with one-release aliases)
        TodayProposedRationale
        transport-iotransport-stdiomatch spec vocabulary; users search "stdio"
        reqwesttls-rustlsthe flag's real meaning is the TLS choice, not the HTTP crate
        reqwest-native-tlstls-nativeconsistent tls- prefix + order
        reqwest-tls-no-providertls-no-providersame
        localunsync (or replace with cfg(target_family = "wasm"))name should say what it does; ideally not a feature at all, since it is non-additive
        which-commandfold into transport-child-process (or command-path-lookup)named after the which crate, not the behavior

        Old names stay as empty forwarding aliases (transport-io = ["transport-stdio"]) for one major cycle, then are removed.

        2. Un-gate capabilities that only exist to hide a trivial dep (v3.0)
        TodayProposedRationale
        elicitationalways ongates a core capability while nothing else (sampling/tasks/caching) is gated. Correction:url is not a trivial dep — making it mandatory adds 29 crates to a minimal build via idna → ICU4X (37 → 66). Un-gating therefore also requires the elicitation API to take URLs as plain strings, so url stays behind auth. New rule: gate a capability only when it carries a heavy dep — request-state (hmac/sha2) and auth (oauth2) stay opt-in
        base64always on (already in defaults)implicit dep-named flag gating three image-content helpers
        uuidhidden via dep:uuidimplicit dep-named flag; pure implementation detail
        3. Hide internal building blocks (v3.0)

        client-side-sse, server-side-http, transport-streamable-http-server-session get the __ prefix (matching __reqwest) and leave the docs.rs feature list. Users compose transports; they don't assemble the parts.

        transport-worker was originally listed here too, but it stays public: crates/rmcp/README.md and the transport module docs both present implementing Worker as one of the ways to build a transport, so hiding the flag would make a documented extension point unreachable.

        4. Use-case meta-features (non-breaking, can ship now)
        server-stdio = ["server", "macros", "transport-stdio"]
        server-http = ["server", "macros", "transport-streamable-http-server"]
        client-stdio = ["client", "transport-child-process"]
        client-http = ["client", "transport-streamable-http-client-reqwest", "tls-rustls"]

        Getting-started docs become one flag per scenario. client-http bakes in rustls; the raw no-TLS combination stays legal (plain-http:// deployments need it) but becomes an opt-in, not a pitfall.

        5. Housekeeping (non-breaking, can ship now)
        • README feature table documents ~13 of 26 flags; document all public ones (missing: request-state, auth-client-credentials-jwt, which-command, tower, local, unix-socket transport).
        • Delete src/transport/ws.rs and the commented transport-ws remnants (recoverable from git if WebSocket ever lands).
        • Document the TLS pitfall until the meta-features land.

        Use cases after this proposal

        Use caseFeatures after
        Stdio serverserver-stdio (or defaults + transport-stdio)
        Stdio clientclient-stdio, default-features = false
        Streamable HTTP serverserver-http
        Streamable HTTP clientclient-http
        HTTP client, native TLSclient, transport-streamable-http-client-reqwest, tls-native
        HTTP client, custom stackclient, transport-streamable-http-client
        OAuth clientclient-http, auth
        Server using elicitationnothing extra — always available
        Sealed request state+ request-state
        WASM / single-threadedunsync (or automatic via target cfg)

        Rollout

        Ship now (minor, non-breaking):

        • Use-case meta-features (§4)
        • README coverage + TLS-pitfall docs (§5)
        • Delete ws.rs / transport-ws remnants (§5)

        v3.0 (breaking):

        • Renames with forwarding aliases (§1)
        • Un-gate elicitation / base64, hide uuid (§2)
        • __-prefix internal flags (§3)

        v4.0:

        • Drop the forwarding aliases

        Open questions

        1. local → renamed feature vs. cfg(target_family = "wasm")? Does anyone use local on native targets (e.g. LocalSet-based single-threaded servers)? If yes, a renamed feature stays; if wasm-only, target cfg is strictly better — it removes the additivity violation entirely.
        2. Should defaults shrink? Shrinking away from server/macros is a bigger break for the majority (server authors). This proposal leaves defaults unchanged.
        3. tls-* vs keeping reqwest-tls-*? If a second HTTP backend ever lands, tls-* generalizes better; reqwest-tls-* is more honest today. Proposal prefers tls-*.

        Metadata

        Metadata

        Assignees

        Labels

        P2Medium: important but non-blocking improvementT-configConfiguration file changesT-documentationDocumentation improvementsT-enhancementNew features and enhancements

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Rationalize feature flags for v3.0 · Issue #1067 · modelcontextprotocol/rust-sdk · GitHub
          Skip to content

          Rationalize feature flags for v3.0 #1067

          Description

          @DaleSeo

          Labels:T-enhancement, T-config, T-documentation

          Problem

          rmcp exposes 26 feature flags (including implicit ones). They have grown organically — roughly one flag per new optional dependency — rather than from a user-facing design. Concretely:

          • Names mix four unrelated concerns. Role (client, server), protocol capability (elicitation, request-state, auth), transport (transport-io, transport-streamable-http-*), and raw dependency crate names (base64, uuid, schemars, tower, reqwest) all sit in one flat list with no way to tell them apart.
          • Internal building blocks look like public flags.client-side-sse, server-side-http, transport-streamable-http-server-session, base64, uuid all appear on docs.rs but should never be enabled directly. Meanwhile __reqwestis correctly hidden — so the convention exists but is applied to exactly one flag.
          • Common use cases need 3+ flags, and one of them is a silent trap. A Streamable HTTP client needs client + transport-streamable-http-client-reqwest + reqwest. Omit the last one and it still compiles — reqwest is pulled without any TLS backend, so every https:// request fails at runtime. Our own conformance/Cargo.toml does exactly this; it survives only because it targets http://localhost.
          • Protocol capabilities are gated inconsistently.elicitation is opt-in (only to gate the url crate), while sampling, tasks (SEP-2663), and response caching (SEP-2549) are always on. There is no principle a user can infer.
          • local violates Cargo's additivity contract. It removes Send bounds crate-wide for WASM/single-threaded use. Because Cargo features are additive, any crate anywhere in the dependency graph enabling local silently changes trait bounds for everyone. The name gives no hint of this, and it is undocumented.

          Use cases today

          What a user must put in Cargo.toml right now, per scenario:

          Use caseRequired features todayPain
          Stdio server (most common server)default (server, macros, base64) + transport-iotransport-io doesn't say "stdio"; spec's term is stdio transport
          Stdio client (spawns a server)client, transport-child-process, default-features = falsedefault drags in server + schemars + pastey the client never uses
          Streamable HTTP serverserver, macros, transport-streamable-http-serverOK; tower integration arrives via an internal flag
          Streamable HTTP clientclient, transport-streamable-http-client-reqwest, reqwestTrap: omit reqwest → compiles, but all TLS fails at runtime
          HTTP client, non-default TLSabove but reqwest-native-tls / reqwest-tls-no-providerreqwest actually means "reqwest + rustls"; segment order inconsistent (reqwest-native-tls vs reqwest-tls-no-provider)
          HTTP client, custom HTTP stackclient, transport-streamable-http-clientfine
          HTTP client over Unix socketclient, transport-streamable-http-client-unix-socketsix-segment name; undocumented in README
          OAuth client+ auth (+ auth-client-credentials-jwt)auth pulls reqwest via __reqwest — same silent no-TLS trap
          Server using elicitation+ elicitationthe only protocol capability behind a flag; exists only to gate url
          Sealed request state (SEP-2322)+ request-statefine (real crypto deps), but undocumented in README
          WASM / single-threadedlocal (+ transport-worker)non-additive; opaque name; undocumented

          Dead remnant: transport-ws exists only as a comment in Cargo.toml, yet src/transport/ws.rs and a commented #[cfg] in transport.rs remain.

          Proposal

          A naming taxonomy — one rule per axis

          AxisConventionExamples
          Rolebare wordclient, server
          Protocol capabilityspec terminology, kebab-caserequest-state, auth
          Transporttransport- prefix, spec terminologytransport-stdio, transport-streamable-http-server
          Ecosystem integrationcrate name (established Rust convention)tower
          TLS backendtls- prefixtls-rustls, tls-native, tls-no-provider
          Use-case bundle<role>-<transport>client-http, server-stdio
          Internal building block__ prefix, hidden from docs__server-side-http
          1. Renames (v3.0, with one-release aliases)
          TodayProposedRationale
          transport-iotransport-stdiomatch spec vocabulary; users search "stdio"
          reqwesttls-rustlsthe flag's real meaning is the TLS choice, not the HTTP crate
          reqwest-native-tlstls-nativeconsistent tls- prefix + order
          reqwest-tls-no-providertls-no-providersame
          localunsync (or replace with cfg(target_family = "wasm"))name should say what it does; ideally not a feature at all, since it is non-additive
          which-commandfold into transport-child-process (or command-path-lookup)named after the which crate, not the behavior

          Old names stay as empty forwarding aliases (transport-io = ["transport-stdio"]) for one major cycle, then are removed.

          2. Un-gate capabilities that only exist to hide a trivial dep (v3.0)
          TodayProposedRationale
          elicitationalways ongates a core capability while nothing else (sampling/tasks/caching) is gated. Correction:url is not a trivial dep — making it mandatory adds 29 crates to a minimal build via idna → ICU4X (37 → 66). Un-gating therefore also requires the elicitation API to take URLs as plain strings, so url stays behind auth. New rule: gate a capability only when it carries a heavy dep — request-state (hmac/sha2) and auth (oauth2) stay opt-in
          base64always on (already in defaults)implicit dep-named flag gating three image-content helpers
          uuidhidden via dep:uuidimplicit dep-named flag; pure implementation detail
          3. Hide internal building blocks (v3.0)

          client-side-sse, server-side-http, transport-streamable-http-server-session get the __ prefix (matching __reqwest) and leave the docs.rs feature list. Users compose transports; they don't assemble the parts.

          transport-worker was originally listed here too, but it stays public: crates/rmcp/README.md and the transport module docs both present implementing Worker as one of the ways to build a transport, so hiding the flag would make a documented extension point unreachable.

          4. Use-case meta-features (non-breaking, can ship now)
          server-stdio = ["server", "macros", "transport-stdio"]
          server-http = ["server", "macros", "transport-streamable-http-server"]
          client-stdio = ["client", "transport-child-process"]
          client-http = ["client", "transport-streamable-http-client-reqwest", "tls-rustls"]

          Getting-started docs become one flag per scenario. client-http bakes in rustls; the raw no-TLS combination stays legal (plain-http:// deployments need it) but becomes an opt-in, not a pitfall.

          5. Housekeeping (non-breaking, can ship now)
          • README feature table documents ~13 of 26 flags; document all public ones (missing: request-state, auth-client-credentials-jwt, which-command, tower, local, unix-socket transport).
          • Delete src/transport/ws.rs and the commented transport-ws remnants (recoverable from git if WebSocket ever lands).
          • Document the TLS pitfall until the meta-features land.

          Use cases after this proposal

          Use caseFeatures after
          Stdio serverserver-stdio (or defaults + transport-stdio)
          Stdio clientclient-stdio, default-features = false
          Streamable HTTP serverserver-http
          Streamable HTTP clientclient-http
          HTTP client, native TLSclient, transport-streamable-http-client-reqwest, tls-native
          HTTP client, custom stackclient, transport-streamable-http-client
          OAuth clientclient-http, auth
          Server using elicitationnothing extra — always available
          Sealed request state+ request-state
          WASM / single-threadedunsync (or automatic via target cfg)

          Rollout

          Ship now (minor, non-breaking):

          • Use-case meta-features (§4)
          • README coverage + TLS-pitfall docs (§5)
          • Delete ws.rs / transport-ws remnants (§5)

          v3.0 (breaking):

          • Renames with forwarding aliases (§1)
          • Un-gate elicitation / base64, hide uuid (§2)
          • __-prefix internal flags (§3)

          v4.0:

          • Drop the forwarding aliases

          Open questions

          1. local → renamed feature vs. cfg(target_family = "wasm")? Does anyone use local on native targets (e.g. LocalSet-based single-threaded servers)? If yes, a renamed feature stays; if wasm-only, target cfg is strictly better — it removes the additivity violation entirely.
          2. Should defaults shrink? Shrinking away from server/macros is a bigger break for the majority (server authors). This proposal leaves defaults unchanged.
          3. tls-* vs keeping reqwest-tls-*? If a second HTTP backend ever lands, tls-* generalizes better; reqwest-tls-* is more honest today. Proposal prefers tls-*.

          Metadata

          Metadata

          Assignees

          Labels

          P2Medium: important but non-blocking improvementT-configConfiguration file changesT-documentationDocumentation improvementsT-enhancementNew features and enhancements

          Type

          No type

          Projects

          No projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rationalize feature flags for v3.0 · Issue #1067 · modelcontextprotocol/rust-sdk · GitHub
            Skip to content

            Rationalize feature flags for v3.0 #1067

            Description

            @DaleSeo

            Labels:T-enhancement, T-config, T-documentation

            Problem

            rmcp exposes 26 feature flags (including implicit ones). They have grown organically — roughly one flag per new optional dependency — rather than from a user-facing design. Concretely:

            • Names mix four unrelated concerns. Role (client, server), protocol capability (elicitation, request-state, auth), transport (transport-io, transport-streamable-http-*), and raw dependency crate names (base64, uuid, schemars, tower, reqwest) all sit in one flat list with no way to tell them apart.
            • Internal building blocks look like public flags.client-side-sse, server-side-http, transport-streamable-http-server-session, base64, uuid all appear on docs.rs but should never be enabled directly. Meanwhile __reqwestis correctly hidden — so the convention exists but is applied to exactly one flag.
            • Common use cases need 3+ flags, and one of them is a silent trap. A Streamable HTTP client needs client + transport-streamable-http-client-reqwest + reqwest. Omit the last one and it still compiles — reqwest is pulled without any TLS backend, so every https:// request fails at runtime. Our own conformance/Cargo.toml does exactly this; it survives only because it targets http://localhost.
            • Protocol capabilities are gated inconsistently.elicitation is opt-in (only to gate the url crate), while sampling, tasks (SEP-2663), and response caching (SEP-2549) are always on. There is no principle a user can infer.
            • local violates Cargo's additivity contract. It removes Send bounds crate-wide for WASM/single-threaded use. Because Cargo features are additive, any crate anywhere in the dependency graph enabling local silently changes trait bounds for everyone. The name gives no hint of this, and it is undocumented.

            Use cases today

            What a user must put in Cargo.toml right now, per scenario:

            Use caseRequired features todayPain
            Stdio server (most common server)default (server, macros, base64) + transport-iotransport-io doesn't say "stdio"; spec's term is stdio transport
            Stdio client (spawns a server)client, transport-child-process, default-features = falsedefault drags in server + schemars + pastey the client never uses
            Streamable HTTP serverserver, macros, transport-streamable-http-serverOK; tower integration arrives via an internal flag
            Streamable HTTP clientclient, transport-streamable-http-client-reqwest, reqwestTrap: omit reqwest → compiles, but all TLS fails at runtime
            HTTP client, non-default TLSabove but reqwest-native-tls / reqwest-tls-no-providerreqwest actually means "reqwest + rustls"; segment order inconsistent (reqwest-native-tls vs reqwest-tls-no-provider)
            HTTP client, custom HTTP stackclient, transport-streamable-http-clientfine
            HTTP client over Unix socketclient, transport-streamable-http-client-unix-socketsix-segment name; undocumented in README
            OAuth client+ auth (+ auth-client-credentials-jwt)auth pulls reqwest via __reqwest — same silent no-TLS trap
            Server using elicitation+ elicitationthe only protocol capability behind a flag; exists only to gate url
            Sealed request state (SEP-2322)+ request-statefine (real crypto deps), but undocumented in README
            WASM / single-threadedlocal (+ transport-worker)non-additive; opaque name; undocumented

            Dead remnant: transport-ws exists only as a comment in Cargo.toml, yet src/transport/ws.rs and a commented #[cfg] in transport.rs remain.

            Proposal

            A naming taxonomy — one rule per axis

            AxisConventionExamples
            Rolebare wordclient, server
            Protocol capabilityspec terminology, kebab-caserequest-state, auth
            Transporttransport- prefix, spec terminologytransport-stdio, transport-streamable-http-server
            Ecosystem integrationcrate name (established Rust convention)tower
            TLS backendtls- prefixtls-rustls, tls-native, tls-no-provider
            Use-case bundle<role>-<transport>client-http, server-stdio
            Internal building block__ prefix, hidden from docs__server-side-http
            1. Renames (v3.0, with one-release aliases)
            TodayProposedRationale
            transport-iotransport-stdiomatch spec vocabulary; users search "stdio"
            reqwesttls-rustlsthe flag's real meaning is the TLS choice, not the HTTP crate
            reqwest-native-tlstls-nativeconsistent tls- prefix + order
            reqwest-tls-no-providertls-no-providersame
            localunsync (or replace with cfg(target_family = "wasm"))name should say what it does; ideally not a feature at all, since it is non-additive
            which-commandfold into transport-child-process (or command-path-lookup)named after the which crate, not the behavior

            Old names stay as empty forwarding aliases (transport-io = ["transport-stdio"]) for one major cycle, then are removed.

            2. Un-gate capabilities that only exist to hide a trivial dep (v3.0)
            TodayProposedRationale
            elicitationalways ongates a core capability while nothing else (sampling/tasks/caching) is gated. Correction:url is not a trivial dep — making it mandatory adds 29 crates to a minimal build via idna → ICU4X (37 → 66). Un-gating therefore also requires the elicitation API to take URLs as plain strings, so url stays behind auth. New rule: gate a capability only when it carries a heavy dep — request-state (hmac/sha2) and auth (oauth2) stay opt-in
            base64always on (already in defaults)implicit dep-named flag gating three image-content helpers
            uuidhidden via dep:uuidimplicit dep-named flag; pure implementation detail
            3. Hide internal building blocks (v3.0)

            client-side-sse, server-side-http, transport-streamable-http-server-session get the __ prefix (matching __reqwest) and leave the docs.rs feature list. Users compose transports; they don't assemble the parts.

            transport-worker was originally listed here too, but it stays public: crates/rmcp/README.md and the transport module docs both present implementing Worker as one of the ways to build a transport, so hiding the flag would make a documented extension point unreachable.

            4. Use-case meta-features (non-breaking, can ship now)
            server-stdio = ["server", "macros", "transport-stdio"]
            server-http = ["server", "macros", "transport-streamable-http-server"]
            client-stdio = ["client", "transport-child-process"]
            client-http = ["client", "transport-streamable-http-client-reqwest", "tls-rustls"]

            Getting-started docs become one flag per scenario. client-http bakes in rustls; the raw no-TLS combination stays legal (plain-http:// deployments need it) but becomes an opt-in, not a pitfall.

            5. Housekeeping (non-breaking, can ship now)
            • README feature table documents ~13 of 26 flags; document all public ones (missing: request-state, auth-client-credentials-jwt, which-command, tower, local, unix-socket transport).
            • Delete src/transport/ws.rs and the commented transport-ws remnants (recoverable from git if WebSocket ever lands).
            • Document the TLS pitfall until the meta-features land.

            Use cases after this proposal

            Use caseFeatures after
            Stdio serverserver-stdio (or defaults + transport-stdio)
            Stdio clientclient-stdio, default-features = false
            Streamable HTTP serverserver-http
            Streamable HTTP clientclient-http
            HTTP client, native TLSclient, transport-streamable-http-client-reqwest, tls-native
            HTTP client, custom stackclient, transport-streamable-http-client
            OAuth clientclient-http, auth
            Server using elicitationnothing extra — always available
            Sealed request state+ request-state
            WASM / single-threadedunsync (or automatic via target cfg)

            Rollout

            Ship now (minor, non-breaking):

            • Use-case meta-features (§4)
            • README coverage + TLS-pitfall docs (§5)
            • Delete ws.rs / transport-ws remnants (§5)

            v3.0 (breaking):

            • Renames with forwarding aliases (§1)
            • Un-gate elicitation / base64, hide uuid (§2)
            • __-prefix internal flags (§3)

            v4.0:

            • Drop the forwarding aliases

            Open questions

            1. local → renamed feature vs. cfg(target_family = "wasm")? Does anyone use local on native targets (e.g. LocalSet-based single-threaded servers)? If yes, a renamed feature stays; if wasm-only, target cfg is strictly better — it removes the additivity violation entirely.
            2. Should defaults shrink? Shrinking away from server/macros is a bigger break for the majority (server authors). This proposal leaves defaults unchanged.
            3. tls-* vs keeping reqwest-tls-*? If a second HTTP backend ever lands, tls-* generalizes better; reqwest-tls-* is more honest today. Proposal prefers tls-*.

            Metadata

            Metadata

            Assignees

            Labels

            P2Medium: important but non-blocking improvementT-configConfiguration file changesT-documentationDocumentation improvementsT-enhancementNew features and enhancements

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rationalize feature flags for v3.0 · Issue #1067 · modelcontextprotocol/rust-sdk · GitHub
              Skip to content

              Rationalize feature flags for v3.0 #1067

              Description

              @DaleSeo

              Labels:T-enhancement, T-config, T-documentation

              Problem

              rmcp exposes 26 feature flags (including implicit ones). They have grown organically — roughly one flag per new optional dependency — rather than from a user-facing design. Concretely:

              • Names mix four unrelated concerns. Role (client, server), protocol capability (elicitation, request-state, auth), transport (transport-io, transport-streamable-http-*), and raw dependency crate names (base64, uuid, schemars, tower, reqwest) all sit in one flat list with no way to tell them apart.
              • Internal building blocks look like public flags.client-side-sse, server-side-http, transport-streamable-http-server-session, base64, uuid all appear on docs.rs but should never be enabled directly. Meanwhile __reqwestis correctly hidden — so the convention exists but is applied to exactly one flag.
              • Common use cases need 3+ flags, and one of them is a silent trap. A Streamable HTTP client needs client + transport-streamable-http-client-reqwest + reqwest. Omit the last one and it still compiles — reqwest is pulled without any TLS backend, so every https:// request fails at runtime. Our own conformance/Cargo.toml does exactly this; it survives only because it targets http://localhost.
              • Protocol capabilities are gated inconsistently.elicitation is opt-in (only to gate the url crate), while sampling, tasks (SEP-2663), and response caching (SEP-2549) are always on. There is no principle a user can infer.
              • local violates Cargo's additivity contract. It removes Send bounds crate-wide for WASM/single-threaded use. Because Cargo features are additive, any crate anywhere in the dependency graph enabling local silently changes trait bounds for everyone. The name gives no hint of this, and it is undocumented.

              Use cases today

              What a user must put in Cargo.toml right now, per scenario:

              Use caseRequired features todayPain
              Stdio server (most common server)default (server, macros, base64) + transport-iotransport-io doesn't say "stdio"; spec's term is stdio transport
              Stdio client (spawns a server)client, transport-child-process, default-features = falsedefault drags in server + schemars + pastey the client never uses
              Streamable HTTP serverserver, macros, transport-streamable-http-serverOK; tower integration arrives via an internal flag
              Streamable HTTP clientclient, transport-streamable-http-client-reqwest, reqwestTrap: omit reqwest → compiles, but all TLS fails at runtime
              HTTP client, non-default TLSabove but reqwest-native-tls / reqwest-tls-no-providerreqwest actually means "reqwest + rustls"; segment order inconsistent (reqwest-native-tls vs reqwest-tls-no-provider)
              HTTP client, custom HTTP stackclient, transport-streamable-http-clientfine
              HTTP client over Unix socketclient, transport-streamable-http-client-unix-socketsix-segment name; undocumented in README
              OAuth client+ auth (+ auth-client-credentials-jwt)auth pulls reqwest via __reqwest — same silent no-TLS trap
              Server using elicitation+ elicitationthe only protocol capability behind a flag; exists only to gate url
              Sealed request state (SEP-2322)+ request-statefine (real crypto deps), but undocumented in README
              WASM / single-threadedlocal (+ transport-worker)non-additive; opaque name; undocumented

              Dead remnant: transport-ws exists only as a comment in Cargo.toml, yet src/transport/ws.rs and a commented #[cfg] in transport.rs remain.

              Proposal

              A naming taxonomy — one rule per axis

              AxisConventionExamples
              Rolebare wordclient, server
              Protocol capabilityspec terminology, kebab-caserequest-state, auth
              Transporttransport- prefix, spec terminologytransport-stdio, transport-streamable-http-server
              Ecosystem integrationcrate name (established Rust convention)tower
              TLS backendtls- prefixtls-rustls, tls-native, tls-no-provider
              Use-case bundle<role>-<transport>client-http, server-stdio
              Internal building block__ prefix, hidden from docs__server-side-http
              1. Renames (v3.0, with one-release aliases)
              TodayProposedRationale
              transport-iotransport-stdiomatch spec vocabulary; users search "stdio"
              reqwesttls-rustlsthe flag's real meaning is the TLS choice, not the HTTP crate
              reqwest-native-tlstls-nativeconsistent tls- prefix + order
              reqwest-tls-no-providertls-no-providersame
              localunsync (or replace with cfg(target_family = "wasm"))name should say what it does; ideally not a feature at all, since it is non-additive
              which-commandfold into transport-child-process (or command-path-lookup)named after the which crate, not the behavior

              Old names stay as empty forwarding aliases (transport-io = ["transport-stdio"]) for one major cycle, then are removed.

              2. Un-gate capabilities that only exist to hide a trivial dep (v3.0)
              TodayProposedRationale
              elicitationalways ongates a core capability while nothing else (sampling/tasks/caching) is gated. Correction:url is not a trivial dep — making it mandatory adds 29 crates to a minimal build via idna → ICU4X (37 → 66). Un-gating therefore also requires the elicitation API to take URLs as plain strings, so url stays behind auth. New rule: gate a capability only when it carries a heavy dep — request-state (hmac/sha2) and auth (oauth2) stay opt-in
              base64always on (already in defaults)implicit dep-named flag gating three image-content helpers
              uuidhidden via dep:uuidimplicit dep-named flag; pure implementation detail
              3. Hide internal building blocks (v3.0)

              client-side-sse, server-side-http, transport-streamable-http-server-session get the __ prefix (matching __reqwest) and leave the docs.rs feature list. Users compose transports; they don't assemble the parts.

              transport-worker was originally listed here too, but it stays public: crates/rmcp/README.md and the transport module docs both present implementing Worker as one of the ways to build a transport, so hiding the flag would make a documented extension point unreachable.

              4. Use-case meta-features (non-breaking, can ship now)
              server-stdio = ["server", "macros", "transport-stdio"]
              server-http = ["server", "macros", "transport-streamable-http-server"]
              client-stdio = ["client", "transport-child-process"]
              client-http = ["client", "transport-streamable-http-client-reqwest", "tls-rustls"]

              Getting-started docs become one flag per scenario. client-http bakes in rustls; the raw no-TLS combination stays legal (plain-http:// deployments need it) but becomes an opt-in, not a pitfall.

              5. Housekeeping (non-breaking, can ship now)
              • README feature table documents ~13 of 26 flags; document all public ones (missing: request-state, auth-client-credentials-jwt, which-command, tower, local, unix-socket transport).
              • Delete src/transport/ws.rs and the commented transport-ws remnants (recoverable from git if WebSocket ever lands).
              • Document the TLS pitfall until the meta-features land.

              Use cases after this proposal

              Use caseFeatures after
              Stdio serverserver-stdio (or defaults + transport-stdio)
              Stdio clientclient-stdio, default-features = false
              Streamable HTTP serverserver-http
              Streamable HTTP clientclient-http
              HTTP client, native TLSclient, transport-streamable-http-client-reqwest, tls-native
              HTTP client, custom stackclient, transport-streamable-http-client
              OAuth clientclient-http, auth
              Server using elicitationnothing extra — always available
              Sealed request state+ request-state
              WASM / single-threadedunsync (or automatic via target cfg)

              Rollout

              Ship now (minor, non-breaking):

              • Use-case meta-features (§4)
              • README coverage + TLS-pitfall docs (§5)
              • Delete ws.rs / transport-ws remnants (§5)

              v3.0 (breaking):

              • Renames with forwarding aliases (§1)
              • Un-gate elicitation / base64, hide uuid (§2)
              • __-prefix internal flags (§3)

              v4.0:

              • Drop the forwarding aliases

              Open questions

              1. local → renamed feature vs. cfg(target_family = "wasm")? Does anyone use local on native targets (e.g. LocalSet-based single-threaded servers)? If yes, a renamed feature stays; if wasm-only, target cfg is strictly better — it removes the additivity violation entirely.
              2. Should defaults shrink? Shrinking away from server/macros is a bigger break for the majority (server authors). This proposal leaves defaults unchanged.
              3. tls-* vs keeping reqwest-tls-*? If a second HTTP backend ever lands, tls-* generalizes better; reqwest-tls-* is more honest today. Proposal prefers tls-*.

              Metadata

              Metadata

              Assignees

              Labels

              P2Medium: important but non-blocking improvementT-configConfiguration file changesT-documentationDocumentation improvementsT-enhancementNew features and enhancements

              Type

              No type

              Projects

              No projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Rationalize feature flags for v3.0 · Issue #1067 · modelcontextprotocol/rust-sdk · GitHub
                Skip to content

                Rationalize feature flags for v3.0 #1067

                Description

                @DaleSeo

                Labels:T-enhancement, T-config, T-documentation

                Problem

                rmcp exposes 26 feature flags (including implicit ones). They have grown organically — roughly one flag per new optional dependency — rather than from a user-facing design. Concretely:

                • Names mix four unrelated concerns. Role (client, server), protocol capability (elicitation, request-state, auth), transport (transport-io, transport-streamable-http-*), and raw dependency crate names (base64, uuid, schemars, tower, reqwest) all sit in one flat list with no way to tell them apart.
                • Internal building blocks look like public flags.client-side-sse, server-side-http, transport-streamable-http-server-session, base64, uuid all appear on docs.rs but should never be enabled directly. Meanwhile __reqwestis correctly hidden — so the convention exists but is applied to exactly one flag.
                • Common use cases need 3+ flags, and one of them is a silent trap. A Streamable HTTP client needs client + transport-streamable-http-client-reqwest + reqwest. Omit the last one and it still compiles — reqwest is pulled without any TLS backend, so every https:// request fails at runtime. Our own conformance/Cargo.toml does exactly this; it survives only because it targets http://localhost.
                • Protocol capabilities are gated inconsistently.elicitation is opt-in (only to gate the url crate), while sampling, tasks (SEP-2663), and response caching (SEP-2549) are always on. There is no principle a user can infer.
                • local violates Cargo's additivity contract. It removes Send bounds crate-wide for WASM/single-threaded use. Because Cargo features are additive, any crate anywhere in the dependency graph enabling local silently changes trait bounds for everyone. The name gives no hint of this, and it is undocumented.

                Use cases today

                What a user must put in Cargo.toml right now, per scenario:

                Use caseRequired features todayPain
                Stdio server (most common server)default (server, macros, base64) + transport-iotransport-io doesn't say "stdio"; spec's term is stdio transport
                Stdio client (spawns a server)client, transport-child-process, default-features = falsedefault drags in server + schemars + pastey the client never uses
                Streamable HTTP serverserver, macros, transport-streamable-http-serverOK; tower integration arrives via an internal flag
                Streamable HTTP clientclient, transport-streamable-http-client-reqwest, reqwestTrap: omit reqwest → compiles, but all TLS fails at runtime
                HTTP client, non-default TLSabove but reqwest-native-tls / reqwest-tls-no-providerreqwest actually means "reqwest + rustls"; segment order inconsistent (reqwest-native-tls vs reqwest-tls-no-provider)
                HTTP client, custom HTTP stackclient, transport-streamable-http-clientfine
                HTTP client over Unix socketclient, transport-streamable-http-client-unix-socketsix-segment name; undocumented in README
                OAuth client+ auth (+ auth-client-credentials-jwt)auth pulls reqwest via __reqwest — same silent no-TLS trap
                Server using elicitation+ elicitationthe only protocol capability behind a flag; exists only to gate url
                Sealed request state (SEP-2322)+ request-statefine (real crypto deps), but undocumented in README
                WASM / single-threadedlocal (+ transport-worker)non-additive; opaque name; undocumented

                Dead remnant: transport-ws exists only as a comment in Cargo.toml, yet src/transport/ws.rs and a commented #[cfg] in transport.rs remain.

                Proposal

                A naming taxonomy — one rule per axis

                AxisConventionExamples
                Rolebare wordclient, server
                Protocol capabilityspec terminology, kebab-caserequest-state, auth
                Transporttransport- prefix, spec terminologytransport-stdio, transport-streamable-http-server
                Ecosystem integrationcrate name (established Rust convention)tower
                TLS backendtls- prefixtls-rustls, tls-native, tls-no-provider
                Use-case bundle<role>-<transport>client-http, server-stdio
                Internal building block__ prefix, hidden from docs__server-side-http
                1. Renames (v3.0, with one-release aliases)
                TodayProposedRationale
                transport-iotransport-stdiomatch spec vocabulary; users search "stdio"
                reqwesttls-rustlsthe flag's real meaning is the TLS choice, not the HTTP crate
                reqwest-native-tlstls-nativeconsistent tls- prefix + order
                reqwest-tls-no-providertls-no-providersame
                localunsync (or replace with cfg(target_family = "wasm"))name should say what it does; ideally not a feature at all, since it is non-additive
                which-commandfold into transport-child-process (or command-path-lookup)named after the which crate, not the behavior

                Old names stay as empty forwarding aliases (transport-io = ["transport-stdio"]) for one major cycle, then are removed.

                2. Un-gate capabilities that only exist to hide a trivial dep (v3.0)
                TodayProposedRationale
                elicitationalways ongates a core capability while nothing else (sampling/tasks/caching) is gated. Correction:url is not a trivial dep — making it mandatory adds 29 crates to a minimal build via idna → ICU4X (37 → 66). Un-gating therefore also requires the elicitation API to take URLs as plain strings, so url stays behind auth. New rule: gate a capability only when it carries a heavy dep — request-state (hmac/sha2) and auth (oauth2) stay opt-in
                base64always on (already in defaults)implicit dep-named flag gating three image-content helpers
                uuidhidden via dep:uuidimplicit dep-named flag; pure implementation detail
                3. Hide internal building blocks (v3.0)

                client-side-sse, server-side-http, transport-streamable-http-server-session get the __ prefix (matching __reqwest) and leave the docs.rs feature list. Users compose transports; they don't assemble the parts.

                transport-worker was originally listed here too, but it stays public: crates/rmcp/README.md and the transport module docs both present implementing Worker as one of the ways to build a transport, so hiding the flag would make a documented extension point unreachable.

                4. Use-case meta-features (non-breaking, can ship now)
                server-stdio = ["server", "macros", "transport-stdio"]
                server-http = ["server", "macros", "transport-streamable-http-server"]
                client-stdio = ["client", "transport-child-process"]
                client-http = ["client", "transport-streamable-http-client-reqwest", "tls-rustls"]

                Getting-started docs become one flag per scenario. client-http bakes in rustls; the raw no-TLS combination stays legal (plain-http:// deployments need it) but becomes an opt-in, not a pitfall.

                5. Housekeeping (non-breaking, can ship now)
                • README feature table documents ~13 of 26 flags; document all public ones (missing: request-state, auth-client-credentials-jwt, which-command, tower, local, unix-socket transport).
                • Delete src/transport/ws.rs and the commented transport-ws remnants (recoverable from git if WebSocket ever lands).
                • Document the TLS pitfall until the meta-features land.

                Use cases after this proposal

                Use caseFeatures after
                Stdio serverserver-stdio (or defaults + transport-stdio)
                Stdio clientclient-stdio, default-features = false
                Streamable HTTP serverserver-http
                Streamable HTTP clientclient-http
                HTTP client, native TLSclient, transport-streamable-http-client-reqwest, tls-native
                HTTP client, custom stackclient, transport-streamable-http-client
                OAuth clientclient-http, auth
                Server using elicitationnothing extra — always available
                Sealed request state+ request-state
                WASM / single-threadedunsync (or automatic via target cfg)

                Rollout

                Ship now (minor, non-breaking):

                • Use-case meta-features (§4)
                • README coverage + TLS-pitfall docs (§5)
                • Delete ws.rs / transport-ws remnants (§5)

                v3.0 (breaking):

                • Renames with forwarding aliases (§1)
                • Un-gate elicitation / base64, hide uuid (§2)
                • __-prefix internal flags (§3)

                v4.0:

                • Drop the forwarding aliases

                Open questions

                1. local → renamed feature vs. cfg(target_family = "wasm")? Does anyone use local on native targets (e.g. LocalSet-based single-threaded servers)? If yes, a renamed feature stays; if wasm-only, target cfg is strictly better — it removes the additivity violation entirely.
                2. Should defaults shrink? Shrinking away from server/macros is a bigger break for the majority (server authors). This proposal leaves defaults unchanged.
                3. tls-* vs keeping reqwest-tls-*? If a second HTTP backend ever lands, tls-* generalizes better; reqwest-tls-* is more honest today. Proposal prefers tls-*.

                Metadata

                Metadata

                Assignees

                Labels

                P2Medium: important but non-blocking improvementT-configConfiguration file changesT-documentationDocumentation improvementsT-enhancementNew features and enhancements

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions