Skip to content

mcp: jsonrpc response fix - #1997

Merged
nacx merged 13 commits into
envoyproxy:mainfrom
Hritik003:mcp-json-rpc-response-fix
Apr 8, 2026
Merged

mcp: jsonrpc response fix#1997
nacx merged 13 commits into
envoyproxy:mainfrom
Hritik003:mcp-json-rpc-response-fix

Conversation

@Hritik003

Copy link
Copy Markdown
Contributor

Description

the problem is that MCP backends are wildly inconsistent in how they format their responses:

Some return plain JSON (application/json Content-Type with a raw JSON-RPC body).
Some return gzip-compressed JSON without setting the Content-Encoding header properly (so the proxy can't rely on headers alone).
Some return SSE-framed data (lines like data: {...}) while incorrectly advertising Content-Type: application/json (e.g. Slack MCP).

decodeJSONRPCMessagesFromBackendBody handles all three cases with a cascading fallback strategy:

Try raw JSON decode first.
If that fails, sniff for the gzip magic bytes (0x1f 0x8b) and try decompressing.
If that also fails, check for SSE framing (data: prefix) and parse it as SSE.

Related Issues/PRs (if applicable)
Closes #1996


cc: @nacx

Signed-off-by: Hritik003 <hritik.raj@nutanix.com>
Signed-off-by: Hritik003 <hritik.raj@nutanix.com>
@Hritik003
Hritik003 requested a review from a team as a code owner March 30, 2026 08:33
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 30, 2026
@codecov-commenter

codecov-commenter commented Mar 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.03448% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.38%. Comparing base (5976c1f) to head (a3d2420).

Files with missing lines Patch % Lines
internal/mcpproxy/handlers.go 68.96% 8 Missing and 1 partial ⚠️
internal/mcpproxy/mcpproxy.go 80.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1997      +/-   ##
==========================================
- Coverage   84.40%   84.38%   -0.02%     
==========================================
  Files         130      130              
  Lines       18104    18113       +9     
==========================================
+ Hits        15280    15284       +4     
- Misses       1877     1883       +6     
+ Partials      947      946       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread internal/mcpproxy/handlers.go Outdated
if len(msgs) == 0 {
return fmt.Errorf("no JSON-RPC messages in MCP response body")
}
for _, mmsg := range msgs {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of duplicating this logic here, can't we just reuse the SSE logic that already exists?
Something like changing the decodeJSONRPCMessagesFromBackendBody method to something that tries to decode the full JSON body, and returns something that tells if it succeeded or if it is an SSE, then enter the SSE handling block below, instead of duplicating the logic.

I think it could be cleaner to have a tryDecodeJSON, and fallback to SSE, based on what that returns

Comment thread internal/mcpproxy/sse.go Outdated
return []jsonrpc.Message{msg}, nil
}
firstErr := err
if len(body) >= 2 && body[0] == 0x1f && body[1] == 0x8b {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does Slack return gzip? I'm wondering if this is a case we wanna handle here. There are other encryption such as br, that are also supported by our MCP server that we're not handling here...
I'm wondering what concrete cases you've found that need this very specific logic here, as I think we're starting to spread the logic about dealing with compressed payloads around, and that is going to become very hard to maintain.

@Hritik003
Hritik003 force-pushed the mcp-json-rpc-response-fix branch from d035a3f to c6c96c2 Compare April 5, 2026 16:26
Signed-off-by: Hritik003 <hritik.raj@nutanix.com>
Signed-off-by: Hritik003 <hritik.raj@nutanix.com>
Signed-off-by: Hritik003 <hritik.raj@nutanix.com>

@nacx nacx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall LGTM. Just one comment left.

Comment thread internal/controller/mcp_route.go Outdated

func mcpProxyBackendName(mcpRoute *aigv1a1.MCPRoute) string {
return fmt.Sprintf("%s-%s-mcp-proxy", mcpRoute.Namespace, mcpRoute.Name)
return fmt.Sprintf("%s%s-%s-mcp-proxy", internalapi.MCPGeneratedResourceCommonPrefix, mcpRoute.Namespace, mcpRoute.Name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would this change have an impact on upgrades?

@Hritik003 Hritik003 Apr 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes on controller restart, a new proxy Backend is created, and the old one becomes an orphan. Traffic will flow through the new Backend once the HTTPRoute is updated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Screenshot 2026-04-08 at 10 21 23 AM

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should we add a cleanup logic for the same?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have a question first. How is this change related to the feature this PR is about? If it is not related, I'd take taht out of this PR.

@nacx
nacx enabled auto-merge (squash) April 8, 2026 12:19
@nacx

nacx commented Apr 8, 2026

Copy link
Copy Markdown
Member

/retest

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

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP: Fails to decode json rpc message

3 participants