feat: prefer stateful runtime for mcp content access

This commit is contained in:
lda
2026-06-07 19:49:07 +07:00 Verified
parent 76ac0432a0
commit 8bb2f32d9a
14 changed files with 252 additions and 14 deletions
+6 -1
View File
@@ -168,6 +168,10 @@ def build_service_from_config(config: BrokerConfig) -> WfMcpService:
# focused services receive role-specific stores.
auth_store = FileAuthStore(store_roots.auth_root)
catalog_store = FileCatalogStore(store_roots.catalog_cache_root)
# Discovery can use short-lived SDK sessions. Workflow execution needs
# a persistent runtime so stateful MCP servers keep session/page state
# across sequential workflow nodes.
runtime_pool = McpRuntimePool(runtime_factory.create)
service = WfMcpService(
store=FileStore(store_roots.auth_root),
auth_store=auth_store,
@@ -178,7 +182,8 @@ def build_service_from_config(config: BrokerConfig) -> WfMcpService:
# Discovery can use short-lived SDK sessions. Workflow execution needs
# a persistent runtime so stateful MCP servers keep session/page state
# across sequential workflow nodes.
tool_executor=McpRuntimePool(runtime_factory.create),
tool_executor=runtime_pool,
stateful_runtime=runtime_pool,
)
source_registry_store = FileSourceRegistryStore(store_roots.source_registry_root)
service.sync_connections_from_config(
+3 -1
View File
@@ -26,7 +26,7 @@ from wf_sources_mcp.catalog import (
CatalogPromptEntry,
CatalogResourceEntry,
)
from wf_sources_mcp.sdk import BackendAdapter, ToolExecutor
from wf_sources_mcp.sdk import BackendAdapter, StatefulMcpRuntime, ToolExecutor
from wf_sources_mcp.source_registry import SourceRegistryStore
from wf_sources_mcp.storage import AuthStore, CatalogStore, Store
@@ -68,6 +68,7 @@ class WfMcpService:
draft_workspace_store: DraftWorkspaceStore | None = None
run_store: RunStore | None = None
tool_executor: ToolExecutor | None = None
stateful_runtime: StatefulMcpRuntime | None = None
events: BrokerEventRecorder = field(init=False)
connection_service: ConnectionService = field(init=False)
upstream: UpstreamTransportService = field(init=False)
@@ -91,6 +92,7 @@ class WfMcpService:
catalog_store=catalog_store,
event_sink=self.events.record_event,
tool_executor=self.tool_executor,
stateful_runtime=self.stateful_runtime,
)
self.source_catalog = SourceCatalogService(
store=catalog_store,
@@ -28,10 +28,9 @@ from wf_mcp.shared.errors import error_payload
from wf_sources_mcp.auth import AuthRecord, connection_auth_diagnostic
from wf_sources_mcp.catalog.models import CatalogSnapshot
from wf_sources_mcp.connections import (
McpSourceConnection,
mcp_source_connection_from_connection_config,
)
from wf_sources_mcp.sdk import BackendAdapter, ToolExecutor
from wf_sources_mcp.sdk import BackendAdapter, StatefulMcpRuntime, ToolExecutor
from wf_sources_mcp.storage import AuthStore, CatalogStore
from .adapters import require_adapter
@@ -53,6 +52,7 @@ class UpstreamTransportService:
event_sink: EventSink
adapters: dict[str, BackendAdapter] = field(default_factory=dict)
tool_executor: ToolExecutor | None = None
stateful_runtime: StatefulMcpRuntime | None = None
def register_adapter(self, server: str, adapter: BackendAdapter) -> None:
self.adapters[server] = adapter
@@ -103,7 +103,6 @@ class UpstreamTransportService:
qualified_name: str,
uri: str,
) -> dict[str, Any]:
adapter = require_adapter(connection, self.adapters)
auth = self.load_connection_auth(connection)
# Compatibility boundary: broker callers still pass ConnectionConfig.
source_connection = mcp_source_connection_from_connection_config(connection)
@@ -115,7 +114,15 @@ class UpstreamTransportService:
payload={"uri": uri},
)
)
result = await adapter.read_resource(source_connection, auth, uri)
if self.stateful_runtime is not None:
result = await self.stateful_runtime.read_resource(
source_connection,
auth,
uri,
)
else:
adapter = require_adapter(connection, self.adapters)
result = await adapter.read_resource(source_connection, auth, uri)
self.event_sink(
make_event(
"resource_read_completed",
@@ -133,7 +140,6 @@ class UpstreamTransportService:
local_name: str,
arguments: dict[str, str] | None = None,
) -> dict[str, Any]:
adapter = require_adapter(connection, self.adapters)
auth = self.load_connection_auth(connection)
# Compatibility boundary: broker callers still pass ConnectionConfig.
source_connection = mcp_source_connection_from_connection_config(connection)
@@ -145,7 +151,16 @@ class UpstreamTransportService:
payload={"argument_keys": sorted((arguments or {}).keys())},
)
)
result = await adapter.get_prompt(source_connection, auth, local_name, arguments)
if self.stateful_runtime is not None:
result = await self.stateful_runtime.get_prompt(
source_connection,
auth,
local_name,
arguments,
)
else:
adapter = require_adapter(connection, self.adapters)
result = await adapter.get_prompt(source_connection, auth, local_name, arguments)
self.event_sink(
make_event(
"prompt_get_completed",