UAT: MCPRefreshHook only responds to push notifications — spec-required periodic polling for MCP tool changes not implemented #2142

Open
opened 2026-04-03 04:24:13 +00:00 by freemo · 1 comment
Owner

Metadata

  • Branch: fix/mcp-refresh-hook-periodic-polling
  • Commit Message: fix(mcp): implement periodic polling in MCPRefreshHook for MCP servers without push notification support
  • Milestone: v3.7.0
  • Parent Epic: #392

Background

The specification states that MCP refresh should support "Automatic refresh: Periodically, if configured, to poll for changes on the MCP server." However, MCPRefreshHook only responds to notifications/tools/list_changed push events from the MCP server. There is no periodic polling mechanism implemented.

What was tested: Code-level analysis of src/cleveragents/mcp/refresh_hook.py and src/cleveragents/mcp/client.py.

Bug Description

Expected Behavior (from spec)

The MCP refresh system should support periodic polling for tool changes, configurable by the user. When the tool list changes (detected by polling), SkillRegistry.refresh_all() should be triggered.

Actual Behavior

  • MCPRefreshHook only listens for notifications/tools/list_changed push events (line 86 in refresh_hook.py) — no periodic polling is implemented.
  • McpClient performs periodic health checks via _check_health() which calls discover_tools() (lines 427–441 in client.py), but when the tool list changes, it does not trigger SkillRegistry.refresh_all().
  • There is no poll_interval or similar configuration parameter on MCPRefreshHook or McpClientConfig.

Code Locations

  • src/cleveragents/mcp/refresh_hook.py — only handles push notifications, no polling logic
  • src/cleveragents/mcp/client.py lines 427–441 — _check_health() calls discover_tools() but does not detect or propagate tool list changes to SkillRegistry

Impact

MCP servers that do not support push notifications (i.e., do not send notifications/tools/list_changed) will never have their tool list refreshed in the SkillRegistry, even if their tool list changes. This affects all stdio and sse MCP servers that don't implement the notifications extension.

Severity: Medium — affects MCP servers without push notification support.

Subtasks

  • Add poll_interval: float | None configuration field to McpClientConfig (default None = disabled)
  • Add poll_interval: float | None parameter to MCPRefreshHook.__init__() to enable periodic polling mode
  • Implement a background polling loop in MCPRefreshHook that calls discover_tools() at the configured interval
  • Detect tool list changes in the polling loop by comparing the current tool list against the previously known list (e.g., by name/signature hash)
  • Trigger SkillRegistry.refresh_all() when a change is detected via polling (mirroring the push notification path)
  • Wire poll_interval from McpClientConfig into MCPRefreshHook during McpClient initialisation
  • Ensure the polling loop is cancelled cleanly on MCPRefreshHook.stop() / client shutdown
  • Add Behave BDD unit tests covering: polling disabled (default), polling enabled with no change, polling enabled with tool list change triggering refresh_all()
  • Add Robot Framework integration test verifying end-to-end refresh via polling for a mock MCP server without push notification support
  • Update docs/api/mcp.md to document the new poll_interval parameter on MCPRefreshHook and McpClientConfig

Definition of Done

  • MCPRefreshHook supports a configurable poll_interval that, when set, periodically polls the MCP server for tool list changes
  • When a tool list change is detected via polling, SkillRegistry.refresh_all() is triggered
  • MCP servers without push notification support (stdio, sse without notifications extension) are correctly refreshed when poll_interval is configured
  • The polling loop is started and stopped cleanly with the MCPRefreshHook lifecycle
  • McpClientConfig exposes poll_interval and wires it through to MCPRefreshHook
  • All new code is fully statically typed and passes nox -e typecheck (Pyright) with no suppressions
  • All nox stages pass (nox -e lint, nox -e typecheck, nox -e unit_tests, nox -e integration_tests)
  • Coverage >= 97%

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-new-issue-creator

## Metadata - **Branch**: `fix/mcp-refresh-hook-periodic-polling` - **Commit Message**: `fix(mcp): implement periodic polling in MCPRefreshHook for MCP servers without push notification support` - **Milestone**: v3.7.0 - **Parent Epic**: #392 ## Background The specification states that MCP refresh should support **"Automatic refresh: Periodically, if configured, to poll for changes on the MCP server."** However, `MCPRefreshHook` only responds to `notifications/tools/list_changed` push events from the MCP server. There is no periodic polling mechanism implemented. **What was tested**: Code-level analysis of `src/cleveragents/mcp/refresh_hook.py` and `src/cleveragents/mcp/client.py`. ## Bug Description ### Expected Behavior (from spec) The MCP refresh system should support periodic polling for tool changes, configurable by the user. When the tool list changes (detected by polling), `SkillRegistry.refresh_all()` should be triggered. ### Actual Behavior - `MCPRefreshHook` only listens for `notifications/tools/list_changed` push events (line 86 in `refresh_hook.py`) — no periodic polling is implemented. - `McpClient` performs periodic health checks via `_check_health()` which calls `discover_tools()` (lines 427–441 in `client.py`), but when the tool list changes, it does **not** trigger `SkillRegistry.refresh_all()`. - There is no `poll_interval` or similar configuration parameter on `MCPRefreshHook` or `McpClientConfig`. ### Code Locations - `src/cleveragents/mcp/refresh_hook.py` — only handles push notifications, no polling logic - `src/cleveragents/mcp/client.py` lines 427–441 — `_check_health()` calls `discover_tools()` but does not detect or propagate tool list changes to `SkillRegistry` ### Impact MCP servers that do not support push notifications (i.e., do not send `notifications/tools/list_changed`) will never have their tool list refreshed in the `SkillRegistry`, even if their tool list changes. This affects all `stdio` and `sse` MCP servers that don't implement the notifications extension. **Severity**: Medium — affects MCP servers without push notification support. ## Subtasks - [ ] Add `poll_interval: float | None` configuration field to `McpClientConfig` (default `None` = disabled) - [ ] Add `poll_interval: float | None` parameter to `MCPRefreshHook.__init__()` to enable periodic polling mode - [ ] Implement a background polling loop in `MCPRefreshHook` that calls `discover_tools()` at the configured interval - [ ] Detect tool list changes in the polling loop by comparing the current tool list against the previously known list (e.g., by name/signature hash) - [ ] Trigger `SkillRegistry.refresh_all()` when a change is detected via polling (mirroring the push notification path) - [ ] Wire `poll_interval` from `McpClientConfig` into `MCPRefreshHook` during `McpClient` initialisation - [ ] Ensure the polling loop is cancelled cleanly on `MCPRefreshHook.stop()` / client shutdown - [ ] Add Behave BDD unit tests covering: polling disabled (default), polling enabled with no change, polling enabled with tool list change triggering `refresh_all()` - [ ] Add Robot Framework integration test verifying end-to-end refresh via polling for a mock MCP server without push notification support - [ ] Update `docs/api/mcp.md` to document the new `poll_interval` parameter on `MCPRefreshHook` and `McpClientConfig` ## Definition of Done - [ ] `MCPRefreshHook` supports a configurable `poll_interval` that, when set, periodically polls the MCP server for tool list changes - [ ] When a tool list change is detected via polling, `SkillRegistry.refresh_all()` is triggered - [ ] MCP servers without push notification support (stdio, sse without notifications extension) are correctly refreshed when `poll_interval` is configured - [ ] The polling loop is started and stopped cleanly with the `MCPRefreshHook` lifecycle - [ ] `McpClientConfig` exposes `poll_interval` and wires it through to `MCPRefreshHook` - [ ] All new code is fully statically typed and passes `nox -e typecheck` (Pyright) with no suppressions - [ ] All nox stages pass (`nox -e lint`, `nox -e typecheck`, `nox -e unit_tests`, `nox -e integration_tests`) - [ ] Coverage >= 97% --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-new-issue-creator
freemo added this to the v3.7.0 milestone 2026-04-03 04:24:17 +00:00
freemo self-assigned this 2026-04-03 16:58:02 +00:00
Author
Owner

MoSCoW classification: Should Have

Rationale: This issue addresses an important spec requirement or quality improvement. The project should include this fix but it is not strictly essential for the milestone.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: ca-project-owner

MoSCoW classification: **Should Have** Rationale: This issue addresses an important spec requirement or quality improvement. The project should include this fix but it is not strictly essential for the milestone. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: ca-project-owner
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Blocks
#392 Epic: Actor YAML & Compiler
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core#2142
No description provided.