Skip to content

Fix: scope a2a/mcp parsers to their own JSON-RPC method namespace - #700

Merged
huang195 merged 1 commit into
rossoctl:mainfrom
huang195:fix/a2a-parser-empty-method-guard
Jul 28, 2026
Merged

Fix: scope a2a/mcp parsers to their own JSON-RPC method namespace#700
huang195 merged 1 commit into
rossoctl:mainfrom
huang195:fix/a2a-parser-empty-method-guard

Conversation

@huang195

@huang195 huang195 commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

a2a-parser and mcp-parser both parse JSON-RPC 2.0 request bodies, and each claimed a request whenever the body carried a non-empty method. But A2A and MCP both ride JSON-RPC 2.0 over HTTP, so "method is non-empty" can't tell them apart — and inference bodies (/v1/messages) unmarshal into a JSONRPCRequest with an empty method:

  • a2a-parser attached an A2AExtension + matched_ observe to MCP calls (initialize, tools/list, notifications/*, …) and to inference bodies.
  • mcp-parser symmetrically claimed A2A traffic (message/send, tasks/*).

In abctl this surfaced as a phantom second plugin on every MCP/inference request — e.g. an inference row labeled "2 plugins" (inference-parser + a2a-parser).

Change

The method namespace is the only in-body signal that distinguishes the two protocols, and the namespaces are disjoint:

Protocol Namespaces
A2A message/*, tasks/*, agent/*
MCP initialize, ping, notifications/*, tools/*, resources/*, prompts/*, completion/*, logging/*, sampling/*, roots/*, elicitation/*

Each parser now gates on its own namespace (isA2AMethod / isMCPMethod) instead of "non-empty method":

  • Each stays forward-compatible within its own namespace design (new message/*, tasks/*, tools/*, … match with no code change).
  • An empty method (non-JSON-RPC body, e.g. inference) matches neither.
  • mcp-parser's body-less transport detection ($transport/stream, $transport/terminate) is unchanged.

Regression tests added to both packages: each parser declines the other's methods and non-JSON-RPC bodies without attaching an extension or recording an invocation.

IBAC / Classification interaction (behavior note)

Scoping the parsers changes how one narrow traffic class is classified: a valid JSON-RPC body whose method is outside both namespaces. Previously such a body was claimed with IsAction=false, so Context.Classification() reported a bypass (→ Skip("protocol_mechanics")). Now no extension is attached, so Classification() reports unclassified (anyAction=false, anyBypass=false).

  • Under the default unclassified_policy: passthrough this is observability-only — the traffic still passes; only the skip/protocol_mechanics row is no longer recorded.
  • Under unclassified_policy: judge (non-default) such traffic is now judged instead of silently skipped. This matches that policy's "judge the unknowns" intent — the old phantom bypass actually let unknown JSON-RPC (and even real A2A message/send in an mcp-only outbound chain) slip past the judge.

The IBAC demo is unaffected: its exfil request is text/plain, already declined by mcp-parser's pre-existing non-JSON-RPC guard (before and after this change), and a2a-parser is inbound-only there. New tests assert Classification() is unclassified after each parser declines a foreign-namespace method.

Testing Instructions

go test ./...        # full authlib module — pass
golangci-lint run ./plugins/a2aparser/... ./plugins/mcpparser/...   # 0 issues

Assisted-By: Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved A2A and MCP request detection by validating method namespaces.
    • Prevented non-A2A traffic, including MCP requests, from being incorrectly classified as A2A.
    • Prevented non-MCP traffic, including A2A requests, from being incorrectly classified as MCP.
    • Requests with invalid or missing JSON-RPC methods now continue without attaching incorrect protocol metadata.
  • Tests

    • Added coverage for invalid request bodies and cross-protocol method handling.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A2A and MCP parsers now validate JSON-RPC method namespaces before attaching protocol extensions or recording invocation observations. Regression tests cover non-JSON-RPC payloads and cross-namespace methods.

Changes

Protocol namespace parser gating

Layer / File(s) Summary
A2A method gating and regression coverage
authbridge/authlib/plugins/a2aparser/plugin.go, authbridge/authlib/plugins/a2aparser/plugin_test.go
A2A parsing is limited to message/, tasks/, and agent/ methods; tests verify that empty, non-JSON-RPC, and MCP methods are declined.
MCP method gating and regression coverage
authbridge/authlib/plugins/mcpparser/plugin.go, authbridge/authlib/plugins/mcpparser/plugin_test.go
MCP parsing is limited to recognized MCP methods and namespaces; tests verify that A2A methods are declined.

Estimated code review effort: 2 (Simple) | ~15 minutes

Suggested reviewers: ibrahim2595, alan-cha, kellyaa

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: scoping the A2A and MCP parsers to their own JSON-RPC namespaces.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@huang195
huang195 force-pushed the fix/a2a-parser-empty-method-guard branch from 5c89d05 to 9a31507 Compare July 27, 2026 20:32
@huang195 huang195 changed the title Fix: a2a-parser skips JSON bodies without a JSON-RPC method Fix: scope a2a/mcp parsers to their own JSON-RPC method namespace Jul 27, 2026
a2a-parser and mcp-parser both parse JSON-RPC 2.0 request bodies, and
each claimed a request whenever the body carried a non-empty "method".
But A2A and MCP both ride JSON-RPC 2.0 over HTTP, so "method is
non-empty" cannot tell them apart:

  - a2a-parser attached an A2AExtension + a "matched_" observe to MCP
    calls (initialize, tools/list, notifications/*, ...) and to inference
    /v1/messages bodies (which unmarshal into JSONRPCRequest with an
    empty method).
  - mcp-parser symmetrically claimed A2A traffic (message/send, tasks/*).

In abctl this showed up as a phantom second plugin on every MCP and
inference request (e.g. an inference row labeled "2 plugins" =
inference-parser + a2a-parser).

The method namespace is the only in-body signal that distinguishes the
two protocols, and the namespaces are disjoint:

  - A2A:  message/*, tasks/*, agent/*
  - MCP:  initialize, ping, notifications/*, tools/*, resources/*,
          prompts/*, completion/*, logging/*, sampling/*, roots/*,
          elicitation/*

Gate each parser on its own namespace (isA2AMethod / isMCPMethod) instead
of "non-empty method". Each stays forward-compatible within its own
namespace design, and an empty method (non-JSON-RPC body) now matches
neither. mcp-parser's body-less transport detection ($transport/stream,
$transport/terminate) is unchanged.

IBAC classification interaction: for a JSON-RPC body whose method falls
outside both namespaces, Context.Classification() now reports
"unclassified" (no extension attached) instead of a recorded bypass.
Under the default unclassified_policy (passthrough) this is
observability-only — a skip/protocol_mechanics row is no longer
recorded. Under "judge" such traffic is now judged rather than silently
skipped, which matches that policy's "judge the unknowns" intent (the
old phantom bypass let unknown JSON-RPC skip the judge). The IBAC demo
is unaffected: its exfil body is text/plain and was already declined by
mcp-parser's pre-existing non-JSON-RPC guard.

Add regression tests: each parser declines the other's methods and
non-JSON-RPC bodies without attaching an extension or recording an
invocation, and Classification() reports unclassified in that case.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
@huang195
huang195 force-pushed the fix/a2a-parser-empty-method-guard branch from 9a31507 to f345284 Compare July 27, 2026 23:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@authbridge/authlib/plugins/a2aparser/plugin.go`:
- Around line 61-64: The A2A and MCP parsers accept namespace-matched methods
without validating the JSON-RPC version. In
authbridge/authlib/plugins/a2aparser/plugin.go lines 61-64 and
authbridge/authlib/plugins/mcpparser/plugin.go lines 226-228, require the parsed
jsonrpc discriminator to equal "2.0" before accepting the method; update
authbridge/authlib/plugins/a2aparser/plugin_test.go lines 333-368 and
authbridge/authlib/plugins/mcpparser/plugin_test.go lines 189-230 with
regression cases for method-bearing payloads missing or using an invalid jsonrpc
value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d6273e0-e6f8-436b-bd21-6427ecb75be1

📥 Commits

Reviewing files that changed from the base of the PR and between 5c89d05 and f345284.

📒 Files selected for processing (4)
  • authbridge/authlib/plugins/a2aparser/plugin.go
  • authbridge/authlib/plugins/a2aparser/plugin_test.go
  • authbridge/authlib/plugins/mcpparser/plugin.go
  • authbridge/authlib/plugins/mcpparser/plugin_test.go

Comment on lines +61 to +64
if !isA2AMethod(rpc.Method) {
slog.Debug("a2a-parser: not an A2A-namespace method, skipping", "method", rpc.Method, "bodyLen", len(pctx.Body))
return pipeline.Action{Type: pipeline.Continue}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Inspect whether JSONRPCRequest validates the JSON-RPC version during unmarshal.
rg -n -A30 -B3 'type JSONRPCRequest struct|UnmarshalJSON' authbridge

# Locate existing JSON-RPC-version checks and parser tests.
rg -n -A6 -B4 '"jsonrpc"|JSONRPC' authbridge

Repository: rossoctl/cortex

Length of output: 50374


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "=== parsercommon.go ==="
cat -n authbridge/authlib/plugins/internal/parsercommon/parsercommon.go | sed -n '1,180p'

echo "=== a2aparser plugin around entrypoint ==="
cat -n authbridge/authlib/plugins/a2aparser/plugin.go | sed -n '1,140p'

echo "=== mcpparser plugin around entrypoint ==="
cat -n authbridge/authlib/plugins/mcpparser/plugin.go | sed -n '200,250p'

echo "=== focused Unmarshal JSONRPC references ==="
rg -n -C 5 'JSONRPCRequest|jsonrpc' authbridge/authlib/plugins/internal/parsercommon authbridge/authlib/plugins/a2aparser authbridge/authlib/plugins/mcpparser

Repository: rossoctl/cortex

Length of output: 50371


Validate jsonrpc: "2.0" before accepting namespace-matched methods.

JSONRPCRequest has no custom unmarshaler, so valid JSON like {"method":"message/send","id":1} or {"method":"tools/list","id":1} can currently skip the namespace gate before being accepted. Add a jsonrpc discriminator check in both parsers, and add regression cases for method-bearing non-JSON-RPC payloads to both test files.

📍 Affects 4 files
  • authbridge/authlib/plugins/a2aparser/plugin.go#L61-L64 (this comment)
  • authbridge/authlib/plugins/mcpparser/plugin.go#L226-L228
  • authbridge/authlib/plugins/a2aparser/plugin_test.go#L333-L368
  • authbridge/authlib/plugins/mcpparser/plugin_test.go#L189-L230
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/authlib/plugins/a2aparser/plugin.go` around lines 61 - 64, The A2A
and MCP parsers accept namespace-matched methods without validating the JSON-RPC
version. In authbridge/authlib/plugins/a2aparser/plugin.go lines 61-64 and
authbridge/authlib/plugins/mcpparser/plugin.go lines 226-228, require the parsed
jsonrpc discriminator to equal "2.0" before accepting the method; update
authbridge/authlib/plugins/a2aparser/plugin_test.go lines 333-368 and
authbridge/authlib/plugins/mcpparser/plugin_test.go lines 189-230 with
regression cases for method-bearing payloads missing or using an invalid jsonrpc
value.

Source: Coding guidelines

@esnible esnible 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.

Clean, tightly-scoped bug fix. a2a-parser and mcp-parser both parse JSON-RPC 2.0 bodies and previously claimed any request with a non-empty method, so each attached a phantom extension to the other's traffic (and to inference bodies). Gating each parser on its own method namespace via isA2AMethod/isMCPMethod is the right fix.

Verified during review:

  • The two namespaces are provably disjoint (A2A: message/, tasks/, agent/; MCP: initialize, ping, and 9 distinct slash-prefixes) — no method is double-claimed, and A2A/MCP have no bare verbs outside these sets.
  • Both isA2AAction/isMCPAction sets are strict subsets of their gated namespaces, so no action-classified method is now dropped.
  • Guard placement is correct: after JSON-RPC parse, before extension attach.
  • MCP's body-less transport detection (len(Body)==0 branch) is untouched — the new guard only runs on non-empty bodies.
  • The subtle bypass -> unclassified behavior change is accurately documented and locked by the new Classification() assertions.

Regression tests on both parsers are assertive (Continue, nil extension, nil invocations, unclassified). All CI green, DCO signed off, valid Fix: title.

Author: huang195 (MEMBER — maintainer)
Areas reviewed: Go (authlib parser plugins), Tests
Agent/IDE config (.claude/.vscode): none
Commits: 1, all signed-off
CI status: passing

Assisted-By: Claude Code

@huang195
huang195 merged commit 1dfd691 into rossoctl:main Jul 28, 2026
21 checks passed
@github-project-automation github-project-automation Bot moved this from New/ToDo to Done in Rossoctl Issue Prioritization Jul 28, 2026
@huang195
huang195 deleted the fix/a2a-parser-empty-method-guard branch July 28, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants