refactor: add mcp source client facade

This commit is contained in:
lda
2026-06-07 17:34:39 +07:00 Verified
parent f6fa12a011
commit c3de026d46
9 changed files with 386 additions and 65 deletions
+26 -56
View File
@@ -1,86 +1,65 @@
from __future__ import annotations
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import Any
from mcp import ClientResult
from mcp.types import (
ClientNotification,
ClientRequest,
ListPromptsResult,
ListResourcesResult,
ListToolsResult,
)
from pydantic import AnyUrl
from wf_sources_mcp.auth import AuthRecord
from wf_sources_mcp.catalog import DiscoveredPrompt, DiscoveredResource, DiscoveredTool
from wf_sources_mcp.client import open_mcp_session
from wf_sources_mcp.client import McpSourceClient, open_mcp_session
from wf_sources_mcp.connections import McpSourceConnection
from .converters import (
prompt_to_discovered,
resource_to_discovered,
tool_result_to_call_result,
tool_to_discovered,
)
from .protocols import BackendAdapter, ToolCallResult
class McpSdkAdapter(BackendAdapter):
"""One-shot MCP client adapter for upstream MCP source operations.
This adapter intentionally opens a fresh SDK session per operation. Stateful
workflow tool execution is handled by `wf_sources_mcp.runtime`; discovery
and admin operations use this simpler one-shot path.
This adapter intentionally opens a fresh SDK session per operation. It
delegates all MCP operation/conversion details to `McpSourceClient` so the
same operation facade can later be used inside persistent runtime owners.
"""
@asynccontextmanager
async def _session(
async def _client(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
):
) -> AsyncIterator[McpSourceClient]:
async with open_mcp_session(connection, auth) as session:
yield session
yield McpSourceClient(session=session, connection=connection)
async def list_tools(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
) -> list[DiscoveredTool]:
async with self._session(connection, auth) as session:
result: ListToolsResult = await session.list_tools()
return [tool_to_discovered(tool) for tool in result.tools]
async with self._client(connection, auth) as client:
return await client.list_tools()
async def list_resources(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
) -> list[DiscoveredResource]:
async with self._session(connection, auth) as session:
result: ListResourcesResult = await session.list_resources()
return [resource_to_discovered(resource) for resource in result.resources]
async with self._client(connection, auth) as client:
return await client.list_resources()
async def list_prompts(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
) -> list[DiscoveredPrompt]:
async with self._session(connection, auth) as session:
result: ListPromptsResult = await session.list_prompts()
return [prompt_to_discovered(prompt) for prompt in result.prompts]
async with self._client(connection, auth) as client:
return await client.list_prompts()
async def get_connection_metadata(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
) -> dict[str, Any]:
transport = connection.transport
return {
"server": connection.provider,
"transport": transport.kind if transport is not None else None,
}
async with self._client(connection, auth) as client:
return await client.get_connection_metadata()
async def read_resource(
self,
@@ -88,9 +67,8 @@ class McpSdkAdapter(BackendAdapter):
auth: AuthRecord | None,
uri: str,
) -> dict[str, Any]:
async with self._session(connection, auth) as session:
result = await session.read_resource(AnyUrl(uri))
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
async with self._client(connection, auth) as client:
return await client.read_resource(uri)
async def get_prompt(
self,
@@ -99,9 +77,8 @@ class McpSdkAdapter(BackendAdapter):
prompt_name: str,
arguments: dict[str, str] | None = None,
) -> dict[str, Any]:
async with self._session(connection, auth) as session:
result = await session.get_prompt(prompt_name, arguments)
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
async with self._client(connection, auth) as client:
return await client.get_prompt(prompt_name, arguments)
async def invoke_method(
self,
@@ -110,12 +87,8 @@ class McpSdkAdapter(BackendAdapter):
method: str,
params: dict[str, Any] | None = None,
) -> dict[str, Any]:
async with self._session(connection, auth) as session:
result = await session.send_request(
ClientRequest.model_validate({"method": method, "params": params}),
ClientResult,
)
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
async with self._client(connection, auth) as client:
return await client.invoke_method(method, params)
async def send_notification(
self,
@@ -124,10 +97,8 @@ class McpSdkAdapter(BackendAdapter):
method: str,
params: dict[str, Any] | None = None,
) -> None:
async with self._session(connection, auth) as session:
await session.send_notification(
ClientNotification.model_validate({"method": method, "params": params})
)
async with self._client(connection, auth) as client:
await client.send_notification(method, params)
async def call_tool(
self,
@@ -136,9 +107,8 @@ class McpSdkAdapter(BackendAdapter):
tool_name: str,
payload: dict[str, Any],
) -> ToolCallResult:
async with self._session(connection, auth) as session:
result = await session.call_tool(tool_name, payload)
return tool_result_to_call_result(result)
async with self._client(connection, auth) as client:
return await client.call_tool(tool_name, payload)
__all__ = ["McpSdkAdapter"]