Skip to content

feat(a2a): A2A server core + client transport + scheme-dispatched remotes - #266

Merged
IanFrelinger merged 2 commits into
masterfrom
feat/a2a-adapters
Aug 13, 2026
Merged

feat(a2a): A2A server core + client transport + scheme-dispatched remotes#266
IanFrelinger merged 2 commits into
masterfrom
feat/a2a-adapters

Conversation

@IanFrelinger

Copy link
Copy Markdown
Owner

Summary

PR-3 of the MCP/A2A series (stacked on #265#264#263; retarget as parents merge). Nexo speaks A2A v1.0 in both directions via the official A2A + A2A.AspNetCore SDK (1.0.0-preview2 — both consuming projects IsPackable=false with a tracking comment until a stable SDK ships, avoiding NU5104). Design: Phase-3 section of docs/architecture/ProtocolIntegration-MCP-A2A.md.

Changes

  • A2A client (src/Nexo.Transport.A2A): A2AAgentTransport : IAgentTransport selected by the a2a+ endpoint scheme — peers are ordinary EndpointDescriptors (a2a+https://peer/api/a2a/agent), so capability routing, health filtering, and barrier levels work unchanged and no kernel public API moved. Correlation/span/barrier context propagates as protocol metadata (message + request) with the same audit event as the gRPC transport's x-nexo-* headers. Remote task states map to typed results (a2a.task.failed, a2a.timeout, …); MaxRetries covers transport-level failures only; per-endpoint API keys are env-var-named and presence-verified at startup; card-fetch health probe included.
  • Remote-transport coexistence (Nexo.Abstractions + Nexo.Runtime): dependency-free AgentTransportSchemeRegistration + SchemeDispatchingAgentTransport; AddNexoRuntimeTransport wraps the remote side only when registrations exist — with none, composition is byte-for-byte the previous gRPC-only behavior, and kernel Phase05 is untouched. Hosts opt in with AddNexoA2ATransport(configuration) before AddNexo().
  • A2A server core (src/Nexo.Transport.A2A.Server): deny-by-default exposure (ExposedAgentIds allowlist + opt-in a2a coordination-protocol marker; enabled-with-zero-agents refuses to boot); NexoA2AAgentHandler mirrors the gRPC facade (fixed agent identity, bounded execution budget, IAgentTransport.SendAsyncnever the reflection-scanning AgentExecutorAdapter path); spec cards via INexoA2ACardProjector (streaming=false in the synchronous v1; the colliding A2A.AgentCard type never leaves the adapter); MapNexoA2AEndpoints() maps per-agent JSON-RPC + card under /api/a2a/{id} and the primary card at /.well-known/agent-card.json, returning RPC and card builders separately so the host can auth-gate them differently (AllowAnonymousAgentCard opt-out).
  • NexoIngressTransports gains mcp/a2a labels (+contracts test); TransportLayeringTests gains A2A mirrors; LICENSING OPEN rows; sln entries; docs Phase-3 section.

Testing

  • src/Nexo.Transport.A2A.Tests — 36/36 (scheme parsing, invocation mapping matrix incl. barrier metadata, options validation, DI no-op when disabled)
  • src/Nexo.Transport.A2A.Server.Tests — 13/13 (net9.0 + TestHost 9.0 VersionOverride, same pattern/reason as Nexo.Tests.Infrastructure), including a full-wire round trip: A2AAgentTransport → JSON-RPC over TestServer → mapped endpoints → handler → fake in-process transport, with correlation propagation asserted
  • src/Nexo.Tests.Transport — 81/81 (6 new scheme-dispatch/composition tests + 3 layering mirrors; no regressions)
  • src/Nexo.Tests.Contracts — 13/13 on both TFMs

Testing strategy (blast radius)

Tier-2 adapters + one composition change inside Nexo.Runtime''s AddNexoRuntimeTransport (covered by new Nexo.Tests.Transport tests, incl. a no-registrations regression guard). No Nexo.Hosting, no gate-path files.

  • make kernel-coverage-gate — n/a (no Core.Domain/Core.Application/Infrastructure changes)
  • make kernel-gate — n/a
  • make test-prod-style — n/a (ProdStyle endpoint coverage lands with the Nexo.API wiring + PR-5 per the layer-boundary split)

Checklist

  • Documentation updated
  • No TODO or NotImplementedException left unresolved
  • Breaking changes are documented (none — additive; transport composition provably unchanged without registrations)

Release (only when this PR ships a versioned NuGet/GHCR release)

  • Not a versioned release — skip

🤖 Generated with Claude Code

@cursor

cursorBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

PlzTouchGrassand others added 2 commits August 13, 2026 14:17
Nexo as an MCP client: external streamable-HTTP MCP servers surface their tools
as namespaced ITool proxies (mcp:{server}:{tool}) that reach agent toolboxes
through a new IToolSource seam.
- src/Nexo.Mcp.Client: McpClientConnectionManager (hosted service) dials
configured servers, pins each tool definition (name/description/raw schema)
for the process lifetime, and faults on drift instead of following remote
redefinitions; unreachable servers degrade to zero tools; remote isError and
transport failures map to error payloads per the repo tool convention;
per-server API keys are env-var named and verified present at startup.
- Nexo.Abstractions.IToolSource: SDK-free seam for post-startup-discovered
tools; RepoFsToolboxFactory gains an extraTools fold-in and
SelfExtendRunnerAdapter snapshots DI-registered sources each cycle.
- src/Nexo.Mcp.Client.Tests: 29 tests, including full protocol round trips -
a real McpClient against the real Nexo.Mcp.Server bridge over in-memory
pipes (list/call/structured-content/protocol-error), plus manager
pin-and-proxy end-to-end.
- Directory.Packages.props: Microsoft.Extensions.Options.ConfigurationExtensions
pin (10.0.11).
Fail-closed: Enabled=false default, ValidateOnStart, AirGapped refusal, empty
allowlists, stdio child processes deliberately excluded from v1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…otes
Nexo speaks A2A v1.0 in both directions via the official A2A + A2A.AspNetCore
SDK (1.0.0-preview2; consuming projects IsPackable=false until stable).
Client (src/Nexo.Transport.A2A):
- A2AAgentTransport : IAgentTransport selected by the a2a+ endpoint scheme;
peers are ordinary EndpointDescriptors, so routing/health/barrier metadata
work unchanged and no kernel public API moved.
- Correlation/span/barrier context propagates as protocol metadata on message
and request (gRPC x-nexo-* header equivalent, same audit event); remote task
states map to typed AgentResults; MaxRetries covers transport-level failures
only; per-endpoint API keys are env-var named and verified at startup.
Coexistence (src/Nexo.Runtime + Nexo.Abstractions):
- AgentTransportSchemeRegistration (Abstractions, dep-free) +
SchemeDispatchingAgentTransport (Runtime): AddNexoRuntimeTransport wraps the
remote side only when scheme registrations exist - with none, composition is
byte-for-byte the previous gRPC-only behavior. Phase05 untouched.
Server core (src/Nexo.Transport.A2A.Server):
- Deny-by-default exposure (ExposedAgentIds allowlist + opt-in coordination-
protocol marker); enabled-with-zero-agents refuses to boot.
- NexoA2AAgentHandler mirrors the gRPC facade: fixed agent identity, bounded
execution budget, IAgentTransport.SendAsync - never the reflection-scanning
RunAgentCommand/AgentExecutorAdapter path.
- Spec cards via INexoA2ACardProjector (streaming=false in the synchronous v1);
the colliding A2A.AgentCard type never leaves the adapter; hosts implement
INexoA2AAgentCatalog (adapter cannot reference Core.Domain per layering).
- MapNexoA2AEndpoints: per-agent JSON-RPC + card under /api/a2a/{id}, primary
card at /.well-known/agent-card.json; RPC and card builders returned
separately for differentiated host auth.
Tests: 36 transport + 13 server (incl. full-wire round trip: A2AAgentTransport
-> JSON-RPC over TestServer -> mapped endpoints -> handler -> fake in-process
transport, correlation verified) + 6 scheme-dispatch/composition tests and 3
layering mirrors in Nexo.Tests.Transport; NexoIngressTransports gains mcp/a2a
labels (contracts test updated).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@IanFrelinger
IanFrelinger changed the base branch from feat/mcp-client-tools to masterAugust 13, 2026 20:08
@IanFrelinger
IanFrelinger merged commit 02d98e3 into masterAug 13, 2026
10 checks passed
@IanFrelinger
IanFrelinger deleted the feat/a2a-adapters branch August 13, 2026 20:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@IanFrelinger@PlzTouchGrass