@@ -212,6 +212,16 @@ egress:
212212 // serving the MCP service "calc" from a backend this test runs.
213213 backend := httptest .NewServer (newBoundaryMCPHandler (t ))
214214 t .Cleanup (backend .Close )
215+ // A stock A2A agent behind the node; its card names its own address.
216+ agentCard := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
217+ if r .URL .Path != "/.well-known/agent-card.json" {
218+ http .NotFound (w , r )
219+ return
220+ }
221+ w .Header ().Set ("Content-Type" , "application/json" )
222+ _ , _ = io .WriteString (w , sdkMeshStockAgentCard )
223+ }))
224+ t .Cleanup (agentCard .Close )
215225 nodeBin := buildBinary (t , "./cmd/sam-node" )
216226 nodeHome := filepath .Join (t .TempDir (), "node" )
217227 if err := os .MkdirAll (nodeHome , 0o755 ); err != nil {
@@ -232,7 +242,9 @@ egress:
232242 "--allow-loopback" ,
233243 "--api-token-path" , tokenPath (t , "node-token" ),
234244 "--discovery-interval" , "100ms" ,
235- "--config" , writeNodeConfig (t , nodeHome , sdkMeshLabels , svcDecl {Type : "mcp" , Name : "calc" , TargetURL : backend .URL }),
245+ "--config" , writeNodeConfig (t , nodeHome , sdkMeshLabels ,
246+ svcDecl {Type : "mcp" , Name : "calc" , TargetURL : backend .URL },
247+ svcDecl {Type : "a2a" , Name : sdkMeshAgentName , TargetURL : agentCard .URL }),
236248 "--secrets-dir" , secrets ,
237249 "--log-level" , "debug" ,
238250 )
@@ -259,6 +271,15 @@ egress:
259271 }
260272}
261273
274+ const sdkMeshAgentName = "echo-agent"
275+
276+ const sdkMeshStockAgentCard = `{"name":"echo-agent","description":"stock agent behind a sam-node","version":"1.0.0",` +
277+ `"capabilities":{"streaming":true},` +
278+ `"supportedInterfaces":[{"url":"http://127.0.0.1:7777/","protocolBinding":"JSONRPC","protocolVersion":"1.0"},` +
279+ `{"url":"127.0.0.1:50051","protocolBinding":"GRPC","protocolVersion":"1.0"}],` +
280+ `"signatures":[{"protected":"eyJhbGciOiJFUzI1NiJ9","signature":"c3RhbGU"}],` +
281+ `"skills":[],"defaultInputModes":["text/plain"],"defaultOutputModes":["text/plain"]}`
282+
262283// sdkMember is a running SDK conformance-join runner: a mesh member written
263284// in another language that the test drives over stdin/stdout. Both runners
264285// (sdk/js/src/conformance-join.ts, sdk/python/src/agent_mesh/conformance_join.py)
@@ -468,6 +489,35 @@ func TestNativeSDKsMesh(t *testing.T) {
468489 if res := m .callRaw (t , nodeRelayAddr , "mcp://no-such-service" , "add" , nil ); res .OK {
469490 t .Fatalf ("%s called a service the node does not serve: %+v" , m .name , res )
470491 }
492+
493+ // A stock client bootstraps from the node's agent card, so the SDK
494+ // serves it rewritten for the mesh, as the node's egress proxy does.
495+ res := m .http (t , nodeRelayAddr , "a2a://" + sdkMeshAgentName , "/.well-known/agent-card.json" )
496+ var card struct {
497+ SupportedInterfaces []struct {
498+ URL string `json:"url"`
499+ ProtocolBinding string `json:"protocolBinding"`
500+ } `json:"supportedInterfaces"`
501+ Capabilities struct {
502+ Streaming bool `json:"streaming"`
503+ } `json:"capabilities"`
504+ Signatures []json.RawMessage `json:"signatures"`
505+ }
506+ if res .Status != 200 || json .Unmarshal ([]byte (res .Body ), & card ) != nil {
507+ t .Fatalf ("%s fetching the node's agent card: %+v" , m .name , res )
508+ }
509+ meshBase := "http://mesh/sam/" + samNode .peerID .String () + "/a2a/" + sdkMeshAgentName
510+ if len (card .SupportedInterfaces ) != 1 || card .SupportedInterfaces [0 ].URL != meshBase || card .SupportedInterfaces [0 ].ProtocolBinding != "JSONRPC" {
511+ t .Errorf ("%s got interfaces %+v, want one JSONRPC interface at %s" , m .name , card .SupportedInterfaces , meshBase )
512+ }
513+ // Streaming stays as the agent declares it: the SDK's transport streams.
514+ if ! card .Capabilities .Streaming || len (card .Signatures ) != 0 {
515+ t .Errorf ("%s got a card with streaming=%v (want true) and %d signatures (want none)" , m .name , card .Capabilities .Streaming , len (card .Signatures ))
516+ }
517+ // The bare service root serves the same card, as the node does for a2a-go.
518+ if root := m .http (t , nodeRelayAddr , "a2a://" + sdkMeshAgentName , "/" ); root .Body != res .Body {
519+ t .Errorf ("%s got a different card at the service root: %s" , m .name , root .Body )
520+ }
471521 })
472522 }
473523
0 commit comments