Skip to content

fix: honor MCPServerRegistration targetRef namespace - #1139

Open
avinxshKD wants to merge 1 commit into
Kuadrant:mainfrom
avinxshKD:fix/targetref-namespace
Open

fix: honor MCPServerRegistration targetRef namespace#1139
avinxshKD wants to merge 1 commit into
Kuadrant:mainfrom
avinxshKD:fix/targetref-namespace

Conversation

@avinxshKD

@avinxshKD avinxshKD commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Fixes MCPServerRegistration reconciliation when spec.targetRef.namespace points at an HTTPRoute outside the registration namespace.

The API and docs allow this, and the field index/status update paths already account for it, but getTargetHTTPRoute always read the route from the MCPServerRegistration namespace.

This updates route lookup to use targetRef.namespace when set, with a regression test for cross-namespace refs.

Fixes: #1199

Tested:

  • go test ./internal/controller

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Fixed cross-namespace HTTPRoute handling for MCPServerRegistration by correctly honoring the target reference namespace (with default fallback).
    • Enforced Gateway API ReferenceGrant permissions for both route resolution and HTTPRoute status updates, preventing unauthorized cross-namespace cleanup/config removal.
    • Updated reconciliation to re-process affected MCPServerRegistrations when ReferenceGrants change.
  • Tests

    • Added/extended unit tests for target-namespace resolution, ReferenceGrant-required error behavior, status cleanup rules, and ReferenceGrant-driven reconcile triggers.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

MCPServerRegistration now resolves HTTPRoutes using targetRef.namespace, enforces ReferenceGrant authorization for cross-namespace access, updates status handling to match, and adds watch/index support plus tests.

Changes

Cross-namespace HTTPRoute lookup

Layer / File(s) Summary
Target route authorization
internal/controller/mcpserverregistration_controller.go
getTargetHTTPRoute uses targetRefNamespace(...), lists ReferenceGrant objects in the target namespace, and returns a missing-grant error when cross-namespace access is not authorized.
Status cleanup on missing grant
internal/controller/mcpserverregistration_controller.go
Reconcile removes managed config when the missing-grant error is returned, and updateHTTPRouteStatus skips cross-namespace updates without authorization.
ReferenceGrant indexing and watch
internal/controller/mcpserverregistration_controller.go
SetupWithManager adds ReferenceGrant indexing and watching, with helpers that map grants back to affected registrations through shared namespace-key logic.
Cross-namespace behavior tests
internal/controller/mcpserverregistration_controller_test.go
Adds fake-client tests for target namespace resolution, missing-grant cleanup, grant-driven indexing, and reconcile behavior when access is denied.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: high-risk

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly summarizes the primary fix: honoring targetRef.namespace during MCPServerRegistration HTTPRoute resolution.
Linked Issues check ✅ Passed The changes address cross-namespace targetRef resolution, keep route/status/index behavior consistent, require ReferenceGrant authorization, and add coverage for the misbinding case.
Out of Scope Changes check ✅ Passed The diff stays focused on targetRef namespace handling, ReferenceGrant gating, and related tests without unrelated feature work.
✨ 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

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

@coderabbitai coderabbitai Bot added the review-effort/medium Medium review effort (3): few files, moderate logic label Jun 14, 2026
@avinxshKD

Copy link
Copy Markdown
Contributor Author

Hey @jasonmadigan fixed a small controller bug where MCPServerRegistration.spec.targetRef.namespace was ignored during HTTPRoute lookup....added a regression test and verified with go test ./internal/controller.

@david-martin
david-martin marked this pull request as draft June 17, 2026 11:22
@david-martin david-martin added the triage/needs-issue PR needs a linked issue label Jun 22, 2026
@Aman-Cool

Copy link
Copy Markdown
Collaborator

Thanks for this @avinxshKD, it's exactly the getTargetHTTPRoute namespace bug, and the fix looks right: it now honors targetRef.namespace and matches the status-writeback and watch-index paths, so all three finally agree.

I'd actually filed #1199 for this same bug just before David pointed me here.., could you add Fixes: #1199 to the description? (also clears the needs-issue label.)

A few notes while it's still draft, no rush:

And whenever your primary PR merges (and if you've nothing else queued), would be great to bring this out of draft. happy to re-review and help get it over the line 🙂

Aman-Cool added a commit to Aman-Cool/A2A-Agent2Agent-protocol that referenced this pull request Jun 30, 2026
…/skill)

addresses david-martin's review on Kuadrant#1114:
- Q2: target v1.0 (v1.0.1 current); version surface isolated, body refs being migrated
- (5) reject oldest-wins prefix tiebreak; recommend namespace-qualified /a2a/{namespace}/{prefix} (pending confirm)
- (4) cross-namespace allowed — controller honors targetRef.namespace (Kuadrant#1139, from Kuadrant#1199)
- (6) skill-level filtering is not applicable (message/send names no skill)
- (8) fix tenant line: tenant carries enforcement via header (x-mcp-toolname pattern); path's edge is attachment
- (3) status mirrors main — Ready = configured, drop AgentCardDiscovered/discoveredSkills
- add pluggable card-store backend seam (in-memory PoC, shared store a future option)
- update signed-card note for the v1.0 target + route-by-tenant/verbatim direction (open dependency flagged)

Signed-off-by: Aman-Cool <aman017102007@gmail.com>
Aman-Cool added a commit to Aman-Cool/A2A-Agent2Agent-protocol that referenced this pull request Jun 30, 2026
… (review #4)

david-martin confirmed: assume Kuadrant#1139 lands and copy its cross-namespace targetRef
handling rather than reimplementing it.

Signed-off-by: Aman-Cool <aman017102007@gmail.com>
@avinxshKD
avinxshKD marked this pull request as ready for review July 1, 2026 14:24
@avinxshKD
avinxshKD force-pushed the fix/targetref-namespace branch from f58aa32 to f378443 Compare July 1, 2026 14:34
@avinxshKD

Copy link
Copy Markdown
Contributor Author

Hey @Aman-Cool thanks for reviewing. Updated.

Added the same-name cross-namespace case and pulled the fallback into targetRefNamespace() so lookup/status/index use the same path now.

Left ReferenceGrant out for this one to keep the PR focused. Pls take a look.

cc @david-martin

@maleck13 maleck13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can't allow a cross namespace reference without a reference grant. The original code enforced same namespace specifically to stop cross namespace references. Being able to create an MCPServerRegistration is not enough permission wise. You need to have permission to expose that server on the HTTPRoute you are targeting. It being in the same ns is the first check. If it is not in the same namespace it must use a reference grant just like the MCPGatewayExtension does

@avinxshKD
avinxshKD force-pushed the fix/targetref-namespace branch from f378443 to 752d432 Compare July 2, 2026 14:21
@avinxshKD

Copy link
Copy Markdown
Contributor Author

@maleck13 Updated.
Cross-namespace HTTPRoute refs now require a ReferenceGrant before lookup. Added coverage for the same-name route case with a valid grant, and the no-grant rejection path.

cc @david-martin @Aman-Cool

@coderabbitai coderabbitai Bot added the high-risk Touches concurrency, auth, sessions, CRDs, ext_proc, or routing label Jul 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/controller/mcpserverregistration_controller.go (1)

351-353: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid copying ReferenceGrant objects in the loop.

Use index iteration and pass the slice element address.

Proposed change
-	for _, rg := range refGrantList.Items {
-		if referenceGrantAllowsMCPServerRegistrationHTTPRoute(&rg, mcpsr) {
+	for i := range refGrantList.Items {
+		if referenceGrantAllowsMCPServerRegistrationHTTPRoute(&refGrantList.Items[i], mcpsr) {
 			return true, nil
 		}
 	}

As per coding guidelines, "Use for i := range not for _, v := range on large structs."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/mcpserverregistration_controller.go` around lines 351 -
353, The loop in the MCP server registration check is copying each
ReferenceGrant object, which is unnecessary for a large struct. Update the
iteration in the function that calls
referenceGrantAllowsMCPServerRegistrationHTTPRoute to use index-based traversal
over refGrantList.Items and pass the address of the slice element instead of the
ranged value, keeping the existing return logic unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/controller/mcpserverregistration_controller.go`:
- Around line 617-620: The deletion cleanup path in updateHTTPRouteStatus can
update a cross-namespace HTTPRoute without going through the ReferenceGrant
check because it is invoked before getTargetHTTPRoute. Move the grant validation
into updateHTTPRouteStatus itself, or ensure the finalizer cleanup path skips
cross-namespace targets unless the ReferenceGrant has already been verified,
using the targetRef namespace handling and getTargetHTTPRoute as the key
locations to adjust.

---

Nitpick comments:
In `@internal/controller/mcpserverregistration_controller.go`:
- Around line 351-353: The loop in the MCP server registration check is copying
each ReferenceGrant object, which is unnecessary for a large struct. Update the
iteration in the function that calls
referenceGrantAllowsMCPServerRegistrationHTTPRoute to use index-based traversal
over refGrantList.Items and pass the address of the slice element instead of the
ranged value, keeping the existing return logic unchanged.
🪄 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: 24102982-09da-471c-beb3-9f31bc095084

📥 Commits

Reviewing files that changed from the base of the PR and between f378443 and 752d432.

📒 Files selected for processing (2)
  • internal/controller/mcpserverregistration_controller.go
  • internal/controller/mcpserverregistration_controller_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/controller/mcpserverregistration_controller_test.go

Comment thread internal/controller/mcpserverregistration_controller.go Outdated
@avinxshKD
avinxshKD force-pushed the fix/targetref-namespace branch from 752d432 to e46cce3 Compare July 2, 2026 15:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
internal/controller/mcpserverregistration_controller_test.go (2)

80-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a same-namespace (empty TargetRef.Namespace) subtest.

Good coverage for the cross-namespace grant/no-grant paths and the same-name-route collision case. Missing a subtest for the default fallback (TargetRef.Namespace == ""), which was specifically requested in review comments as coverage for targetRefNamespace()'s default path. If it's not already covered by an existing test earlier in this file, please add it.

🧪 Suggested subtest
{
    name: "falls back to registration namespace when targetRef namespace is empty",
    objects: []client.Object{
        testHTTPRoute("target-route", "registrations"),
    },
    wantNamespace: "registrations",
},

Note this would require the outer test to vary mcpsr.Spec.TargetRef.Namespace per case rather than hardcoding "routes".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/mcpserverregistration_controller_test.go` around lines 80
- 104, Add a same-namespace case to TestGetTargetHTTPRouteUsesTargetRefNamespace
so targetRefNamespace() is covered when TargetRef.Namespace is empty. Update the
existing test table and setup so mcpsr.Spec.TargetRef.Namespace can vary per
case instead of being hardcoded to "routes", and add a subtest that asserts the
registration namespace is used as the fallback. Keep the existing ReferenceGrant
and collision coverage intact in the same test.

152-209: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing positive test: cleanup succeeds when a valid ReferenceGrant is present.

This test only covers the "no grant → early return" branch. It doesn't verify that updateHTTPRouteStatus actually removes the Programmed condition on deletion when a valid ReferenceGrant exists in the target namespace — the actual "cleanup" behavior the test name references. Since this is new ReferenceGrant-gated logic, a bug in grant matching (e.g., always returning false) wouldn't be caught by the current test.

🧪 Suggested additional case
// seed testMCPServerReferenceGrant(...) alongside the HTTPRoute, then assert
// httpRoute.Status.Parents[0].Conditions is empty after calling updateHTTPRouteStatus
// with mcpsr.DeletionTimestamp set.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/mcpserverregistration_controller_test.go` around lines
152 - 209, The test for updateHTTPRouteStatus currently only covers the
no-ReferenceGrant early-return path and misses the successful cleanup case. Add
a positive test alongside
TestUpdateHTTPRouteStatusRequiresReferenceGrantForCrossNamespaceCleanup that
seeds a valid ReferenceGrant in the target namespace, then calls
MCPReconciler.updateHTTPRouteStatus with a deleting MCPServerRegistration and
verifies the HTTPRoute status no longer contains the Programmed condition. Use
the existing test helpers like testHTTPRoute and testMCPServerReferenceGrant to
keep the setup aligned with the new grant-gated logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/controller/mcpserverregistration_controller_test.go`:
- Around line 80-104: Add a same-namespace case to
TestGetTargetHTTPRouteUsesTargetRefNamespace so targetRefNamespace() is covered
when TargetRef.Namespace is empty. Update the existing test table and setup so
mcpsr.Spec.TargetRef.Namespace can vary per case instead of being hardcoded to
"routes", and add a subtest that asserts the registration namespace is used as
the fallback. Keep the existing ReferenceGrant and collision coverage intact in
the same test.
- Around line 152-209: The test for updateHTTPRouteStatus currently only covers
the no-ReferenceGrant early-return path and misses the successful cleanup case.
Add a positive test alongside
TestUpdateHTTPRouteStatusRequiresReferenceGrantForCrossNamespaceCleanup that
seeds a valid ReferenceGrant in the target namespace, then calls
MCPReconciler.updateHTTPRouteStatus with a deleting MCPServerRegistration and
verifies the HTTPRoute status no longer contains the Programmed condition. Use
the existing test helpers like testHTTPRoute and testMCPServerReferenceGrant to
keep the setup aligned with the new grant-gated logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6f25811-05dd-4464-898a-b0a1f9832a78

📥 Commits

Reviewing files that changed from the base of the PR and between 752d432 and e46cce3.

📒 Files selected for processing (2)
  • internal/controller/mcpserverregistration_controller.go
  • internal/controller/mcpserverregistration_controller_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/controller/mcpserverregistration_controller.go

@avinxshKD
avinxshKD force-pushed the fix/targetref-namespace branch from e46cce3 to c20b9bc Compare July 2, 2026 16:03
@Aman-Cool

Copy link
Copy Markdown
Collaborator

@avinxshKD. Agreeing with @maleck13... creating a registration shouldn't be enough to expose another namespace's server, so same-namespace as the first check and a grant otherwise is the right boundary. The updated fix looks right too; grant check before the lookup, and targetRefNamespace() keeping the getter, status update and index in agreement.

Two things I'd flag though: there's no ReferenceGrant watch, so a grant created or revoked after the registration does nothing until something unrelated triggers a reconcile; the extension controller already watches grants, so it's the other half of the pattern being copied here. And revoking a grant only flips status.., the already-written config stays in the Secret and the broker keeps serving the server after consent was withdrawn, so the no-grant path probably wants a RemoveMCPServer so revoking actually revokes.

@avinxshKD
avinxshKD force-pushed the fix/targetref-namespace branch from c20b9bc to 6797135 Compare July 3, 2026 06:54
@avinxshKD

Copy link
Copy Markdown
Contributor Author

@Aman-Cool Thanks for the review and agreed. Updated this to watch ReferenceGrants as well, and the no-grant path now removes the MCPServer config so revokes take effect.

Also filled in the fallback/cleanup test cases. Pls take a look when get chance.

@avinxshKD
avinxshKD requested a review from maleck13 July 3, 2026 06:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
internal/controller/mcpserverregistration_controller.go (1)

627-648: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Allow status cleanup without a ReferenceGrant.
Revoking the grant leaves the target HTTPRoute with a stale Programmed/InUseByMCPServerRegistration condition because updateHTTPRouteStatus returns before removing it. Let cleanup proceed even when the grant is gone, while still requiring the grant to set the condition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/mcpserverregistration_controller.go` around lines 627 -
648, The cleanup path in updateHTTPRouteStatus is returning too early when
hasValidHTTPRouteReferenceGrant is false, which prevents stale
Programmed/InUseByMCPServerRegistration conditions from being removed. Update
the logic around targetRefNamespace, hasValidHTTPRouteReferenceGrant, and the
HTTPRoute fetch so that a missing ReferenceGrant only blocks setting the
condition, but still allows the code to continue and clear existing status on
the target HTTPRoute.
🧹 Nitpick comments (3)
internal/controller/mcpserverregistration_controller.go (2)

877-884: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Range large struct by index. MCPServerRegistration is a large struct; iterating by value copies each item. Use the index form (as done in hasValidHTTPRouteReferenceGrant at Line 361).

♻️ Proposed change
-		for _, mcpsr := range mcpsrList.Items {
-			nn := client.ObjectKeyFromObject(&mcpsr)
+		for i := range mcpsrList.Items {
+			nn := client.ObjectKeyFromObject(&mcpsrList.Items[i])
 			if _, ok := seen[nn]; ok {
 				continue
 			}

As per coding guidelines: "Use for i := range not for _, v := range on large structs".

Source: Coding guidelines


868-875: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Per-iteration List error discards already-collected requests. Returning nil on a transient list failure for one from entry drops enqueues gathered from earlier entries, silently skipping reconciliation. Prefer continue.

♻️ Proposed change
 		if err := r.List(ctx, mcpsrList,
 			client.MatchingFields{MCPServerRegistrationReferenceGrantIndex: refGrantFromToMCPServerRegistrationIndexValue(from)},
 		); err != nil {
 			logf.FromContext(ctx).Error(err, "Failed to list MCPServerRegistrations for ReferenceGrant")
-			return nil
+			continue
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/mcpserverregistration_controller.go` around lines 868 -
875, The per-iteration list failure in the ReferenceGrant processing loop drops
all previously collected requests because the code returns nil inside the
ref.Spec.From loop. Update the error handling in the MCPServerRegistration
controller’s ReferenceGrant path to log the failure for the current from entry
and continue to the next iteration instead of exiting early. Keep the existing
request collection logic intact so earlier enqueues are preserved when one
r.List call fails.
internal/controller/mcpserverregistration_controller_test.go (1)

120-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared scheme setup to reduce duplication.

The runtime.NewScheme() + AddToScheme/Install triple is repeated across four test functions. A small newTestScheme(t *testing.T) *runtime.Scheme helper would remove the repetition.

♻️ Proposed helper
func newTestScheme(t *testing.T) *runtime.Scheme {
	t.Helper()
	scheme := runtime.NewScheme()
	if err := mcpv1alpha1.AddToScheme(scheme); err != nil {
		t.Fatalf("AddToScheme: %v", err)
	}
	if err := gatewayv1.Install(scheme); err != nil {
		t.Fatalf("gatewayv1.Install: %v", err)
	}
	if err := gatewayv1beta1.Install(scheme); err != nil {
		t.Fatalf("gatewayv1beta1.Install: %v", err)
	}
	return scheme
}

Also applies to: 205-208, 249-251, 288-291

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/mcpserverregistration_controller_test.go` around lines
120 - 123, The scheme setup is duplicated across multiple controller tests;
extract it into a shared helper such as newTestScheme(t *testing.T) in
mcpserverregistration_controller_test.go. Move the runtime.NewScheme() plus
mcpv1alpha1.AddToScheme, gatewayv1.Install, and gatewayv1beta1.Install calls
into that helper, use t.Helper() and fail fast on any error, then replace the
repeated blocks in each test function with a single call to newTestScheme.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/controller/mcpserverregistration_controller.go`:
- Around line 627-648: The cleanup path in updateHTTPRouteStatus is returning
too early when hasValidHTTPRouteReferenceGrant is false, which prevents stale
Programmed/InUseByMCPServerRegistration conditions from being removed. Update
the logic around targetRefNamespace, hasValidHTTPRouteReferenceGrant, and the
HTTPRoute fetch so that a missing ReferenceGrant only blocks setting the
condition, but still allows the code to continue and clear existing status on
the target HTTPRoute.

---

Nitpick comments:
In `@internal/controller/mcpserverregistration_controller_test.go`:
- Around line 120-123: The scheme setup is duplicated across multiple controller
tests; extract it into a shared helper such as newTestScheme(t *testing.T) in
mcpserverregistration_controller_test.go. Move the runtime.NewScheme() plus
mcpv1alpha1.AddToScheme, gatewayv1.Install, and gatewayv1beta1.Install calls
into that helper, use t.Helper() and fail fast on any error, then replace the
repeated blocks in each test function with a single call to newTestScheme.

In `@internal/controller/mcpserverregistration_controller.go`:
- Around line 868-875: The per-iteration list failure in the ReferenceGrant
processing loop drops all previously collected requests because the code returns
nil inside the ref.Spec.From loop. Update the error handling in the
MCPServerRegistration controller’s ReferenceGrant path to log the failure for
the current from entry and continue to the next iteration instead of exiting
early. Keep the existing request collection logic intact so earlier enqueues are
preserved when one r.List call fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e962a17c-758c-44ba-8382-bbb17ea093af

📥 Commits

Reviewing files that changed from the base of the PR and between e46cce3 and 6797135.

📒 Files selected for processing (2)
  • internal/controller/mcpserverregistration_controller.go
  • internal/controller/mcpserverregistration_controller_test.go

@avinxshKD
avinxshKD force-pushed the fix/targetref-namespace branch from 6797135 to bb5a802 Compare July 3, 2026 07:09
@avinxshKD

Copy link
Copy Markdown
Contributor Author

This now covers the full cross-namespace permission path, not just the namespace lookup: ReferenceGrant gate, revoke requeue, stale config cleanup, and regression tests around each path.

@maleck13

maleck13 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Suggestion: when errReferenceGrantRequired is detected in Reconcile (controller L147-157), the status update uses conditionReasonNotReady. The MCPGatewayExtension controller uses a dedicated mcpv1alpha1.ConditionReasonRefGrantRequired ("ReferenceGrantRequired") for this case — see mcpgatewayextension_controller.go:324. The constant already exists in the API types package.

Using the same reason here would keep status reasons consistent across controllers and let users distinguish "missing ReferenceGrant" from other NotReady causes when inspecting MCPServerRegistration status conditions.

Signed-off-by: Avinash Kumar Deepak <avinash8655279@gmail.com>
@avinxshKD
avinxshKD force-pushed the fix/targetref-namespace branch from bb5a802 to f325bab Compare July 3, 2026 08:41
@avinxshKD

Copy link
Copy Markdown
Contributor Author

@maleck13 good call, updated this to use ConditionReasonRefGrantRequired for the missing grant path and added a status assertion for it.

@avinxshKD

Copy link
Copy Markdown
Contributor Author

@Aman-Cool @jasonmadigan ptal when get chance and lmk if changes needed

@Patryk-Stefanski Patryk-Stefanski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the fix — the core approach is right and the ReferenceGrant enforcement is the correct security model for cross-namespace references. A few things to address before this merges (see inline comments).

@@ -41,6 +43,8 @@ const (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Blocking: errReferenceGrantRequired falls through to return ctrl.Result{}, fmt.Errorf("reconcile failed %w", err), causing a tight requeue loop.

When a ReferenceGrant is missing, the ReferenceGrant watcher already handles re-triggering reconciliation once the grant appears. Returning an error here causes the controller to requeue immediately with exponential backoff — burning cycles for nothing, identical to any transient error.

The right behaviour is to update status (already done above) and return ctrl.Result{}, nil, trusting the watch to re-trigger. Suggested fix:

if errors.Is(err, errReferenceGrantRequired) {
    statusReason = mcpv1alpha1.ConditionReasonRefGrantRequired
    if removeErr := r.ConfigReaderWriter.RemoveMCPServer(ctx, mcpServerName(mcpsr)); removeErr != nil {
        return ctrl.Result{}, removeErr
    }
    if updateErr := r.updateStatus(ctx, mcpsr, false, statusReason, err.Error()); updateErr != nil {
        if apierrors.IsConflict(updateErr) {
            return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil
        }
        return ctrl.Result{}, fmt.Errorf("reconcile failed: status update failed %w", updateErr)
    }
    return ctrl.Result{}, nil // ReferenceGrant watch re-triggers when grant is created
}

Compare with how the deletion finalizer path (line ~128) returns nil cleanly after completing its work.

}
}

httpRoute := &gatewayv1.HTTPRoute{}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: silent no-op when ReferenceGrant is revoked on a live registration should at least log.

If a ReferenceGrant is revoked while the MCPServerRegistration is not being deleted, this returns nil silently — the stale route status stays on the HTTPRoute. That's a safe default, but without a log line an operator has no way to know why route status isn't being cleaned up. Consider:

if !hasGrant && mcpsr.DeletionTimestamp == nil {
    logger.Info("skipping HTTPRoute status update: ReferenceGrant missing", "targetNamespace", targetNamespace)
    return nil
}

func refGrantFromToMCPServerRegistrationIndexValue(from gatewayv1beta1.ReferenceGrantFrom) string {
return fmt.Sprintf("%s/%s/%s", from.Group, from.Kind, from.Namespace)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: the two index-key functions form a matched pair but their symmetry is non-obvious.

mcpServerRegistrationToRefGrantIndexValue produces <group>/MCPServerRegistration/<namespace> while refGrantFromToMCPServerRegistrationIndexValue produces <group>/<kind>/<namespace>. They match when the ReferenceGrant's from.Kind == "MCPServerRegistration" and from.Group == mcp.kuadrant.io/v1alpha1, which is correct — but nothing in the code makes this pairing visible to a future reader.

A short comment on either function linking them would help:

// produces a key matching refGrantFromToMCPServerRegistrationIndexValue for an MCPServerRegistration from
func mcpServerRegistrationToRefGrantIndexValue(mcpsr mcpv1alpha1.MCPServerRegistration) string {

},
Spec: mcpv1alpha1.MCPServerRegistrationSpec{
TargetRef: mcpv1alpha1.TargetReference{
Name: "target-route",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: test name is misleading.

TestUpdateHTTPRouteStatusRequiresReferenceGrantForCrossNamespaceCleanup implies that a ReferenceGrant is required for cleanup to occur, but the test actually proves the opposite: status is removed even without a grant (during deletion). The name should reflect what is actually under test, e.g. TestUpdateHTTPRouteStatusCrossNamespaceCleanupOnDeletion.

scheme := testScheme(t)

mcpsr := &mcpv1alpha1.MCPServerRegistration{
ObjectMeta: metav1.ObjectMeta{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: this test asserts the current (broken) error-return behaviour.

Lines 532–537 assert err != nil and that the error message contains "ReferenceGrant required". If the blocking issue above is fixed (Reconcile returns nil when the grant is missing), this assertion will need to flip to if err != nil { t.Fatal(...) }. Worth updating both together so the test documents the intended behaviour, not the current one.

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

Labels

high-risk Touches concurrency, auth, sessions, CRDs, ext_proc, or routing review-effort/medium Medium review effort (3): few files, moderate logic triage/needs-issue PR needs a linked issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCPServerRegistration: targetRef.namespace is ignored during HTTPRoute resolution

5 participants