feat(mcp): expose agents as MCP resource with change notifications#1856
Open
onematchfox wants to merge 3 commits into
Open
feat(mcp): expose agents as MCP resource with change notifications#1856onematchfox wants to merge 3 commits into
onematchfox wants to merge 3 commits into
Conversation
… handlers The Go MCP SDK detaches the HTTP request context before dispatching to tool handlers. From the [SDK source](https://github.com/modelcontextprotocol/go-sdk/blob/v1.5.0/mcp/streamable.go#L485-L487): > // Pass req.Context() here, to allow middleware to add context values. > // The context is detached in the jsonrpc2 library when handling the > // long-running stream. This means the auth session placed by `AuthnMiddleware` is not visible via `auth.AuthSessionFrom(ctx)` in tool handlers. The SDK does preserve the original HTTP headers in [RequestExtra.Header](https://github.com/modelcontextprotocol/go-sdk/blob/v1.5.0/mcp/streamable.go#L1155-L1158) though. Re-authenticate from those headers at the top of handleInvokeAgent so the A2A client's outbound request to the agent carries the user's JWT. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Brian Fox <878612+onematchfox@users.noreply.github.com>
Replace the HTTP round-trip through the controller's own A2A listener with direct invocation via a new `AgentClientRegistry`. The registry is owned by `A2ARegistrar`, which already maintains an `A2AClient` per agent for its HTTP mux — the registry gives the MCP handler access to those same clients without an extra network hop. The old approach routed through the controller's public A2A endpoint, meaning requests could traverse the external network (and any ingress or load-balancer in front of it) unnecessarily. The new path stays in-process. The old handler also cached its own `A2AClient` per agent in a `sync.Map` with no eviction, so clients for deleted agents would remain indefinitely. The registry is kept consistent by the registrar's add/update/delete lifecycle, eliminating that staleness. `A2ARegistrar.upsertAgentHandler` writes to both the HTTP mux (for inbound /api/a2a/<ns>/<name>/ routing) and the registry (for direct invocation). The registry is exposed via `ClientRegistry()` and passed to `NewMCPHandler` in app.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Brian Fox <878612+onematchfox@users.noreply.github.com>
Adds a `kagent://agents` [MCP resource](https://modelcontextprotocol.io/specification/2025-06-18/server/resources) that lists agents as JSON. Clients can read the resource directly or subscribe to receive notifications when the agent list changes. I've left the existing `list_agents` tool in place for now to not break existing consumers although this can potentially be removed prior to the next breaking version? - Extract listReadyAgents helper shared by tool and resource handlers - Register kagent://agents resource with subscribe/unsubscribe support - Add NotifyAgentsChanged on MCPHandler, called via a new callback on A2ARegistrar whenever agents are added, updated, or removed - Wire the callback in app.go Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Brian Fox <878612+onematchfox@users.noreply.github.com>
508913d to
e231a30
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an MCP “agents” resource (kagent://agents) backed by the controller’s Kubernetes agent listing, plus change notifications so MCP clients can subscribe and refresh when the agent list changes. It also refactors MCP agent invocation to use in-process A2A clients via a new registry maintained by the A2A registrar (avoiding a loopback HTTP hop).
Changes:
- Introduce
kagent://agentsMCP resource (JSON) andresources/updatednotifications viaNotifyAgentsChanged. - Refactor
invoke_agentto call agents viaAgentClientRegistrypopulated byA2ARegistrar. - Add unit tests validating auth propagation for
invoke_agentwhen MCP request context is detached.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| go/core/pkg/app/app.go | Wires A2ARegistrar into MCP handler via ClientRegistry() and registers agent-change callback for notifications. |
| go/core/internal/mcp/mcp_handler.go | Adds MCP resource + notifications; refactors list logic; switches invocation to registry-based A2A clients and restores auth session from RequestExtra. |
| go/core/internal/mcp/mcp_handler_test.go | New tests for recovering auth from MCP RequestExtra and propagating upstream auth to A2A calls. |
| go/core/internal/a2a/agent_client_registry.go | Adds a concurrency-safe registry for mapping agent refs to A2A clients and invoking them directly. |
| go/core/internal/a2a/a2a_registrar.go | Populates registry during agent lifecycle and triggers a callback on agent add/update/delete. |
Comments suppressed due to low confidence (1)
go/core/internal/mcp/mcp_handler.go:236
- Comment says the agent reference can be "namespace/name or just name", but the implementation rejects values without a slash and returns an error. Either support the name-only form (e.g., defaulting the namespace) or update the comment/jsonschema to match the actual required format.
// Parse agent reference (namespace/name or just name)
agentNS, agentName, ok := strings.Cut(input.Agent, "/")
if !ok {
return &mcpsdk.CallToolResult{
Content: []mcpsdk.Content{
&mcpsdk.TextContent{Text: "agent must be in format 'namespace/name'"},
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
131
to
+136
| if oldAgent.GetGeneration() != newAgent.GetGeneration() || !sameAgentSpec(oldAgent, newAgent) { | ||
| if err := a.upsertAgentHandler(ctx, newAgent, log); err != nil { | ||
| log.Error(err, "failed to upsert A2A handler", "agent", common.GetObjectRef(newAgent)) | ||
| return | ||
| } | ||
| a.notifyAgentChange(ctx) |
| c, ok := r.clients[key] | ||
| r.mu.RUnlock() | ||
| if !ok { | ||
| return nil, fmt.Errorf("agent %s/%s not found or not ready", namespace, name) |
Comment on lines
+95
to
+104
| // Add agents resource for clients that pre-populate context | ||
| server.AddResource( | ||
| &mcpsdk.Resource{ | ||
| URI: "kagent://agents", | ||
| Name: "agents", | ||
| Description: "List of invokable kagent agents (accepted + deploymentReady)", | ||
| MIMEType: "application/json", | ||
| }, | ||
| handler.readAgentsResource, | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a
kagent://agentsMCP resource that lists agents as JSON. Clients can read the resource directly or subscribe to receive notifications when the agent list changes. I've left the existinglist_agentstool in place for now to not break existing consumers although this can potentially be removed prior to the next breaking version?Notes:
RequestExtrain tool handlers #1853 and refactor(controller): invoke agents directly in MCP handler #1855 at present. Diff will clean up as those get reviewed.