Repository navigation
Conversation
590b2a8 to
7ce4a84
Compare
msureshkumar88
left a comment
There was a problem hiding this comment.
The direction is right. The router stays mounted, a dependency returns one typed 404 after authentication, and /auth/email/me exposes the flag. No Alembic migration is needed. Unauthenticated callers still get 401. I request changes because the PR does not yet cover every A2A route, as the description and issue criterion 1 require.
Blocking
blocking | mcpgateway/admin.py:16290 | GET /admin/a2a/{agent_id} has no disabled guard. When A2A is off, a2a_service is None. The call to a2a_service.get_agent(...) raises AttributeError, the handler re-raises it, and the client gets a 500. | Add if a2a_service is None: raise HTTPException(status_code=404, detail=dict(A2A_DISABLED_DETAIL)).
blocking | mcpgateway/admin.py:11239, 11453, 11513 | /admin/a2a/partial, /admin/a2a/ids, and /admin/a2a/search do not change. /ids and /search read the database and return 200 when A2A is off. /partial calls a2a_service.convert_agent_to_read and fails with a 500 when agents remain in the database. | Apply the same guard, or add one dependency for all /admin/a2a* routes. Add a test for each route.
Functionally impacting
functionally-impacting | tests/live_gateway/a2a/ | The PR has no black-box test against a running gateway. The repo requires one. The existing live tests run with A2A enabled. | Add a live test with MCPGATEWAY_A2A_ENABLED=false. Cover /v1/a2a, /v1/a2a/{id}, invoke, the admin routes, POST /metrics/reset?entity=a2a, and /me.features.
functionally-impacting | docs/docs/using/agents/a2a.md:26, docs/docs/manage/api-usage.md:1781 | The docs do not describe the a2a_disabled contract or features on /me. This change breaks consumers, so the docs must state the new contract. | Document both. Add the old-to-new status table from the PR description.
functionally-impacting | mcpgateway/main.py:3636, mcpgateway/admin.py:16374, 16421, 16952, mcpgateway/routers/email_auth.py:572 | Three predicates decide "disabled": a2a_service is None, not a2a_service or not settings.mcpgateway_a2a_enabled, and settings.mcpgateway_a2a_enabled. admin.py:883 also keeps a separate a2a_service. They agree in production and diverge when a test patches one of them. | Add one a2a_enabled() helper in a2a_service.py and use it in all of these places.
functionally-impacting | mcpgateway/main.py:4962, 5026, 5102, 5159, 5214, 5276, 5425, 5735, 5787, 5965 | Ten 503 "A2A service not available" branches are now unreachable. The PR description says nine. They contradict the new contract and have no coverage, because the updated tests hit the dependency first. | Delete them, or raise A2A_DISABLED_DETAIL with a 404.
functionally-impacting | mcpgateway/api/v1/__init__.py:134 | The router is always mounted, so OpenAPI lists every A2A operation on a deployment where all of them return 404. | Use include_router(a2a_router, include_in_schema=settings.mcpgateway_a2a_enabled). Apply the same change to the legacy /a2a mount.
functionally-impacting | mcpgateway/routers/email_auth.py:554, mcpgateway/middleware/token_scoping.py:1320 | Issue criterion 2 requires that any authenticated non-admin can read the flag in one call. /auth/email/me is not in the token-scoping skip_paths. I did not confirm that team-scoped API tokens and SSO sessions can reach it. | Add a test for a team-scoped token calling /me. If the call is blocked, fix the scoping so the criterion holds.
Suggestions and minor
suggestion | mcpgateway/schemas.py:7121 | features: Dict[str, bool] is open-ended. Every flag added later becomes visible to every authenticated user. | Use a typed model or an explicit allowlist.
suggestion | mcpgateway/main.py:3623 | The dependency resolves get_current_user_with_permissions again. FastAPI deduplicates it only when the handlers use the same dependency. | Assert the call count in a test.
minor | mcpgateway/main.py:12591 | The /metrics/reset 400 message changes from "A2A features are disabled" to "A2A metrics are disabled". The CHANGELOG says only "still returns 400". | Mention the new message.
minor | mcpgateway/admin.py:16422 | The dependency denial logs nothing. Only the admin create path keeps its warning. | Log denials at one level, preferably in the shared helper.
Tests
The /me test covers the flag in both states. The new tests cover the main.py routes and the three changed admin handlers. These gaps remain:
- the admin routes in the blocking findings
- a wrong-permission caller and a non-admin on the admin routes
- 401 for each route family
- OpenAPI behavior
/mefor scoped and SSO tokens
The disabled-mode test depends on importlib.reload of the app and can leak global state between tests.
Breaking change
The PR flags the status changes in the description and the CHANGELOG. To reduce the impact, mark the CHANGELOG entry as breaking. For POST /admin/a2a and POST /admin/a2a/{id}/test, consider keeping success: false in the 404 body for one release, so existing parsers keep working.
I reviewed the diff and the surrounding code. I did not run the test suite or a live gateway.
674577e to
64a0571
Compare
|
@msureshkumar88 - Thanks for the review. I pushed fixes for every finding except one, which I deferred with a reason. Blocking
Functionally impacting
Suggestions and minor
Deferred
Added live tests for the remaining gaps:
|
15a1dba to
8560798
Compare
msureshkumar88
left a comment
There was a problem hiding this comment.
Thanks for the thorough follow-up. I checked each finding from my previous review against 856079828. The fixes are in place:
GET /admin/a2a/{agent_id},/partial,/ids, and/searchreturn the typed 404.a2a_enabled()andraise_if_a2a_disabled()replace the separate predicates and log each denial.- The ten unreachable 503 branches are gone.
- OpenAPI omits the A2A routes when A2A is off.
- Token scoping allows
GET /auth/email/mefor any token. The path is matched after the/v1prefix is stripped. featuresis a typedFeatureFlagsmodel.- The CHANGELOG entry is marked breaking and states the new reset message.
- The docs and the live test
tests/live_gateway/a2a/test_a2a_disabled_e2e.pyare added.
The deferral of success: false is fine with me.
CI is green. Locally, the nine changed unit test files pass (3195 passed, 1 skipped). I did not run the live test.
I request changes for one remaining route.
Blocking
blocking | mcpgateway/admin.py:16964 | POST /admin/a2a/{agent_id}/edit has no disabled guard. When A2A is off, a2a_service is None. The call to a2a_service.update_agent(...) raises AttributeError, the generic handler catches it, and the client gets a 500. The CHANGELOG and the PR description say that this route still redirects to the A2A tab. | Add raise_if_a2a_disabled() at the top of the handler. The route returns JSON, so the typed 404 fits better than a redirect. Correct the CHANGELOG sentence and add a unit test.
I reproduced the failure with a unit-level call to admin_edit_a2a_agent with A2A disabled and a valid form:
AttributeError: 'NoneType' object has no attribute 'update_agent'
500 {"message":"An unexpected error occurred. Please try again or contact support.","success":false}
This gap existed before this PR, and my first review missed it. It still blocks issue #6698 criterion 1 (every A2A route), and the CHANGELOG states the wrong behavior.
Suggestions and minor
suggestion | mcpgateway/admin.py:18000, 18060, 18137 | The /admin/a2a/plugin-bindings* routes have no disabled guard. They do not use a2a_service, so they probably return 200 when A2A is off. I did not test this. | Guard them, or list them as exempt in the CHANGELOG.
minor | mcpgateway/admin.py:4606, mcpgateway/main.py:12629, 12661 | These sites still use a2a_service and settings... instead of a2a_enabled(). They behave correctly. | Use the helper for consistency.
…s on /me
Mount the A2A router in all cases. A router dependency returns 404
with detail.error "a2a_disabled" when A2A is disabled. The
dependency runs after authentication, so only authenticated callers
learn the flag.
The admin list, create and test routes and POST /metrics/reset for
entity a2a return the same response. Before, they returned an empty
page, 403, 403 and 400. The admin state, edit and delete form routes
still redirect to the A2A tab.
GET /auth/email/me adds features {"a2a": <bool>}. Other endpoints
that return EmailUserResponse do not change.
Signed-off-by: Marek Dano <mk.dano@gmail.com>
The A2A router is always mounted and answers with a typed 404 when A2A is disabled. Authentication runs before the flag check. Expect 404 with detail.error "a2a_disabled" instead of 503 when the service is unavailable. In the feature-flag test, override the auth dependency on the reloaded app and assert 401 for an unauthenticated caller and 404 for an authenticated caller. Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Marek Dano <mk.dano@gmail.com>
Guard the admin A2A read routes with the typed a2a_disabled 404. Add a2a_enabled() and raise_if_a2a_disabled() as the single disabled check. Delete the unreachable 503 branches in the A2A handlers. Hide A2A operations from OpenAPI when A2A is disabled. Type the /auth/email/me features field as FeatureFlags. Let any token read GET /auth/email/me in token scoping. Document the a2a_disabled contract and mark the CHANGELOG entry breaking. Add unit tests and a live gateway test for disabled mode. Drop importlib.reload from the disabled-mode passthrough test. Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Marek Dano <mk.dano@gmail.com>
…disabled Signed-off-by: Marek Dano <mk.dano@gmail.com>
8560798 to
2994579
Compare
Closes #6698
Summary
A client had no unprivileged way to tell whether A2A is enabled. When A2A is disabled, each route answered differently.
This PR gives every A2A route one disabled response. It also exposes the enabled flag to any authenticated user.
GET /auth/email/mereturnsfeatures: {"a2a": <bool>}. It uses a newCurrentUserResponsethat extendsEmailUserResponse. Other endpoints that returnEmailUserResponsedo not change.Disabled contract
Every A2A route returns 404 with this body:
Clients branch on
detail.error.Before this change,
/a2a/*and/v1/a2a/*returned a generic{"detail": "Not Found"}because the router was not mounted. The 503 branches in the handlers never ran./a2a/*,/v1/a2a/*GET /admin/a2aPOST /admin/a2asuccess: falsePOST /admin/a2a/{agent_id}/testsuccess: falsePOST /metrics/reset, entitya2aThe admin state, edit, and delete form routes still redirect to the A2A tab. They are HTML form flows, and the tab is hidden when A2A is off.
POST /metrics/resetfor entitya2astill returns 400 when A2A is on andMCPGATEWAY_A2A_METRICS_ENABLEDisfalse.Consumer impact
detail.error.GET /admin/a2awhen A2A is off now receive a 404.main.pyhandlers stay as a defensive fallback. They cannot run when A2A is disabled.Manual test against a running gateway
Create two tokens:
The admin rows need
MCPGATEWAY_ADMIN_API_ENABLED=trueandADMIN_TOKEN. The admin router rejects other tokens with 403 before the A2A check runs.A2A disabled
Start the gateway with
MCPGATEWAY_A2A_ENABLED=false make dev.curl -s -i localhost:8000/v1/a2a -H "Authorization: Bearer $TOKEN"detail.errorisa2a_disabledcurl -s -i localhost:8000/v1/a2a/any-id -H "Authorization: Bearer $TOKEN"curl -s -i -X POST localhost:8000/v1/a2a/any-name/invoke -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'curl -s -i localhost:8000/v1/a2acurl -s -i localhost:8000/v1/admin/a2a -H "Authorization: Bearer $ADMIN_TOKEN"curl -s -i -X POST localhost:8000/v1/admin/a2a -H "Authorization: Bearer $ADMIN_TOKEN"curl -s -i -X POST localhost:8000/v1/admin/a2a/any-id/test -H "Authorization: Bearer $ADMIN_TOKEN"curl -s -i localhost:8000/v1/admin/a2a -H "Authorization: Bearer $TOKEN"Admin privileges requiredcurl -s -i -X POST "localhost:8000/v1/metrics/reset?entity=a2a" -H "Authorization: Bearer $ADMIN_TOKEN"curl -s localhost:8000/v1/auth/email/me -H "Authorization: Bearer $TOKEN"featuresis{"a2a": false}A2A enabled
Start the gateway with
make dev.curl -s localhost:8000/v1/auth/email/me -H "Authorization: Bearer $TOKEN"featuresis{"a2a": true}curl -s localhost:8000/v1/a2a -H "Authorization: Bearer $TOKEN"curl -s -i -X POST "localhost:8000/v1/metrics/reset?entity=a2a" -H "Authorization: Bearer $ADMIN_TOKEN"status: successMCPGATEWAY_A2A_ENABLED=true MCPGATEWAY_A2A_METRICS_ENABLED=false make dev, thencurl -s -i -X POST "localhost:8000/v1/metrics/reset?entity=a2a" -H "Authorization: Bearer $ADMIN_TOKEN"A2A metrics are disabled, not the typed 404Result from manual testing