Background
The ContextForge gateway (IBM/mcp-context-forge) has a Rust runtime fast path (tools_rust/mcp_runtime) for tool invocation. When RetryWithBackoffPlugin is the only active tool_post_invoke hook, the gateway can execute the tool natively in Rust and apply the retry policy without round-tripping through Python.
To do that, the gateway currently has to:
- Import
RetryConfig from this package directly
- Construct it from a raw config dict
- Apply the gateway's
max_tool_retries ceiling
- Merge per-tool overrides
- Reject if
check_text_content=True (Rust runtime can't evaluate that)
- Translate the result into the wire format Rust expects
See mcpgateway/services/tool_service.py:_build_rust_native_tool_post_invoke_retry_policy.
This is a leaky abstraction — the gateway core has hardcoded knowledge of this plugin's internal config schema. We'd like to move the transformation into the plugin itself so the gateway only depends on a stable protocol method.
Proposed change
Add a to_rust_native_policy() method to RetryWithBackoffPlugin:
class RetryWithBackoffPlugin(Plugin):
def to_rust_native_policy(self, tool_name: str, ceiling: int) -> Optional[dict]:
'''Translate this plugin's effective config into the Rust runtime wire format.
Args:
tool_name: The tool being invoked, used to look up per-tool overrides.
ceiling: The gateway's hard maximum on retries (settings.max_tool_retries).
The plugin must clamp its own max_retries to this value.
Returns:
A dict matching the Rust runtime wire format, or None if this plugin
invocation is not eligible for native Rust execution (e.g. when
check_text_content=True, which requires Python text inspection).
Wire format (must be stable):
{
'kind': 'retry_with_backoff',
'maxRetries': int,
'backoffBaseMs': int,
'maxBackoffMs': int,
'retryOnStatus': list[int],
'jitter': bool,
}
'''
Reference implementation
The translation logic the gateway currently performs (which should move into this method):
effective_cfg = RetryConfig(**(self.config.config or {}))
if effective_cfg.max_retries > ceiling:
effective_cfg = effective_cfg.model_copy(update={'max_retries': ceiling})
overrides = effective_cfg.tool_overrides.get(tool_name)
if overrides:
merged_cfg = effective_cfg.model_dump()
merged_cfg.update(overrides)
merged_cfg.pop('tool_overrides', None)
effective_cfg = RetryConfig(**merged_cfg)
if effective_cfg.max_retries > ceiling:
effective_cfg = effective_cfg.model_copy(update={'max_retries': ceiling})
if effective_cfg.check_text_content:
return None # not eligible
return {
'kind': 'retry_with_backoff',
'maxRetries': int(effective_cfg.max_retries),
'backoffBaseMs': int(effective_cfg.backoff_base_ms),
'maxBackoffMs': int(effective_cfg.max_backoff_ms),
'retryOnStatus': list(effective_cfg.retry_on_status),
'jitter': bool(effective_cfg.jitter),
}
Wire format stability
The returned dict format is consumed by tools_rust/mcp_runtime in the gateway repo. Treat the keys (kind, maxRetries, backoffBaseMs, maxBackoffMs, retryOnStatus, jitter) as a stable wire contract — additions are fine, removals/renames are breaking.
Acceptance criteria
Linked gateway issue
Once this ships, the gateway will refactor to use the new method and drop its direct cpex_retry_with_backoff import:
Context
Surfaced during PR review of IBM/mcp-context-forge#3965, which migrated six in-tree plugins (including retry_with_backoff) from the gateway repo to standalone PyPI packages. The gateway currently uses a try/except ImportError guard as a tactical fix; this issue tracks the architectural cleanup.
Background
The ContextForge gateway (
IBM/mcp-context-forge) has a Rust runtime fast path (tools_rust/mcp_runtime) for tool invocation. WhenRetryWithBackoffPluginis the only activetool_post_invokehook, the gateway can execute the tool natively in Rust and apply the retry policy without round-tripping through Python.To do that, the gateway currently has to:
RetryConfigfrom this package directlymax_tool_retriesceilingcheck_text_content=True(Rust runtime can't evaluate that)See
mcpgateway/services/tool_service.py:_build_rust_native_tool_post_invoke_retry_policy.This is a leaky abstraction — the gateway core has hardcoded knowledge of this plugin's internal config schema. We'd like to move the transformation into the plugin itself so the gateway only depends on a stable protocol method.
Proposed change
Add a
to_rust_native_policy()method toRetryWithBackoffPlugin:Reference implementation
The translation logic the gateway currently performs (which should move into this method):
Wire format stability
The returned dict format is consumed by
tools_rust/mcp_runtimein the gateway repo. Treat the keys (kind,maxRetries,backoffBaseMs,maxBackoffMs,retryOnStatus,jitter) as a stable wire contract — additions are fine, removals/renames are breaking.Acceptance criteria
RetryWithBackoffPlugin.to_rust_native_policy(tool_name: str, ceiling: int) -> Optional[dict]is implemented.check_text_content=TruereturningNone.Linked gateway issue
Once this ships, the gateway will refactor to use the new method and drop its direct
cpex_retry_with_backoffimport:Context
Surfaced during PR review of IBM/mcp-context-forge#3965, which migrated six in-tree plugins (including
retry_with_backoff) from the gateway repo to standalone PyPI packages. The gateway currently uses atry/except ImportErrorguard as a tactical fix; this issue tracks the architectural cleanup.