Skip to content

fix(a2a): cascade agent state changes to associated MCP tools - #3173

Merged
crivetimihai merged 7 commits into
mainfrom
2997-deactive-a2a-agents-tools
Mar 27, 2026
Merged

fix(a2a): cascade agent state changes to associated MCP tools#3173
crivetimihai merged 7 commits into
mainfrom
2997-deactive-a2a-agents-tools

Conversation

@crivetimihai

Copy link
Copy Markdown
Member

Note: This PR was re-created from #3073 due to repository maintenance. Your code and branch are intact. @marekdano please verify everything looks good.

🐛 Bug-fix PR


📌 Summary

When an A2A agent is deactivated, its associated tool remains active in the database — appearing in virtual server tool listings and still invocable. This is inconsistent with gateway deactivation, which cascades to all child tools, prompts, and resources.

This PR cascades A2A agent activation/deactivation to the associated MCP tool, adds the missing "Enable/Disable A2A Agent" API documentation, and brings a2a_service.py unit test coverage to 100%.

Closes #2997

🔁 Reproduction Steps

  1. Go to http://localhost:8080/admin/?agents_inactive=true&agents_page=1#a2a-agents
  2. Set as Inactive an Agent
  3. Check Tools and Virtual Server

🐞 Root Cause

The root cause was that set_agent_state() in a2a_service.py only updated the agent's own enabled field and never touched the associated tool record. Unlike gateway_service.py:set_gateway_state(), which bulk-updates all child tools/prompts/resources when a gateway is toggled, the A2A code path simply didn't have any cascade logic — the tool's enabled column stayed unchanged regardless of the agent's state.

💡 Fix Description

deactive_a2a_agents_flow.mov

Bug fix

  • mcpgateway/services/a2a_service.py: After toggling agent.enabled in set_agent_state(), cascade the new state to the agent's associated tool via a single UPDATE statement. The WHERE DbTool.enabled != activate guard avoids redundant commits when the tool already matches the desired state. This mirrors the existing pattern in gateway_service.py:set_gateway_state().

Test fixes

  • tests/unit/mcpgateway/services/test_a2a_service.py: Added tool_id=None to two existing SimpleNamespace agent mocks (test_set_state_permission_allowed, test_set_state_with_reachable) that broke because the new cascade code accesses agent.tool_id.

New Unit Tests (12 tests, coverage 94% → 100%)

  • TestSetAgentStateToolCascade (4 tests): Deactivation cascades to tool, activation cascades to tool, no-op when tool_id is None, no commit when tool already matches state.
  • TestUpdateAgentQueryParamAuth (7 tests): Switching away from query_param clears params, switching to query_param encrypts key/value, value-only rotation reuses existing key, masked placeholder with same/different key, None value no-op, plain string value path.
  • TestListAgentsCacheAttributeError (1 test): AttributeError during cache set is silently skipped.
  • Add extra tests to get 100% coverage on a2a_service.py

Documentation

  • docs/docs/using/agents/a2a.md: New "Lifecycle Management > Agent-Tool State Cascade" section explaining the linked lifecycle between A2A agents and their tools.
  • docs/docs/manage/api-usage.md: New "Enable/Disable A2A Agent" subsection with API examples and a "State Cascade" admonition note.

🧪 Verification

Check Command Status
Lint suite make lint
Unit tests make test
Coverage ≥ 80 % make coverage
Manual regression no longer fails steps / screenshots

📐 MCP Compliance (if relevant)

  • Matches current MCP spec
  • No breaking change to MCP clients

✅ Checklist

  • Code formatted (make black isort pre-commit)
  • No secrets/credentials committed

@crivetimihai crivetimihai added this to the Release 1.0.0-GA milestone Feb 24, 2026
@crivetimihai crivetimihai added bug Something isn't working a2a Support for A2A protocol SHOULD P2: Important but not vital; high-value items that are not crucial for the immediate release labels Feb 24, 2026
@Lang-Akshay

Copy link
Copy Markdown
Collaborator

Acceptance Criteria Breakdown:

Criterion Status Evidence
Deactivating an A2A agent should hide its tool from Tools panel ✅ Done Tool cascade logic (a2a_service.py L1256-L1268) sets enabled=activate on DbTool
Deactivating an A2A agent should hide it from Virtual Servers panel ✅ Done Tool's enabled=False means it won't appear in virtual server tool listings (existing filtering)
Behavior should match MCP Server (gateway) deactivation pattern ✅ Done Pattern mirrors gateway_service.set_gateway_state() appropriately (1:1 vs 1:many relationship)
Reactivating should restore visibility ✅ Done Cascade works bidirectionally (activate=True/False)

Overall Issue Coverage: 4 of 4 requirements addressed.

Strengths

  1. Idempotent cascade: The WHERE DbTool.enabled != activate guard avoids redundant commits — well-tested via test_cascade_tool_already_matching_no_commit.
  2. Best-effort semantics: The try/except Exception around the cascade ensures an agent state-change is never blocked by a tool-update failure — appropriate for a non-critical cascade.
  3. Comprehensive test coverage: All branches covered — happy path (activate/deactivate), no tool_id, no gateway_id, already-matching state, and cascade failure.
  4. Cache invalidation: Both registry cache and tool lookup cache are invalidated, preventing stale data.

The fix is well-scoped, correctly mirrors the existing gateway cascade pattern (adapted for 1:1 relationship), is thoroughly tested with 15 new tests (100% coverage on a2a_service.py), and includes clear documentation.

Lang-Akshay
Lang-Akshay previously approved these changes Feb 25, 2026
Lang-Akshay
Lang-Akshay previously approved these changes Mar 23, 2026
msureshkumar88
msureshkumar88 previously approved these changes Mar 23, 2026

@msureshkumar88 msureshkumar88 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

✅ PR #3173 — Approved

[BUG][UI]: Inactive A2A agents still visible in Tools and Virtual Servers panels by @author

This PR successfully implements state cascade functionality where deactivating an A2A agent automatically deactivates its associated tool, ensuring inactive agents no longer appear in Tools and Virtual Servers panels. The implementation follows the same pattern as MCP server deactivation and includes comprehensive test coverage.

Security: 🟢 No issues found
Issue Coverage: Fully addresses issue #2997
Quality: Well-tested with proper cache management

The changes are production-ready and align with the expected behavior described in the issue.

@marekdano marekdano added the release-fix Critical bugfix required for the release label Mar 23, 2026
@crivetimihai crivetimihai self-assigned this Mar 26, 2026
marekdano and others added 6 commits March 26, 2026 22:06
Signed-off-by: Marek Dano <mk.dano@gmail.com>
…dating a2a agent tools

Signed-off-by: Marek Dano <mk.dano@gmail.com>
…t_agent_state' function

Signed-off-by: Marek Dano <mk.dano@gmail.com>
- Run black on test_a2a_service.py to fix line-length violations in new tests
- Update docstring Location path in test_a2a_agent.py after rename from
  test_issue_840_a2a_agent.py

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
- Update test_a2a_agent.py module docstring to reflect both #840 and
  #2997 coverage instead of stale issue-840-only wording
- Clarify a2a.md cascade docs: invocation was already rejected for
  disabled agents; the fix ensures the tool's enabled flag stays in
  sync so it no longer appears in listings

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Remove try/except around the tool cascade in set_agent_state() so that
DB failures surface to the caller. This aligns with gateway_service.py's
set_gateway_state() which also commits the parent first (line 2773) then
cascades to child tools/prompts/resources without catching exceptions.

The previous best-effort pattern silently returned success when the tool
UPDATE failed, leaving agent disabled but tool still enabled — the exact
inconsistency this PR is meant to fix.

Update test_cascade_tool_update_failure to assert the exception
propagates instead of being swallowed.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai
crivetimihai dismissed stale reviews from msureshkumar88 and Lang-Akshay via 0c132ac March 27, 2026 00:17
@crivetimihai
crivetimihai force-pushed the 2997-deactive-a2a-agents-tools branch from 6ea6721 to 0c132ac Compare March 27, 2026 00:17
@crivetimihai

Copy link
Copy Markdown
Member Author

Maintainer Review — Changes Made During Rebase

Rebased onto main (clean, no conflicts) and made the following fixes on top of the original 3 commits:

fix: apply black formatting and correct stale docstring path

  • Ran black on test_a2a_service.py — the new test code had several lines exceeding the formatter's limit.
  • Fixed test_a2a_agent.py docstring Location: path which still referenced the pre-rename filename test_issue_840_a2a_agent.py.

fix: tighten docs and stale docstring per code review feedback

fix: let cascade failures propagate instead of swallowing them

  • Removed the try/except around the tool cascade in set_agent_state() so DB failures surface to the caller. This aligns with gateway_service.py:set_gateway_state() which also commits the parent first (line 2773) then cascades to child tools/prompts/resources without catching exceptions.
  • The previous best-effort pattern silently returned success when the tool UPDATE failed, leaving the agent disabled but its tool still enabled — the exact inconsistency this PR is meant to fix.
  • Updated the failure-path test from asserting "warning logged + success returned" to asserting pytest.raises(RuntimeError).

Verification

  • All 73 unit tests pass, 99% coverage on a2a_service.py (5 uncovered lines are pre-existing).
  • E2E tested against localhost:8080 (docker-compose): created agent, deactivated via API and Admin UI, verified tool disappears from virtual server listings, reactivated, verified tool reappears. Idempotency and cleanup verified.
  • No regressions in gateways, tools, prompts, resources, or virtual servers.

@crivetimihai
crivetimihai merged commit ef8a42f into main Mar 27, 2026
25 checks passed
@crivetimihai
crivetimihai deleted the 2997-deactive-a2a-agents-tools branch March 27, 2026 08:57
brian-hussey pushed a commit that referenced this pull request Mar 27, 2026
* fix(api): deactive a2a agents tools when a2a agents are inactive

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: add tool_lookup_cache and try catch with handling errors when updating a2a agent tools

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: add gateway_id to 'tool_lookup_cache.invalidate' function in 'set_agent_state' function

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: apply black formatting and correct stale docstring path

- Run black on test_a2a_service.py to fix line-length violations in new tests
- Update docstring Location path in test_a2a_agent.py after rename from
  test_issue_840_a2a_agent.py

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: tighten docs and stale docstring per code review feedback

- Update test_a2a_agent.py module docstring to reflect both #840 and
  #2997 coverage instead of stale issue-840-only wording
- Clarify a2a.md cascade docs: invocation was already rejected for
  disabled agents; the fix ensures the tool's enabled flag stays in
  sync so it no longer appears in listings

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: let cascade failures propagate instead of swallowing them

Remove try/except around the tool cascade in set_agent_state() so that
DB failures surface to the caller. This aligns with gateway_service.py's
set_gateway_state() which also commits the parent first (line 2773) then
cascades to child tools/prompts/resources without catching exceptions.

The previous best-effort pattern silently returned success when the tool
UPDATE failed, leaving agent disabled but tool still enabled — the exact
inconsistency this PR is meant to fix.

Update test_cascade_tool_update_failure to assert the exception
propagates instead of being swallowed.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Marek Dano <mk.dano@gmail.com>
madhu-mohan-jaishankar pushed a commit that referenced this pull request Mar 27, 2026
* fix(api): deactive a2a agents tools when a2a agents are inactive

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: add tool_lookup_cache and try catch with handling errors when updating a2a agent tools

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: add gateway_id to 'tool_lookup_cache.invalidate' function in 'set_agent_state' function

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: apply black formatting and correct stale docstring path

- Run black on test_a2a_service.py to fix line-length violations in new tests
- Update docstring Location path in test_a2a_agent.py after rename from
  test_issue_840_a2a_agent.py

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: tighten docs and stale docstring per code review feedback

- Update test_a2a_agent.py module docstring to reflect both #840 and
  #2997 coverage instead of stale issue-840-only wording
- Clarify a2a.md cascade docs: invocation was already rejected for
  disabled agents; the fix ensures the tool's enabled flag stays in
  sync so it no longer appears in listings

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: let cascade failures propagate instead of swallowing them

Remove try/except around the tool cascade in set_agent_state() so that
DB failures surface to the caller. This aligns with gateway_service.py's
set_gateway_state() which also commits the parent first (line 2773) then
cascades to child tools/prompts/resources without catching exceptions.

The previous best-effort pattern silently returned success when the tool
UPDATE failed, leaving agent disabled but tool still enabled — the exact
inconsistency this PR is meant to fix.

Update test_cascade_tool_update_failure to assert the exception
propagates instead of being swallowed.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Marek Dano <mk.dano@gmail.com>
msureshkumar88 pushed a commit that referenced this pull request May 13, 2026
* fix(api): deactive a2a agents tools when a2a agents are inactive

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: add tool_lookup_cache and try catch with handling errors when updating a2a agent tools

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: add gateway_id to 'tool_lookup_cache.invalidate' function in 'set_agent_state' function

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: apply black formatting and correct stale docstring path

- Run black on test_a2a_service.py to fix line-length violations in new tests
- Update docstring Location path in test_a2a_agent.py after rename from
  test_issue_840_a2a_agent.py

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: tighten docs and stale docstring per code review feedback

- Update test_a2a_agent.py module docstring to reflect both #840 and
  #2997 coverage instead of stale issue-840-only wording
- Clarify a2a.md cascade docs: invocation was already rejected for
  disabled agents; the fix ensures the tool's enabled flag stays in
  sync so it no longer appears in listings

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: let cascade failures propagate instead of swallowing them

Remove try/except around the tool cascade in set_agent_state() so that
DB failures surface to the caller. This aligns with gateway_service.py's
set_gateway_state() which also commits the parent first (line 2773) then
cascades to child tools/prompts/resources without catching exceptions.

The previous best-effort pattern silently returned success when the tool
UPDATE failed, leaving agent disabled but tool still enabled — the exact
inconsistency this PR is meant to fix.

Update test_cascade_tool_update_failure to assert the exception
propagates instead of being swallowed.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Marek Dano <mk.dano@gmail.com>
kamath-a pushed a commit to kamath-a/mcp-context-forge that referenced this pull request Jun 26, 2026
* fix(api): deactive a2a agents tools when a2a agents are inactive

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: add tool_lookup_cache and try catch with handling errors when updating a2a agent tools

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: add gateway_id to 'tool_lookup_cache.invalidate' function in 'set_agent_state' function

Signed-off-by: Marek Dano <mk.dano@gmail.com>

* fix: apply black formatting and correct stale docstring path

- Run black on test_a2a_service.py to fix line-length violations in new tests
- Update docstring Location path in test_a2a_agent.py after rename from
  test_issue_840_a2a_agent.py

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: tighten docs and stale docstring per code review feedback

- Update test_a2a_agent.py module docstring to reflect both IBM#840 and
  IBM#2997 coverage instead of stale issue-840-only wording
- Clarify a2a.md cascade docs: invocation was already rejected for
  disabled agents; the fix ensures the tool's enabled flag stays in
  sync so it no longer appears in listings

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: let cascade failures propagate instead of swallowing them

Remove try/except around the tool cascade in set_agent_state() so that
DB failures surface to the caller. This aligns with gateway_service.py's
set_gateway_state() which also commits the parent first (line 2773) then
cascades to child tools/prompts/resources without catching exceptions.

The previous best-effort pattern silently returned success when the tool
UPDATE failed, leaving agent disabled but tool still enabled — the exact
inconsistency this PR is meant to fix.

Update test_cascade_tool_update_failure to assert the exception
propagates instead of being swallowed.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Marek Dano <mk.dano@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a2a Support for A2A protocol bug Something isn't working release-fix Critical bugfix required for the release SHOULD P2: Important but not vital; high-value items that are not crucial for the immediate release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG][UI]: Inactive A2A agents still visible in Tools and Virtual Servers panels

4 participants