Skip to content

[Feat]: Agent card should be customizable per request #353

Description

@darena-mhaque

Is your feature request related to a problem? Please describe.

TLDR: Allow agent cards to by dynamic instead of a static agent card being registered on .AddA2AAgent.

Currently in the aspnetcore package, when you call IServiceCollection.AddA2AAgent, WebApplication.MapA2A, and WebApplication.MapHttpA2A, you need to provide an agent card.

This is an issue for servers that can host multiple agents and the agent changes depending on the url (and potentially other information like the current user).

Right now I can get by with:

  • IServiceCollection.AddA2AAgent - Here I can just pass new AgentCard() and no issues here.
  • WebApplication.MapA2A - This method also has an overload where you can pass in an IA2ARequestHandler which does not require an AgentCard, so we are good here.
  • WebApplication.MapHttpA2A - This method does not have the overload like the jsonrpc method. For now we are just telling our customers that the v1/card operation will return an empty agent card and they should just call the ./well-known/agent-card.json endpoint instead.

Note that we also aren't using the library's WebApplication.MapWellKnownAgentCard, since it uses the same static agent card that was registered on .AddA2AAgent, we implemented our own well known agent card endpoint.

BTW, I believe we were able to provide our own agent card on v0.3 of this library because the previous version of this library had a .OnAgentCardQuery handler on the task manager which allowed us to return the agent card at the time of request. Would like to have similar functionality carried over, or maybe the interface solution I mention below.

Describe the solution you'd like

Possibly an IAgentCard interface that allows the developer to return an agent card in their own way will be best.

Describe alternatives you've considered

No response

Additional context

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

  1. rokonec commented on Apr 10, 2026

    @rokonec
    Collaborator

    Thanks for the detailed write-up @darena-mhaque. The multi-agent hosting scenario you describe is a real need, so let me share what we found.

    The spec's answer: tenants + GetExtendedAgentCard

    The A2A v1.0 specification already addresses multi-agent hosting through the tenant mechanism:

    1. AgentInterface.Tenant — each entry in the agent card's supportedInterfaces can declare a different tenant value. A single well-known card at /.well-known/agent-card.json can advertise multiple agents, each with its own tenant and URL.

    2. All REST routes have /{tenant}/ prefix variants defined in the proto (e.g., /{tenant}/message:send, /{tenant}/tasks/{id}).

    3. GetExtendedAgentCard (Section 3.1.11) is the spec's intended mechanism for per-user/per-context dynamic cards. It explicitly "MAY return different details based on client authentication level" and has a /{tenant}/extendedAgentCard variant.

    Why not dynamic well-known cards

    The well-known card endpoint is designed as static by the spec — Section 8.6 recommends HTTP caching with max-age/ETag and notes "content changes infrequently." Making it dynamic per-request would conflict with the spec's caching model and diverge from the protocol.

    What's actually missing in the SDK

    The real gap is that the .NET SDK doesn't implement tenant routing yet. MapHttpA2A even documents this limitation today. We've filed #368 to track implementing:

    • /{tenant}/ REST route variants
    • Tenant extraction from routes into request objects
    • GetExtendedAgentCard with tenant support

    Suggested approach for your scenario

    With tenant support implemented:

    • Register one well-known card with multiple supportedInterfaces, each having a different Tenant + URL
    • Clients discover agents via the static card, then use the tenant-prefixed routes
    • For per-user details, override GetExtendedAgentCardAsync on your A2AServer subclass

    Would tenant routing address your needs, or is there a gap in this approach for your specific scenario?

  2. darena-mhaque commented on Apr 11, 2026

    @darena-mhaque
    Author

    We have at this time hundreds of users each building different agents on our platform, so there are multiple agents per user. We are estimating that eventually we will have 10s of thousands of agents, are you suggesting that the supportedInterfaces property should list all 10s of thousands of those agents?

    Regarding about your point on the caching of the agent card, suppose we get a new user tomorrow and now we have an additional supportedInterface that agent card needs to be updated. In an enterprise scenario, the supportedInterfaces will change frequently if we are to store all tenant specific interfaces in that array.

    Regarding "dynamic per-request", what I mean is that the subdomain of the /.well-known/agent-card.json endpoint changes and we should be able to return a different agent card depending on the subdomain. In an enterprise scenario it is common to assign a subdomain to different users, but it all being served by the same server underneath.

    I do not see supportedInterfaces and "tenants" in those interfaces solves this scaling issue. I still think we should just have what v0.3 of this library provided, some sort of .OnAgentCardQuery handler or similar functionality to allow the developer to provide the agent card, rather than it being a static card on server start, it's a limitation on the new v1 library.

    EDIT:
    Going to provide an example.

    We have two users, they each deployed an agent on our platform:

    User1
    Agent name: Scheduler
    Description: Can manage appointments for providers
    Skills: add appointment, delete appointment, reschedule appointment, cancel appointment

    User2
    Agent name: Clinical Trials
    Description: Can find patients that match trials
    Skills: read patient data, run trial match on population, run trial match on single patient

    What would be your recommendation on how to architect this with a single agent card on server start?

    Note, now imagine this at an enterprise scale, hundreds/thousands of users, each user deploying several agents.

  3. rokonec commented on Apr 17, 2026

    @rokonec
    Collaborator

    @darena-mhaque thanks for the follow-up. After looking at your scenario more closely, we believe this enters enterprise-scale territory where each deployment will have very different requirements around:

    • Storage isolation — separate databases per tenant, shared DB with row-level filtering, or in-memory per agent
    • Cache isolation — independent caches per agent vs shared with tenant-scoped keys
    • Authorization — per-subdomain auth, token-based tenant resolution, or external identity providers
    • Domain-to-agent mapping — static config, database-driven, or registry-based discovery
    • Agent lifecycle — dynamic creation/removal, versioning, health monitoring

    We can't reasonably cover all these combinations in the SDK itself. Instead, the SDK provides the building blocks (A2AServer, IAgentHandler, IA2ARequestHandler, InMemoryTaskStore, MapA2A) and we expect customers to compose them for their specific deployment model.

    To demonstrate how this can be approached, we've put together a draft sample in #380 that shows:

    • Subdomain-based multi-agent routing — a SubdomainMiddleware extracts the agent identity from the Host header (or an X-Agent-Subdomain header for local dev without DNS)
    • A2AServerFactory — lazily creates and caches an A2AServer per subdomain, each with its own InMemoryTaskStore and ChannelEventNotifier, guaranteeing task isolation by construction
    • Custom IAgentHandler per agent — each registration accepts its own handler implementation, so different agents can have completely different logic
    • Dynamic per-subdomain agent card generation — a custom MapGet(".well-known/agent-card.json") endpoint replaces the static MapWellKnownAgentCard, returning a different AgentCard (name, description, skills) based on the resolved subdomain
    • MultiAgentHandler adapter — a single IA2ARequestHandler that delegates all 11 interface methods to the correct A2AServer via IHttpContextAccessor, wired up with a single MapA2A call

    The accompanying MultiAgentClient validates agent discovery, message routing, task isolation, cross-agent access blocking, and unknown subdomain error handling.

    Note: This sample is not production-ready and serves educational purposes only. A production deployment would need persistent storage, proper authentication, handler eviction for memory management at scale, and error handling appropriate to your domain.

    Does this address your needs, or is there a specific gap in the SDK's extensibility that blocks your implementation?

  4. darena-mhaque commented on Apr 17, 2026

    @darena-mhaque
    Author

    This does not address my concerns. As mentioned, the JSONRPC binding I was able to find a workaround by using my own endpoint to handle the agent card. However, the issue is with the HTTP+JSON mapping where you need to provide a static agent card.

    Additionally, I feel like this is a lot of workaround magic whereas just providing an IAgentCardRetriever interface or similar works much better to allow developers to provide an agent card in their own way.

    As mentioned before the gap in the sdk is that in v0.3, we had an .OnAgentCard handler method that developers can customize to their needs, this kind of functionality is missing in v1.

  5. rokonec commented on Apr 18, 2026

    @rokonec
    Collaborator

    I see. I think solving dynamic agent card would be insufficient for many multi-agent use cases.

    Would bellow pattern suffice:

    // Shared factories
    IA2ARequestHandler ResolveHandler(HttpContext ctx)
    {
        var subdomain = ctx.RequestServices.GetRequiredService<SubdomainContext>().Subdomain
            ?? throw new A2AException("No subdomain.", A2AErrorCode.InvalidRequest);
        return serverFactory.GetServer(subdomain)
            ?? throw new A2AException($"Unknown: '{subdomain}'.", A2AErrorCode.InvalidRequest);
    }
    
    AgentCard ResolveCard(HttpContext ctx)
    {
        var subdomain = ctx.RequestServices.GetRequiredService<SubdomainContext>().Subdomain ?? "";
        var baseUrl = $"{ctx.Request.Scheme}://{ctx.Request.Host}";
        return serverFactory.GetAgentCard(subdomain, baseUrl)
            ?? throw new A2AException($"Unknown: '{subdomain}'.", A2AErrorCode.InvalidRequest);
    }
    
    // JSON-RPC + agent card discovery in one call
    app.MapA2A(ResolveHandler, ResolveCard, "/");
    
    // Optionally also add REST binding
    app.MapHttpA2A(ResolveHandler, ResolveCard);
  6. darena-mhaque commented on Apr 18, 2026

    @darena-mhaque
    Author

    Hmm, are you proposing that we allow the ability to pass in a method to .MapA2A and .MapHttpA2A? I suppose that can work. This is not functionality that exists today, does it seem like something that will be released in a future update?

  7. tracyboehrer commented on Oct 8, 2026

    @tracyboehrer
    Collaborator

    Context from current main and open PRs

    I reviewed this issue against the current code on main, the server tenant-field policy in #539, and the existing implementation attempt in #382. The underlying multi-agent hosting need remains valid, but several details in the original issue and earlier discussion are now outdated.

    Current state on main

    • MapHttpA2A no longer requires an AgentCard. fix: remove /card endpoint from HTTP+JSON binding #385 removed the non-standard /card route and its card parameter.
    • MapA2A also does not accept or serve an agent card. Agent discovery is mapped separately through MapWellKnownAgentCard.
    • MapWellKnownAgentCard currently accepts only a static AgentCard, so applications that select an agent by host, subdomain, authenticated identity, or another per-request context must still implement their own well-known endpoint.
    • AddA2AAgent<THandler> still requires a static card, but it also derives A2AServerOptions capabilities and supported input modes from that card. Replacing this parameter with a simple card provider would therefore affect more than card discovery.
    • Dynamic extended cards are already supported through IA2ARequestHandler.GetExtendedAgentCardAsync. A resolved handler can return the appropriate authenticated/extended card for its request context.

    There is also a small documentation inconsistency on main: the DI-based MapA2A(path) XML summary says it maps the JSON-RPC endpoint and well-known card, but the implementation maps only the JSON-RPC POST endpoint.

    Relationship to #539

    #539 clarifies the a2a-dotnet HTTP server model:

    • The host application selects the agent through its URL, host/subdomain, authenticated application context, or equivalent trusted routing state.
    • Incoming standard request Tenant fields are cleared before application dispatch and must not override that selection.
    • Clients continue serializing Tenant fields for interoperability with other A2A server implementations.

    Given that direction, the earlier recommendation in this thread to use supportedInterfaces[].tenant and SDK-owned tenant routing is no longer the right solution for a2a-dotnet HTTP hosting. The subdomain scenario described here is instead a direct example of host-application routing: resolve both the request handler and discovery card from the trusted HttpContext, not from the client-supplied Tenant field.

    Remaining SDK gap

    A focused implementation would add:

    1. An async, HttpContext-aware well-known card resolver, for example Func<HttpContext, CancellationToken, ValueTask<AgentCard>>.
    2. Per-request IA2ARequestHandler resolution for both MapA2A and MapHttpA2A.
    3. Protocol-appropriate error mapping when handler or card resolution fails.
    4. A shared HTTP+JSON route-registration implementation so fixed-handler and per-request-handler overloads cannot drift.
    5. Request-level tests proving that separate requests can resolve different agents and that resolver failures do not become generic HTTP 500 responses.
    6. Preservation of the existing source-generated serialization, caching headers, and Native AOT behavior.

    The v0.3 compatibility endpoint already provides an async card-factory precedent through MapAgentCardGetWithV03Compat(Func<Task<AgentCard>>, ...).

    Status of #382

    #382 implements the general factory-overload direction, but it is now 114 commits behind main and should not be merged as-is:

    • It includes the /card HTTP+JSON route and card parameter that fix: remove /card endpoint from HTTP+JSON binding #385 subsequently removed.
    • Its card factories are synchronous, which would force sync-over-async for database or registry-backed agent resolution.
    • It duplicates the complete HTTP+JSON route table rather than sharing route registration.
    • Its tests primarily validate endpoint registration and arguments rather than per-request selection and error behavior.
    • Its change making A2AJsonRpcProcessor.ProcessRequestAsync public is already present on main.

    My recommendation is to keep this issue open, narrow it to trusted per-request handler and well-known-card resolution, and either substantially refresh #382 from current main or replace it with a new implementation. The issue should not include tenant-field routing as part of the proposed solution.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions