Source code for ralph.mcp.tools.bridge._upstream_proxy_handler
"""UpstreamProxyHandler class."""
from __future__ import annotations
from importlib import import_module
from typing import TYPE_CHECKING, cast
if TYPE_CHECKING:
from ralph.mcp.tools.bridge._types import JsonObject
from ralph.mcp.upstream.client import HasMediaManifest
from ralph.mcp.upstream.registry import UpstreamRegistry
from ralph.workspace.protocol import Workspace
[docs]
class UpstreamProxyHandler:
"""Proxy handler that forwards tool calls to an upstream MCP registry."""
def __init__(self, alias: str, upstream_registry: UpstreamRegistry) -> None:
self._alias = alias
self._upstream_registry = upstream_registry
def __call__(
self,
host_session: object | None,
workspace: object | None,
params: JsonObject,
) -> object:
# The activity sink is intentionally NOT invoked here. The
# single emission point for every ``tools/call`` (in-process or
# upstream-proxied) is ``McpServer._handle_tools_call``, which
# calls ``self._invoke_activity_sinks(tool_name)`` before
# dispatch. Invoking the sink inside the proxy as well would
# double-count upstream-proxied calls on the ``mcp_tool``
# channel and break per-channel accounting. The upstream proxy
# is therefore a pure pass-through: it forwards the call and
# returns the result, while the server boundary records activity
# exactly once per logical call.
result = self._upstream_registry.call_tool(
self._alias,
params,
session=cast("HasMediaManifest | None", host_session),
workspace=cast("Workspace | None", workspace),
)
if workspace is not None and host_session is not None and isinstance(result, dict):
mod = import_module("ralph.mcp.tools.workspace")
mod.persist_upstream_media_artifacts(result, host_session, workspace)
return result