Skip to content

fix(transport): match unix-socket media types case-insensitively - #1453

Open
jokemanfire wants to merge 1 commit into
modelcontextprotocol:mainfrom
jokemanfire:fix/1443-unix-socket-media-type
Open

jokemanfire wants to merge 1 commit into
modelcontextprotocol:mainfrom
jokemanfire:fix/1443-unix-socket-media-type

Conversation

@jokemanfire

Copy link
Copy Markdown
Member

Fixes #1443

Problem

UnixSocketHttpClient chose JSON vs SSE by byte-prefix comparison:

ct.as_bytes().starts_with(JSON_MIME_TYPE.as_bytes())

Media types are case-insensitive (RFC 9110 §8.3.1) and may carry parameters, so
a response labelled Application/JSON; charset=utf-8 — legal and common behind
proxies — was rejected with UnexpectedContentType, failing the request even
though the body was a valid JSON-RPC response.

Change

  • Move media_type_is from common/reqwest/streamable_http_client.rs into
    common/http_header.rs as the one shared essence-comparison helper (it
    already ignores parameters and ASCII case).
  • Use it at all four unix-socket call sites (error path, POST response match,
    GET stream validation).
  • The reqwest client now imports the same helper instead of carrying its own
    copy, so the two transports cannot drift again.

Evidence

New transport test test_unix_socket_mixed_case_json_content_type_is_parsed
serves {"jsonrpc":"2.0","id":1,"result":{}} with
Content-Type: Application/JSON; charset=utf-8.

Before the change (source reverted, test kept):

panicked at crates/rmcp/tests/test_unix_socket_transport.rs:409:
expected parsed JSON response, got: Err(UnexpectedContentType(Some("Application/JSON; charset=utf-8")))
test result: FAILED. 0 passed; 1 failed

After:

test test_unix_socket_mixed_case_json_content_type_is_parsed ... ok
test result: ok. 8 passed; 0 failed

Unit tests for the helper cover Application/JSON, APPLICATION/JSON; charset=utf-8,
leading/trailing spaces, and non-matches (application/jsonx,
application/json-patch+json).

cargo test -p rmcp --lib --features "client transport-streamable-http-client-unix-socket" -> 367 passed
cargo test -p rmcp --test test_unix_socket_transport --features ...            -> 8 passed
cargo clippy -p rmcp --lib --features ... -- -D warnings                       -> clean
cargo fmt --check                                                              -> clean

Notes

  • No public API change: the helper is pub(crate).
  • test_unix_socket_* is cfg(unix), so the regression test is compiled out on
    Windows, matching the rest of that suite.

@jokemanfire
jokemanfire requested a review from a team as a code owner November 14, 2026 03:29
Copilot AI balanced review requested due to automatic review settings November 14, 2026 03:29
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-14T13:16:02.570640Z 257d55b New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added T-test Testing related changes T-core Core library changes T-transport Transport layer changes labels Nov 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@michaelneale is on triage rotation this week and is the reviewer for this one.

Labels come from the PR labeler. Please review the change.

Automated triage. Labels are a first pass and the assigned maintainer has the final say.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Misplaced and missing client-side-sse feature gates cause default-feature builds and library tests without that feature to fail with unresolved dependency and function errors.

2 open issues
What changed in this PR

This PR addresses #1443 by aligning Unix-socket response media-type matching with the reqwest transport, accepting case variants and parameters while rejecting prefix lookalikes.

Changes:

  • Share the media-type essence matcher through common/http_header.rs.
  • Use it for Unix-socket POST dispatch, error handling, and GET stream validation.
  • Add helper unit tests and a Unix-socket mixed-case JSON regression test.
File Description
crates/​rmcp/​tests/​test_unix_socket_transport.rs Tests parsing JSON with a mixed-case, parameterized content type.
crates/​rmcp/​src/​transport/​common/​unix_socket.rs Replaces content-type prefix checks with the shared matcher.
crates/​rmcp/​src/​transport/​common/​reqwest/​streamable_http_client.rs Imports the shared matcher instead of defining its own.
crates/​rmcp/​src/​transport/​common/​http_header.rs Houses the shared matcher and adds unit coverage.

Review effort: Balanced

/// `Content-Type` values are case-insensitive by RFC 9110 (``Text/Event-Stream``
/// is the same type as ``text/event-stream``) and may carry parameters such as
/// ``; charset=utf-8``. A byte-prefix check gets both wrong.
pub(crate) fn media_type_is(value: &[u8], expected: &str) -> bool {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and that is what broke four CI gates (Lint with Clippy, Example test, SemVer Check, Public API Check all failed with cannot find module or crate http at http_header.rs:48) — my insertion split the validator's doc block, so the #[cfg(feature = "client-side-sse")] line migrated to the new function.

Fixed in 257d55b by not putting the helper there at all: common::http_header is compiled without the http dependency, so an ungated helper cannot live in it. media_type_is now lives in common::client_side_http, the module both HTTP clients already use for response handling and which is gated on exactly the features that have a client (transport-streamable-http-client-reqwest or unix + transport-streamable-http-client-unix-socket). http_header.rs is back to its pre-change content, validator gate included.

Verified: cargo clippy --package rmcp --lib --no-default-features --features {client, client-side-sse, transport-streamable-http-client-reqwest, transport-streamable-http-client-unix-socket} -- -D warnings clean for each of the four (the four gates above are green on 257d55b).

Comment on lines +158 to +171
#[test]
fn media_type_is_case_insensitive() {
for value in [
b"application/json".as_slice(),
b"Application/JSON".as_slice(),
b"APPLICATION/JSON; charset=utf-8".as_slice(),
b" application/json ",
] {
assert!(media_type_is(value, "application/json"), "{value:?}");
assert!(!media_type_is(value, "text/event-stream"), "{value:?}");
}
}

#[test]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also fixed by the same move. The helper and its tests now sit in common::client_side_http, which is only compiled when a streamable HTTP client feature is on, so there is no feature combination where the tests reference a missing function — the ungated-mod tests problem disappears rather than needing per-test gates.

cargo test -p rmcp --lib --features "client transport-streamable-http-client-reqwest transport-streamable-http-client-unix-socket" -- client_side_http -> 23 passed, including the 7 new media_type_is_* cases.

`UnixSocketHttpClient` decided JSON vs SSE with a byte-prefix check, so a
response labelled `Application/JSON; charset=utf-8` was rejected with
`UnexpectedContentType` even though media types are case-insensitive
(RFC 9110) and parameters do not change the essence.

Move `media_type_is` out of the reqwest client into
`common::client_side_http`, the module both HTTP clients already share for
response handling, and use it at all four unix-socket call sites so the two
clients agree. `common::http_header` is compiled without the `http`
dependency, so the helper cannot live there.

Fixes modelcontextprotocol#1443

Signed-off-by: jokemanfire <[email protected]>
@jokemanfire
jokemanfire force-pushed the fix/1443-unix-socket-media-type branch from 8ed8e55 to 257d55b Compare November 14, 2026 13:12

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-core Core library changes T-test Testing related changes T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unix-socket HTTP client matches response media types by case-sensitive prefix

2 participants