feat(api): add POST /v1/mcp-servers/test endpoint for React UI connection testing#5443
feat(api): add POST /v1/mcp-servers/test endpoint for React UI connection testing#5443Lang-Akshay wants to merge 6 commits into
Conversation
3458c16 to
7782c65
Compare
90c2b37 to
2c51532
Compare
POST /v1/mcp-servers/test endpoint for MCP server connection testing99f878f to
b316454
Compare
marekdano
left a comment
There was a problem hiding this comment.
Solid, well-scoped refactor. SSRF and RBAC protections are genuinely preserved. Two items should be addressed before merge (deny-path tests; a carried-over OAuth failure path); the rest are minor.
Findings
gateway_service.py— OAuth client-credentials token-fetch failure silently proceeds unauthenticated (carried over from original)
In the client-credentials branch, if oauth_manager.get_access_token() raises, the except sets response_body = {"error": ...} but does not return. Execution falls through, the outbound request is sent without the Authorization header, and that response_body is immediately overwritten by response.json().
- Failure scenario: A gateway with
auth_type="oauth"/ client-credentials whose token endpoint is down → the caller gets whatever the unauthenticated upstream returns (often a misleading 200/401 from the target) instead of a clear token-retrieval error. - Contrast the authorization-code branch just above, which correctly returns a 500.
- Pre-existing behavior copied verbatim — not introduced here — but since the code is freshly landing in
gateway_service.py, it's a good moment to add the missing earlyreturn(or drop the dead assignment).
test_mcp_servers_router.py— no deny-path regression tests (violates a stated security invariant)
"Security-sensitive changes must include deny-path regression tests (unauthenticated, wrong team, insufficient permissions, feature disabled)."
- The new test file applies an autouse
rbac_bypassfixture to every test, and there is not a single assertion that an unauthenticated or insufficient-permission caller is rejected (grep for401/403/insufficient/PermissionError→ nothing). - SSRF-blocked (400) is well covered, but the RBAC enforcement this PR adds is never actually exercised - the only test that could catch a regression bypasses the mechanism under test.
- Ask: add at least one 403 case that does not apply the bypass.
mcp_servers_router.py:406— empty-team_idbehavior diverges from the admin route (minor)
_validated_team_id guards with if team_id is None, so ?team_id= (empty string) reaches uuid.UUID("") and raises HTTP 400. The admin route's _normalize_team_id uses if not team_id: return None, treating empty string as "no filter." Same query string, different behavior across the two endpoints. The new test locks in the stricter behavior — arguably fine, but worth a deliberate decision given the PR's parity goal.
- Client-supplied
team_idis used unverified (low / pre-existing parity)
team_id comes from an unauthenticated query param and filters the gateway/allowlist query without verifying the caller's team membership. In gateway_test_allow_registered_only mode a caller could widen the test-allowlist using another team's registered gateway URLs. This mirrors the existing admin endpoint exactly, so it's not a regression — but since it's a fresh public v1 surface, confirm that team_id here is only ever a visibility filter, never an authorization decision (per the "never trust client-provided team_id" invariant).
|
Thanks for the review @marekdano . I have addressed the valid finding.
|
Extract the gateway connectivity test logic from admin_test_gateway into a standalone test_gateway_connectivity() function in gateway_service.py, then expose it via both the legacy /admin/gateways/test route (unchanged) and the new /v1/mcp-servers/test REST endpoint. This lets the React UI call POST /v1/mcp-servers/test without needing the HTMX-only admin route. - Add test_gateway_connectivity() to gateway_service.py (shared core) - Refactor admin_test_gateway to delegate to the shared function - Create mcpgateway/routers/mcp_servers_router.py with POST /v1/mcp-servers/test - Register new router in main.py via app.include_router() - Add /mcp-servers/test permission pattern to token_scoping.py - Update test patch targets from mcpgateway.admin.* to mcpgateway.services.gateway_service.* - Add 6 unit tests for the new router Closes #5326 Signed-off-by: Lang-Akshay <akshay.shinde26@ibm.com>
…tion and metrics files Signed-off-by: Lang-Akshay <akshay.shinde26@ibm.com>
… handling Signed-off-by: Lang-Akshay <akshay.shinde26@ibm.com>
…and enhance tests for permission handling Signed-off-by: Lang-Akshay <akshay.shinde26@ibm.com>
…lter Signed-off-by: Lang-Akshay <akshay.shinde26@ibm.com>
5e9510a to
1943aaf
Compare
marekdano
left a comment
There was a problem hiding this comment.
The PR looks good!
LGTM 🚀
gandhipratik203
left a comment
There was a problem hiding this comment.
Nice cleanup, nothing broke in the SSRF/OAuth handling, and the deny-path tests hold up. LGTM.
E2E VerificationI ran this against a real, running instance of the gateway — not mocks — using a new script, Config: Note: Results (all real HTTP round-trips):
Also ran the existing suites, all green: Live-confirmed both of the behavior claims called out in the PR description:
Code ReviewScope: only files touched by Blocking
Suggestions
Other review notes (no action needed)
VerdictE2E passed across all 9 real-HTTP checks plus the full existing test suite, and this PR's two headline claims (OAuth fails closed, v1 cross-team hardening) are verified live and accurate as far as they go. But E2E check #6 also live-confirms a real, reachable authorization gap: the same cross-team probing risk this PR fixes for the new route is left open on |
msureshkumar88
left a comment
There was a problem hiding this comment.
E2E-verified this live end-to-end (real server, real HTTP, real JWTs) — the two headline claims (OAuth fails closed, v1 cross-team hardening) both check out. But the same live testing also confirms /admin/gateways/test still lacks the cross-team team_id check the new v1 route adds, even though both now share the identical underlying test_gateway_connectivity() function — a non-admin token scoped to team A can use the admin route to test connectivity against team B's registered-gateway allowlist, which is exactly the enumeration risk the new router's own code comment calls out. Requesting changes to close that gap (or get an explicit decision that it's out of scope) before merge. Full details, plus a few non-blocking suggestions, in my comment above.
…ent cross-team access Signed-off-by: Lang-Akshay <akshay.shinde26@ibm.com>
90125a8
|
Thanks for the review @msureshkumar88
|
Closes #5326
The React UI cannot reach `/admin/**` endpoints, which are reserved for the legacy HTMX admin interface. This PR exposes a dedicated v1 REST endpoint so the React "Test Connection" dialog (#5040) can test MCP server / gateway connectivity without touching admin routes.
Rather than duplicating the gateway-test handler, the core logic is extracted into a standalone `test_gateway_connectivity()` function in `gateway_service.py`. Both the existing `/admin/gateways/test` endpoint and the new `/v1/mcp-servers/test` endpoint become thin delegation wrappers around this single source of truth.
Changes
New files
Modified files
Security hardening (beyond straight extraction)
Minor cleanups