Skip to content

feat: federate MCP resources/templates (list + read) with scheme-pref…#853

Closed
aryan7081 wants to merge 2 commits intoKuadrant:mainfrom
aryan7081:feat/mcp-resource-federation
Closed

feat: federate MCP resources/templates (list + read) with scheme-pref…#853
aryan7081 wants to merge 2 commits intoKuadrant:mainfrom
aryan7081:feat/mcp-resource-federation

Conversation

@aryan7081
Copy link
Copy Markdown

@aryan7081 aryan7081 commented May 2, 2026

Ref #788

Adds MCP resources and resource templates federation: upstream discovery in the broker, <toolPrefix>+<scheme>: URI namespacing, aggregated resources/list and resources/templates/list with gateway-internal _meta stripped, router handling for resources/read (routing + unprefixed upstream URI), and MCPServerRegistration.status.discoveredResources plus broker /status resource counts. Design: docs/design/resource-federation.md.

Known limitations

  • findResourceConflicts is a stub; duplicate-prefix registrations are not rejected for overlapping federated URIs the way tools are—routing can be nondeterministic until broker-side validation exists.
  • Resource templates: mcp-go AddResourceTemplates is merge-only—stale templates are possible when upstream removes or clears templates; documented in the design doc.

Clarifications I need

  1. Conflict detection: Should broker-side resource collision checks match tools before merge, or is the documented stub OK with a follow-up issue?
  2. E2E: Do you want blocking E2E for resources/list, resources/read, and resources/templates/list in this PR, or merge with unit tests and add E2E next?
  3. Templates: Is documenting staleness enough for v1, or should we block until broker-level template reconcile (e.g. aggregated SetResourceTemplates) or mcp-go gains proper removal/replace?
  4. Scope: Confirm subscriptions, forwarding notifications/resources/list_changed to clients, and JWT/virtual-server resource auth remain explicit non-goals for this PR.

Summary by CodeRabbit

Release Notes

  • New Features
    • Gateway now discovers and exposes MCP resources from upstream servers, complementing existing tool federation.
    • New resources/read requests allow clients to read resources through the gateway.
    • Resource URIs are automatically namespaced using the configured tool prefix.
    • New discoveredResources status field in server registrations reports discovered resource counts.
    • Resource templates are supported with automatic URI rewriting.

…ix URIs (Kuadrant#788)

Signed-off-by: aryan7081 <haryan458@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

Warning

Rate limit exceeded

@aryan7081 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 35 minutes and 48 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 658892ce-47f3-4dbe-a2b1-fdffc1d2f4f2

📥 Commits

Reviewing files that changed from the base of the PR and between 0227581 and 67867e7.

📒 Files selected for processing (10)
  • docs/design/resource-federation.md
  • internal/broker/broker.go
  • internal/broker/filtered_resources_handler_test.go
  • internal/broker/filtered_tools_handler_test.go
  • internal/broker/status_test.go
  • internal/broker/upstream/manager.go
  • internal/broker/upstream/manager_resources_test.go
  • internal/broker/upstream/manager_test.go
  • internal/mcp-router/resource_handlers.go
  • internal/mcp-router/resource_handlers_test.go
📝 Walkthrough

Walkthrough

The PR implements end-to-end MCP resources federation through the gateway. It adds resource discovery/listing in the upstream manager, broker-side resource aggregation with metadata filtering, router support for resource read requests with URI rewriting, CRD status tracking, and comprehensive tests across all layers.

Changes

MCP Resources Federation

Layer / File(s) Summary
CRD & API Types
api/v1alpha1/types.go, bundle/manifests/mcp.kuadrant.io_mcpserverregistrations.yaml, docs/reference/mcpserverregistration.md
MCPServerRegistrationStatus gains discoveredResources field; CRD adds printer column; toolPrefix documentation expanded to describe RFC 3986-compliant URI scheme prefixing for both tool names and federated resource URIs.
Dependency Management
go.mod
github.com/yosida95/uritemplate/v3 promoted from indirect to direct dependency for URI template handling.
Upstream Resource Federation
internal/broker/upstream/mcp.go, internal/broker/upstream/manager.go
MCPServer adds capability checks (SupportsResources, SupportsResourcesListChanged) and list methods (ListResources, ListResourceTemplates). MCPManager gains resource state (maps, locks), diffing/prefixing helpers, and manageResources flow mirroring tool management; includes ResourcesAdderDeleter interface and exported accessors (GetManagedResources, GetServedManagedResource, SetResourcesForTesting).
Broker Resource Aggregation & Lookup
internal/broker/broker.go, internal/broker/filtered_resources_handler.go
Broker adds GetServerInfoByResourceURI interface method and implements it via servedResourcesMap lookup. New FilterResources and FilterResourceTemplates hooks remove gateway-internal kuadrant/id metadata from list results and normalize nil slices to empty slices.
Router Resource Request Handling
internal/mcp-router/headers.go, internal/mcp-router/request_handlers.go, internal/mcp-router/resource_handlers.go
New x-mcp-resource-uri header support via WithMCPResourceURI(). Request handlers add resources/read method constant, ResourceURI() and ReWriteResourceURI() accessors, and routing logic. New HandleResourceRead handler validates URIs, looks up upstream server via broker, strips <prefix>+ scheme marker, initializes backend session, and forwards request with rewritten URI.
Controller Status Wiring
internal/controller/mcpserverregistration_controller.go
Reconcile and updateStatus updated to propagate resourceCount from broker status alongside toolCount, updating MCPServerRegistrationStatus.DiscoveredResources.
Tests & Mocks
internal/broker/filtered_resources_handler_test.go, internal/broker/filtered_tools_handler_test.go, internal/broker/status_test.go, internal/broker/upstream/manager_test.go, internal/broker/upstream/manager_resources_test.go, internal/mcp-router/request_handlers_test.go, internal/mcp-router/resource_handlers_test.go, internal/mcp-router/response_handlers_test.go
New test file manager_resources_test.go validates URI prefixing round-trips, resource diffing, server lookup, and manager resource federation. Updated mocks in manager_test.go, filtered_tools_handler_test.go, status_test.go, and router tests to support resource capabilities and updated NewUpstreamMCPManager constructor signature. New resource handler tests validate URI extraction, rewriting, header mutation, and error handling.
Design & Documentation
docs/design/resource-federation.md, tests/e2e/test_cases.md
New design document specifies URI namespacing strategy (scheme-plus prefix), conflict detection semantics, request flows for resources/list/resources/templates/list/resources/read, component-level changes, and backward compatibility. E2E test cases cover resource list aggregation with federated URIs, resource read routing with prefix stripping, and resource template federation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • Add support for MCP Resources federation #788 — This PR directly implements the end-to-end MCP resources federation feature, including upstream discovery/listing, broker aggregation with server routing, router request handling for resources/read, CRD status updates, and comprehensive test coverage across all layers.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: federation of MCP resources/templates with scheme-prefixed URIs (list and read operations).
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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
Review rate limit: 0/1 reviews remaining, refill in 35 minutes and 48 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/broker/upstream/manager.go (1)

252-255: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t return before the resource pass.

Once tools are already cached and the upstream supports notifications/tools/list_changed, timer events exit here. That means manageResources(...) never runs, so servers that rely on timer-based resource refresh stop updating resources/templates entirely.

Possible fix shape
- if !man.shouldFetchTools(event) {
- 	man.logger.Debug("not fetching tools", "event", event, "upstream mcp server", man.MCP.ID(), "waiting for notification", notificationToolsListChanged)
- 	return
- }
-
- man.logger.Debug("fetching tools", "upstream mcp server", man.MCP.ID())
- current, fetched, err := man.getTools(ctx)
- ...
+ if man.shouldFetchTools(event) {
+ 	man.logger.Debug("fetching tools", "upstream mcp server", man.MCP.ID())
+ 	current, fetched, err := man.getTools(ctx)
+ 	...
+ } else {
+ 	man.logger.Debug("not fetching tools", "event", event, "upstream mcp server", man.MCP.ID(), "waiting for notification", notificationToolsListChanged)
+ }
+
+ resourceCount, templateCount, resourceErr := man.manageResources(ctx, event)
+ ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/broker/upstream/manager.go` around lines 252 - 255, The early return
in the block that checks man.shouldFetchTools prevents the resource refresh pass
from running; instead of returning immediately when shouldFetchTools is false,
ensure manageResources still executes. Modify the branch around
man.shouldFetchTools (and the debug log referencing
"notificationToolsListChanged" and man.MCP.ID()) so that fetching of tools is
skipped but man.manageResources(event) is always invoked (or invoked before any
return), preserving the existing debug log context.
🧹 Nitpick comments (2)
docs/design/resource-federation.md (1)

133-133: ⚡ Quick win

Prefer > [!NOTE] admonition syntax.

The repo uses GitHub-style admonitions (> [!NOTE]) which the mkdocs plugin converts automatically. > **Note**: works but is inconsistent with the convention.

♻️ Proposed fix
-> **Note**: mcp-go does **not** expose a public `ListResources()` on the server (matching the prompts gap noted in [prompts-federation.md](prompts-federation.md)). The manager maintains its own `resourcesMap` and `servedResourcesMap` for lookups, the same way it does for tools.
+> [!NOTE]
+> mcp-go does **not** expose a public `ListResources()` on the server (matching the prompts gap noted in [prompts-federation.md](prompts-federation.md)). The manager maintains its own `resourcesMap` and `servedResourcesMap` for lookups, the same way it does for tools.

Based on learnings: "In this repo's GitHub-flavored docs, prefer the GitHub-style > [!NOTE] syntax."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/resource-federation.md` at line 133, Replace the inline
admonition style "Note" with the repository's preferred GitHub-style admonition;
change the line that currently reads "Note: mcp-go does not expose a public
`ListResources()`..." to use the GitHub-flavored admonition marker (`> [!NOTE]`)
instead, keeping the same content and references to `mcp-go`, `ListResources()`,
`resourcesMap`, and `servedResourcesMap` (and the link to
`prompts-federation.md`) so the mkdocs plugin will render it consistently.
internal/broker/filtered_resources_handler_test.go (1)

58-76: ⚡ Quick win

Missing nil-normalization test for FilterResourceTemplates.

TestFilterResources_NormalisesNilSlice covers the nil→[] normalization for resources, but there's no equivalent for templates. FilterResourceTemplates has the same nil-to-empty branch (per filtered_resources_handler.go) that would silently regress without coverage.

✅ Suggested test
+func TestFilterResourceTemplates_NormalisesNilSlice(t *testing.T) {
+	b := newTestBroker(t)
+	res := &mcp.ListResourceTemplatesResult{ResourceTemplates: nil}
+	b.FilterResourceTemplates(context.Background(), nil, &mcp.ListResourceTemplatesRequest{}, res)
+	assert.NotNil(t, res.ResourceTemplates, "nil slice must be replaced with empty slice so wire format is [] not null")
+	assert.Empty(t, res.ResourceTemplates)
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/broker/filtered_resources_handler_test.go` around lines 58 - 76, Add
a unit test to assert FilterResourceTemplates normalizes a nil ResourceTemplates
slice to an empty slice (same pattern as
TestFilterResources_NormalisesNilSlice). Create a test (e.g.
TestFilterResourceTemplates_NormalisesNilSlice) that constructs a broker with
newTestBroker(t), sets res :=
&mcp.ListResourceTemplatesResult{ResourceTemplates: nil}, calls
b.FilterResourceTemplates(context.Background(), nil,
&mcp.ListResourceTemplatesRequest{}, res) and then asserts that
res.ResourceTemplates is non-nil and has length 0; reference the
FilterResourceTemplates method and the
mcp.ListResourceTemplatesResult.ResourceTemplates field to locate where to add
the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/broker/upstream/manager.go`:
- Around line 382-398: The current logic in getResourceTemplates ->
templateToServerTemplate then gatewayResources.AddResourceTemplates only ever
adds templates, so removed upstream templates remain advertised; change the
publish behavior in manager.go (around man.getResourceTemplates,
man.templateToServerTemplate, and the gatewayResources.AddResourceTemplates
call) to perform a full replace: after acquiring man.resourcesLock and building
serverTemplates, compute which previously-published template IDs are no longer
present (compare man.resourceTemplates keys/IDs vs current templates), call
gatewayResources.RemoveResourceTemplates for those removed IDs, and then call
AddResourceTemplates for the current set (or use a
gatewayResources.ReplaceResourceTemplates API if available); also ensure
removeAllResources() clears published templates by invoking
RemoveResourceTemplates for all previously published IDs.
- Around line 773-776: The MCPManager.findResourceConflicts function currently
returns nil and must instead detect and reject duplicate-prefix / duplicate-URI
collisions before registration: iterate the provided candidates slice and
compare each server.ServerResource's prefix/URI against other candidates and the
manager's existing registered resources (the MCPManager's in-memory resource
registry/state), and if any duplicate prefix or URI is found return a non-nil
error describing the conflicting resource(s); ensure you perform this check
under the same lock/coordination used for mutations so the check is race-free
and return a clear error from findResourceConflicts when a conflict is detected
so resources/read rejects the registration.

In `@internal/mcp-router/resource_handlers.go`:
- Around line 37-38: Wrap the unconditional span.SetAttributes(...) calls with
an if span.IsRecording() check to avoid needless attribute serialization on
non-recording spans: keep the existing span.SetStatus(codes.Error, ...) calls
as-is but move each attribute write (span.SetAttributes(...)) inside a guard
like if span.IsRecording() { span.SetAttributes(...) }; do this for the
span.SetAttributes invocations shown (the one after span.SetStatus("no resource
uri set") and the other occurrences noted in the review) so all attribute writes
in resource_handlers.go use span.IsRecording() before calling
span.SetAttributes.
- Around line 71-84: The SSE JSON-RPC error payload currently omits the request
"id"; update the payload passed to
calculatedResponse.WithImmediateJSONRPCResponse so the JSON includes the
original request id (use mcpReq.ID or mcpReq.GetId()/GetID() as available)
alongside the error object and jsonrpc fields. Locate the call to
calculatedResponse.WithImmediateJSONRPCResponse and change the inline data
string to include `"id": <the-request-id>` (ensuring proper string
quoting/escaping) so clients can correlate the -32002 "Resource not found" error
with the original MCP request; keep the existing headers (mcpReq.GetSessionID())
and other fields unchanged.

---

Outside diff comments:
In `@internal/broker/upstream/manager.go`:
- Around line 252-255: The early return in the block that checks
man.shouldFetchTools prevents the resource refresh pass from running; instead of
returning immediately when shouldFetchTools is false, ensure manageResources
still executes. Modify the branch around man.shouldFetchTools (and the debug log
referencing "notificationToolsListChanged" and man.MCP.ID()) so that fetching of
tools is skipped but man.manageResources(event) is always invoked (or invoked
before any return), preserving the existing debug log context.

---

Nitpick comments:
In `@docs/design/resource-federation.md`:
- Line 133: Replace the inline admonition style "Note" with the repository's
preferred GitHub-style admonition; change the line that currently reads "Note:
mcp-go does not expose a public `ListResources()`..." to use the GitHub-flavored
admonition marker (`> [!NOTE]`) instead, keeping the same content and references
to `mcp-go`, `ListResources()`, `resourcesMap`, and `servedResourcesMap` (and
the link to `prompts-federation.md`) so the mkdocs plugin will render it
consistently.

In `@internal/broker/filtered_resources_handler_test.go`:
- Around line 58-76: Add a unit test to assert FilterResourceTemplates
normalizes a nil ResourceTemplates slice to an empty slice (same pattern as
TestFilterResources_NormalisesNilSlice). Create a test (e.g.
TestFilterResourceTemplates_NormalisesNilSlice) that constructs a broker with
newTestBroker(t), sets res :=
&mcp.ListResourceTemplatesResult{ResourceTemplates: nil}, calls
b.FilterResourceTemplates(context.Background(), nil,
&mcp.ListResourceTemplatesRequest{}, res) and then asserts that
res.ResourceTemplates is non-nil and has length 0; reference the
FilterResourceTemplates method and the
mcp.ListResourceTemplatesResult.ResourceTemplates field to locate where to add
the test.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cdfeb98d-ba43-4cbf-98f6-70cf5ded63a3

📥 Commits

Reviewing files that changed from the base of the PR and between 85e3b77 and 0227581.

⛔ Files ignored due to path filters (2)
  • charts/mcp-gateway/crds/mcp.kuadrant.io_mcpserverregistrations.yaml is excluded by !charts/mcp-gateway/crds/**
  • config/crd/mcp.kuadrant.io_mcpserverregistrations.yaml is excluded by !config/crd/mcp.kuadrant.io_*.yaml
📒 Files selected for processing (21)
  • api/v1alpha1/types.go
  • bundle/manifests/mcp.kuadrant.io_mcpserverregistrations.yaml
  • docs/design/resource-federation.md
  • docs/reference/mcpserverregistration.md
  • go.mod
  • internal/broker/broker.go
  • internal/broker/filtered_resources_handler.go
  • internal/broker/filtered_resources_handler_test.go
  • internal/broker/filtered_tools_handler_test.go
  • internal/broker/status_test.go
  • internal/broker/upstream/manager.go
  • internal/broker/upstream/manager_resources_test.go
  • internal/broker/upstream/manager_test.go
  • internal/broker/upstream/mcp.go
  • internal/controller/mcpserverregistration_controller.go
  • internal/mcp-router/headers.go
  • internal/mcp-router/request_handlers.go
  • internal/mcp-router/resource_handlers.go
  • internal/mcp-router/resource_handlers_test.go
  • internal/mcp-router/response_handlers_test.go
  • tests/e2e/test_cases.md

Comment thread internal/broker/upstream/manager.go Outdated
Comment thread internal/broker/upstream/manager.go
Comment thread internal/mcp-router/resource_handlers.go Outdated
Comment thread internal/mcp-router/resource_handlers.go Outdated
Signed-off-by: aryan7081 <haryan458@gmail.com>
@david-martin
Copy link
Copy Markdown
Member

Thanks for the work here, but resource federation is something we need to design and sequence internally — it touches CRDs and has broader implications for how the broker handles non-tool primitives. We have related work planned (#787, #788) and want to make sure the approach is cohesive.

Going to close this one, but don't let it discourage you from picking up smaller scoped issues.

Sign up for free to 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