refactor: move mcp discovery capabilities to wf_sources_mcp
This commit is contained in:
@@ -43,6 +43,7 @@ if TYPE_CHECKING:
|
||||
|
||||
__all__ = [
|
||||
"AuthRecord",
|
||||
"DiscoveredConnectionCapabilities",
|
||||
"FileSourceRegistryStore",
|
||||
"HttpSourceTransport",
|
||||
"McpSourceConnection",
|
||||
@@ -55,6 +56,7 @@ __all__ = [
|
||||
"auth_ref_for_connection",
|
||||
"connection_auth_diagnostic",
|
||||
"connection_config_to_registry_entry",
|
||||
"discover_connection_capabilities",
|
||||
"mcp_auth_env",
|
||||
"mcp_auth_from_neutral",
|
||||
"mcp_auth_headers",
|
||||
@@ -95,4 +97,11 @@ def __getattr__(name: str) -> object:
|
||||
from . import transports
|
||||
|
||||
return getattr(transports, name)
|
||||
if name in {
|
||||
"DiscoveredConnectionCapabilities",
|
||||
"discover_connection_capabilities",
|
||||
}:
|
||||
from . import discovery
|
||||
|
||||
return getattr(discovery, name)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, TypeVar
|
||||
|
||||
from mcp import McpError
|
||||
from mcp.types import METHOD_NOT_FOUND
|
||||
|
||||
from wf_sources_mcp.auth import AuthRecord
|
||||
from wf_sources_mcp.catalog import DiscoveredPrompt, DiscoveredResource, DiscoveredTool
|
||||
from wf_sources_mcp.connections import McpSourceConnection
|
||||
from wf_sources_mcp.sdk import BackendAdapter
|
||||
|
||||
_CapabilityT = TypeVar("_CapabilityT")
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class DiscoveredConnectionCapabilities:
|
||||
tools: list[DiscoveredTool] = field(default_factory=list)
|
||||
resources: list[DiscoveredResource] = field(default_factory=list)
|
||||
prompts: list[DiscoveredPrompt] = field(default_factory=list)
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
async def discover_connection_capabilities(
|
||||
*,
|
||||
connection: McpSourceConnection,
|
||||
auth: AuthRecord | None,
|
||||
adapter: BackendAdapter,
|
||||
) -> DiscoveredConnectionCapabilities:
|
||||
tools = await adapter.list_tools(connection, auth)
|
||||
resources = await _list_optional_capabilities(
|
||||
lambda: adapter.list_resources(connection, auth)
|
||||
)
|
||||
prompts = await _list_optional_capabilities(lambda: adapter.list_prompts(connection, auth))
|
||||
metadata = await adapter.get_connection_metadata(connection, auth)
|
||||
return DiscoveredConnectionCapabilities(
|
||||
tools=tools,
|
||||
resources=resources,
|
||||
prompts=prompts,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
|
||||
async def _list_optional_capabilities(
|
||||
load: Callable[[], Awaitable[list[_CapabilityT]]],
|
||||
) -> list[_CapabilityT]:
|
||||
"""Treat unsupported optional MCP capability families as empty lists.
|
||||
|
||||
Some SDK transports raise ``METHOD_NOT_FOUND`` from inside an
|
||||
``ExceptionGroup`` because the request ran through a task group. Resources
|
||||
and prompts are optional families, so only that exact root error means "not
|
||||
supported"; every other failure still needs to surface.
|
||||
"""
|
||||
try:
|
||||
return await load()
|
||||
except Exception as exc:
|
||||
root = _root_exception(exc)
|
||||
if isinstance(root, McpError) and root.error.code == METHOD_NOT_FOUND:
|
||||
return []
|
||||
raise
|
||||
|
||||
|
||||
def _root_exception(exc: BaseException) -> BaseException:
|
||||
"""Unwrap the first nested exception from MCP task-group ExceptionGroups."""
|
||||
current: BaseException = exc
|
||||
while isinstance(current, ExceptionGroup) and current.exceptions:
|
||||
nested = current.exceptions[0]
|
||||
if isinstance(nested, BaseException):
|
||||
current = nested
|
||||
continue
|
||||
break
|
||||
return current
|
||||
|
||||
|
||||
__all__ = ["DiscoveredConnectionCapabilities", "discover_connection_capabilities"]
|
||||
Reference in New Issue
Block a user