fix: restore dict-at-boundary MCP source API for SDK v2
This commit is contained in:
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Any, Protocol
|
from typing import TYPE_CHECKING, Any, Protocol
|
||||||
|
|
||||||
from mcp import ClientResult
|
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
CallToolResult,
|
CallToolResult,
|
||||||
ClientNotification,
|
ClientNotification,
|
||||||
@@ -13,8 +12,12 @@ from mcp.types import (
|
|||||||
ListResourcesResult,
|
ListResourcesResult,
|
||||||
ListToolsResult,
|
ListToolsResult,
|
||||||
ReadResourceResult,
|
ReadResourceResult,
|
||||||
|
ServerResult,
|
||||||
|
client_notification_adapter,
|
||||||
|
client_request_adapter,
|
||||||
|
server_result_adapter,
|
||||||
)
|
)
|
||||||
from pydantic import AnyUrl
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
from wf_sources_mcp.connections import McpSourceConnection
|
from wf_sources_mcp.connections import McpSourceConnection
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ class McpClientSession(Protocol):
|
|||||||
|
|
||||||
async def list_prompts(self) -> ListPromptsResult: ...
|
async def list_prompts(self) -> ListPromptsResult: ...
|
||||||
|
|
||||||
async def read_resource(self, uri: AnyUrl) -> ReadResourceResult: ...
|
async def read_resource(self, uri: str) -> ReadResourceResult: ...
|
||||||
|
|
||||||
async def get_prompt(
|
async def get_prompt(
|
||||||
self,
|
self,
|
||||||
@@ -56,8 +59,8 @@ class McpClientSession(Protocol):
|
|||||||
async def send_request(
|
async def send_request(
|
||||||
self,
|
self,
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
result_type: type[ClientResult],
|
result_type: type[ServerResult] | TypeAdapter[ServerResult],
|
||||||
) -> ClientResult: ...
|
) -> ServerResult: ...
|
||||||
|
|
||||||
async def send_notification(self, notification: ClientNotification) -> None: ...
|
async def send_notification(self, notification: ClientNotification) -> None: ...
|
||||||
|
|
||||||
@@ -118,7 +121,7 @@ class McpSourceClient:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async def read_resource(self, uri: str) -> dict[str, Any]:
|
async def read_resource(self, uri: str) -> dict[str, Any]:
|
||||||
result = await self.session.read_resource(AnyUrl(uri))
|
result = await self.session.read_resource(str(uri))
|
||||||
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||||
|
|
||||||
async def get_prompt(
|
async def get_prompt(
|
||||||
@@ -135,8 +138,10 @@ class McpSourceClient:
|
|||||||
params: dict[str, Any] | None = None,
|
params: dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
result = await self.session.send_request(
|
result = await self.session.send_request(
|
||||||
ClientRequest.model_validate({"method": method, "params": params}),
|
client_request_adapter.validate_python(
|
||||||
ClientResult,
|
{"method": method, "params": params}
|
||||||
|
),
|
||||||
|
server_result_adapter,
|
||||||
)
|
)
|
||||||
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||||
|
|
||||||
@@ -146,7 +151,9 @@ class McpSourceClient:
|
|||||||
params: dict[str, Any] | None = None,
|
params: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await self.session.send_notification(
|
await self.session.send_notification(
|
||||||
ClientNotification.model_validate({"method": method, "params": params})
|
client_notification_adapter.validate_python(
|
||||||
|
{"method": method, "params": params}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def call_tool(
|
async def call_tool(
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from dataclasses import dataclass
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from mcp.client.session import ClientSession
|
from mcp.client.session import ClientSession
|
||||||
from pydantic import AnyUrl
|
|
||||||
|
|
||||||
from wf_sources_mcp.auth import AuthRecord
|
from wf_sources_mcp.auth import AuthRecord
|
||||||
from wf_sources_mcp.catalog import DiscoveredPrompt, DiscoveredResource, DiscoveredTool
|
from wf_sources_mcp.catalog import DiscoveredPrompt, DiscoveredResource, DiscoveredTool
|
||||||
@@ -66,7 +65,7 @@ class PersistentMcpSession:
|
|||||||
if self.read_resource_callback is not None:
|
if self.read_resource_callback is not None:
|
||||||
return await self.read_resource_callback(uri)
|
return await self.read_resource_callback(uri)
|
||||||
if self.client is not None:
|
if self.client is not None:
|
||||||
result = await self.client.read_resource(AnyUrl(uri))
|
result = await self.client.read_resource(str(uri))
|
||||||
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||||
raise RuntimeError("persistent MCP session has no resource read transport")
|
raise RuntimeError("persistent MCP session has no resource read transport")
|
||||||
|
|
||||||
@@ -135,12 +134,13 @@ class PersistentMcpSession:
|
|||||||
if self.invoke_method_callback is not None:
|
if self.invoke_method_callback is not None:
|
||||||
return await self.invoke_method_callback(method, params)
|
return await self.invoke_method_callback(method, params)
|
||||||
if self.client is not None:
|
if self.client is not None:
|
||||||
from mcp import ClientResult
|
from mcp.types import client_request_adapter, server_result_adapter
|
||||||
from mcp.types import ClientRequest
|
|
||||||
|
|
||||||
result = await self.client.send_request(
|
result = await self.client.send_request(
|
||||||
ClientRequest.model_validate({"method": method, "params": params}),
|
client_request_adapter.validate_python(
|
||||||
ClientResult,
|
{"method": method, "params": params}
|
||||||
|
),
|
||||||
|
server_result_adapter,
|
||||||
)
|
)
|
||||||
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
return result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||||
raise RuntimeError("persistent MCP session has no method invoke transport")
|
raise RuntimeError("persistent MCP session has no method invoke transport")
|
||||||
@@ -155,10 +155,12 @@ class PersistentMcpSession:
|
|||||||
await self.send_notification_callback(method, params)
|
await self.send_notification_callback(method, params)
|
||||||
return
|
return
|
||||||
if self.client is not None:
|
if self.client is not None:
|
||||||
from mcp.types import ClientNotification
|
from mcp.types import client_notification_adapter
|
||||||
|
|
||||||
await self.client.send_notification(
|
await self.client.send_notification(
|
||||||
ClientNotification.model_validate({"method": method, "params": params})
|
client_notification_adapter.validate_python(
|
||||||
|
{"method": method, "params": params}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
raise RuntimeError("persistent MCP session has no notification send transport")
|
raise RuntimeError("persistent MCP session has no notification send transport")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mcp import McpError
|
from mcp import MCPError
|
||||||
from mcp.types import ErrorData
|
from mcp.types import ErrorData
|
||||||
|
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
@@ -33,10 +33,10 @@ class _ToolsOnlyAdapter:
|
|||||||
]
|
]
|
||||||
|
|
||||||
async def list_resources(self, connection, auth):
|
async def list_resources(self, connection, auth):
|
||||||
raise McpError(ErrorData(code=-32601, message="Method not found"))
|
raise MCPError.from_error_data(ErrorData(code=-32601, message="Method not found"))
|
||||||
|
|
||||||
async def list_prompts(self, connection, auth):
|
async def list_prompts(self, connection, auth):
|
||||||
raise McpError(ErrorData(code=-32601, message="Method not found"))
|
raise MCPError.from_error_data(ErrorData(code=-32601, message="Method not found"))
|
||||||
|
|
||||||
async def get_connection_metadata(self, connection, auth):
|
async def get_connection_metadata(self, connection, auth):
|
||||||
return {
|
return {
|
||||||
@@ -65,7 +65,7 @@ class _WrappedToolsOnlyAdapter(_ToolsOnlyAdapter):
|
|||||||
async def list_resources(self, connection, auth):
|
async def list_resources(self, connection, auth):
|
||||||
raise ExceptionGroup(
|
raise ExceptionGroup(
|
||||||
"unhandled errors in a TaskGroup",
|
"unhandled errors in a TaskGroup",
|
||||||
[McpError(ErrorData(code=-32601, message="Method not found"))],
|
[MCPError.from_error_data(ErrorData(code=-32601, message="Method not found"))],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -50,10 +50,14 @@ def finalize_tool(
|
|||||||
|
|
||||||
@deprecated("Use pytests tmp_path fixture instead")
|
@deprecated("Use pytests tmp_path fixture instead")
|
||||||
@overload
|
@overload
|
||||||
|
|
||||||
|
|
||||||
def local_temp_root() -> Path: ...
|
def local_temp_root() -> Path: ...
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
|
||||||
|
|
||||||
def local_temp_root(root_path: Path) -> Path: ...
|
def local_temp_root(root_path: Path) -> Path: ...
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from contextlib import AsyncExitStack
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mcp import ClientResult
|
from mcp import GetPromptResult, ReadResourceResult, ServerResult
|
||||||
from mcp.client.session import ClientSession
|
from mcp.client.session import ClientSession
|
||||||
from mcp.types import CallToolResult as RawCallToolResult
|
from mcp.types import CallToolResult as RawCallToolResult
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
@@ -18,8 +18,9 @@ from mcp.types import (
|
|||||||
Resource,
|
Resource,
|
||||||
TextContent,
|
TextContent,
|
||||||
Tool,
|
Tool,
|
||||||
|
server_result_adapter,
|
||||||
)
|
)
|
||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl, TypeAdapter
|
||||||
|
|
||||||
from wf_sources_mcp.auth import AuthRecord
|
from wf_sources_mcp.auth import AuthRecord
|
||||||
from wf_sources_mcp.connections import McpSourceConnection
|
from wf_sources_mcp.connections import McpSourceConnection
|
||||||
@@ -96,7 +97,7 @@ class _FakeFactory(PersistentSessionFactory):
|
|||||||
self.calls.append((tool_name, payload))
|
self.calls.append((tool_name, payload))
|
||||||
return RawCallToolResult(
|
return RawCallToolResult(
|
||||||
content=[TextContent(type="text", text="ok")],
|
content=[TextContent(type="text", text="ok")],
|
||||||
structuredContent={"echoed": payload["text"]},
|
structured_content={"echoed": payload["text"]},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _create_with_stack(
|
async def _create_with_stack(
|
||||||
@@ -115,48 +116,38 @@ class _FakeFactory(PersistentSessionFactory):
|
|||||||
return await factory._call_tool(tool_name, payload)
|
return await factory._call_tool(tool_name, payload)
|
||||||
|
|
||||||
async def read_resource(self, uri: AnyUrl):
|
async def read_resource(self, uri: AnyUrl):
|
||||||
return type(
|
return ReadResourceResult.model_validate(
|
||||||
"ReadResourceResult",
|
{"contents": [{"uri": str(uri), "text": "resource text"}]}
|
||||||
(),
|
)
|
||||||
{
|
|
||||||
"model_dump": lambda _self, **_kwargs: {
|
|
||||||
"contents": [{"uri": str(uri), "text": "resource text"}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)()
|
|
||||||
|
|
||||||
async def get_prompt(
|
async def get_prompt(
|
||||||
self,
|
self,
|
||||||
prompt_name: str,
|
prompt_name: str,
|
||||||
arguments: dict[str, str] | None = None,
|
arguments: dict[str, str] | None = None,
|
||||||
):
|
) -> GetPromptResult:
|
||||||
return type(
|
return GetPromptResult.model_validate(
|
||||||
"GetPromptResult",
|
|
||||||
(),
|
|
||||||
{
|
{
|
||||||
"model_dump": lambda _self, **_kwargs: {
|
"messages": [
|
||||||
"messages": [
|
{
|
||||||
{
|
"role": "user",
|
||||||
"role": "user",
|
"content": {
|
||||||
"content": {
|
"type": "text",
|
||||||
"type": "text",
|
"text": f"{prompt_name}:{arguments or {}}",
|
||||||
"text": f"{prompt_name}:{arguments or {}}",
|
},
|
||||||
},
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
}
|
)
|
||||||
},
|
|
||||||
)()
|
|
||||||
|
|
||||||
async def list_resources(self) -> ListResourcesResult:
|
async def list_resources(self) -> ListResourcesResult:
|
||||||
return ListResourcesResult(
|
return ListResourcesResult(
|
||||||
resources=[
|
resources=[
|
||||||
Resource(
|
Resource(
|
||||||
uri=AnyUrl("fixture://docs/runtime"),
|
uri=("fixture://docs/runtime"),
|
||||||
name="resource.runtime",
|
name="resource.runtime",
|
||||||
title="Runtime Resource",
|
title="Runtime Resource",
|
||||||
description="Runtime-scoped resource.",
|
description="Runtime-scoped resource.",
|
||||||
mimeType="text/plain",
|
mime_type="text/plain",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -180,7 +171,7 @@ class _FakeFactory(PersistentSessionFactory):
|
|||||||
name="tool.runtime",
|
name="tool.runtime",
|
||||||
title="Runtime Tool",
|
title="Runtime Tool",
|
||||||
description="Runtime-scoped tool.",
|
description="Runtime-scoped tool.",
|
||||||
inputSchema={"type": "object"},
|
input_schema={"type": "object"},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -205,9 +196,9 @@ class _FakeFactory(PersistentSessionFactory):
|
|||||||
async def send_request(
|
async def send_request(
|
||||||
self,
|
self,
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
result_type: type[ClientResult],
|
result_type: type[ServerResult],
|
||||||
) -> ClientResult:
|
) -> ServerResult:
|
||||||
return ClientResult.model_validate(
|
return server_result_adapter.validate_python(
|
||||||
{"jsonrpc": "2.0", "id": 1, "result": {}}
|
{"jsonrpc": "2.0", "id": 1, "result": {}}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -360,11 +351,9 @@ async def test_persistent_session_factory_routes_resource_reads_through_owner()
|
|||||||
|
|
||||||
assert factory.created_connections == [connection]
|
assert factory.created_connections == [connection]
|
||||||
assert factory.calls == [("echo", {"text": "one"})]
|
assert factory.calls == [("echo", {"text": "one"})]
|
||||||
assert resource_payload == {
|
assert resource_payload["contents"] == [
|
||||||
"contents": [
|
{"uri": "fixture://docs/welcome", "text": "resource text"}
|
||||||
{"uri": "fixture://docs/welcome", "text": "resource text"},
|
]
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -564,7 +553,7 @@ async def test_persistent_session_list_tools_client_fallback() -> None:
|
|||||||
Tool(
|
Tool(
|
||||||
name="client_tool",
|
name="client_tool",
|
||||||
description="Client tool",
|
description="Client tool",
|
||||||
inputSchema={"type": "object"},
|
input_schema={"type": "object"},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -582,17 +571,13 @@ async def test_persistent_session_list_tools_client_fallback() -> None:
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_persistent_session_invoke_method_client_fallback() -> None:
|
async def test_persistent_session_invoke_method_client_fallback() -> None:
|
||||||
from mcp import ClientResult
|
|
||||||
|
|
||||||
class _MinimalClient:
|
class _MinimalClient:
|
||||||
async def send_request(
|
async def send_request(
|
||||||
self,
|
self,
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
result_type: type[ClientResult],
|
result_type: type[ServerResult] | TypeAdapter[ServerResult],
|
||||||
) -> ClientResult:
|
) -> ServerResult:
|
||||||
return ClientResult.model_validate(
|
return server_result_adapter.validate_python({"tools": []})
|
||||||
{"jsonrpc": "2.0", "id": 1, "result": {"tools": []}}
|
|
||||||
)
|
|
||||||
|
|
||||||
session = PersistentMcpSession(
|
session = PersistentMcpSession(
|
||||||
connection=_connection(),
|
connection=_connection(),
|
||||||
@@ -601,8 +586,7 @@ async def test_persistent_session_invoke_method_client_fallback() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
result = await session.invoke_method("tools/list")
|
result = await session.invoke_method("tools/list")
|
||||||
|
assert result["tools"] == []
|
||||||
assert result["result"]["tools"] == []
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from contextlib import asynccontextmanager
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mcp import ClientResult
|
from mcp import GetPromptResult, ReadResourceResult, ServerResult
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
CallToolResult,
|
CallToolResult,
|
||||||
ClientNotification,
|
ClientNotification,
|
||||||
@@ -18,7 +18,7 @@ from mcp.types import (
|
|||||||
TextContent,
|
TextContent,
|
||||||
Tool,
|
Tool,
|
||||||
)
|
)
|
||||||
from pydantic import AnyUrl
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
from wf_sources_mcp.client import McpSourceClient
|
from wf_sources_mcp.client import McpSourceClient
|
||||||
from wf_sources_mcp.connections import McpSourceConnection
|
from wf_sources_mcp.connections import McpSourceConnection
|
||||||
@@ -47,7 +47,7 @@ class _FakeSession:
|
|||||||
name="echo",
|
name="echo",
|
||||||
title="Echo",
|
title="Echo",
|
||||||
description="Echo text.",
|
description="Echo text.",
|
||||||
inputSchema={"type": "object", "properties": {}},
|
input_schema={"type": "object", "properties": {}},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -56,11 +56,11 @@ class _FakeSession:
|
|||||||
return ListResourcesResult(
|
return ListResourcesResult(
|
||||||
resources=[
|
resources=[
|
||||||
Resource(
|
Resource(
|
||||||
uri=AnyUrl("fixture://docs/welcome"),
|
uri="fixture://docs/welcome",
|
||||||
name="resource.welcome",
|
name="resource.welcome",
|
||||||
title="Welcome",
|
title="Welcome",
|
||||||
description="Welcome resource.",
|
description="Welcome resource.",
|
||||||
mimeType="text/plain",
|
mime_type="text/plain",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -77,49 +77,39 @@ class _FakeSession:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
async def read_resource(self, uri: AnyUrl) -> Any:
|
async def read_resource(self, uri: str) -> ReadResourceResult:
|
||||||
return type(
|
return ReadResourceResult.model_validate(
|
||||||
"ReadResourceResult",
|
{"contents": [{"uri": str(uri), "text": "hello"}]}
|
||||||
(),
|
)
|
||||||
{
|
|
||||||
"model_dump": lambda _self, **_kwargs: {
|
|
||||||
"contents": [{"uri": str(uri), "text": "hello"}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)()
|
|
||||||
|
|
||||||
async def get_prompt(
|
async def get_prompt(
|
||||||
self,
|
self,
|
||||||
prompt_name: str,
|
prompt_name: str,
|
||||||
arguments: dict[str, str] | None = None,
|
arguments: dict[str, str] | None = None,
|
||||||
) -> Any:
|
) -> GetPromptResult:
|
||||||
return type(
|
return GetPromptResult.model_validate(
|
||||||
"GetPromptResult",
|
|
||||||
(),
|
|
||||||
{
|
{
|
||||||
"model_dump": lambda _self, **_kwargs: {
|
"messages": [
|
||||||
"messages": [
|
{
|
||||||
{
|
"role": "user",
|
||||||
"role": "user",
|
"content": {
|
||||||
"content": {
|
"type": "text",
|
||||||
"type": "text",
|
"text": f"{prompt_name}:{arguments or {}}",
|
||||||
"text": f"{prompt_name}:{arguments or {}}",
|
},
|
||||||
},
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
}
|
)
|
||||||
},
|
|
||||||
)()
|
|
||||||
|
|
||||||
async def send_request(
|
async def send_request(
|
||||||
self,
|
self,
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
result_type: type[ClientResult],
|
result_type: type[ServerResult] | TypeAdapter[ServerResult],
|
||||||
) -> Any:
|
) -> Any:
|
||||||
assert result_type is ClientResult
|
assert isinstance(result_type, TypeAdapter)
|
||||||
self.requests.append(request)
|
self.requests.append(request)
|
||||||
return type(
|
return type(
|
||||||
"ClientResultModel",
|
"ServerResultModel",
|
||||||
(),
|
(),
|
||||||
{"model_dump": lambda _self, **_kwargs: {"ok": True}},
|
{"model_dump": lambda _self, **_kwargs: {"ok": True}},
|
||||||
)()
|
)()
|
||||||
@@ -134,7 +124,7 @@ class _FakeSession:
|
|||||||
) -> CallToolResult:
|
) -> CallToolResult:
|
||||||
return CallToolResult(
|
return CallToolResult(
|
||||||
content=[TextContent(type="text", text="ok")],
|
content=[TextContent(type="text", text="ok")],
|
||||||
structuredContent={"tool": tool_name, "payload": payload},
|
structured_content={"tool": tool_name, "payload": payload},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mcp import ClientResult
|
from mcp import GetPromptResult, ReadResourceResult, ServerResult
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
CallToolResult,
|
CallToolResult,
|
||||||
ClientNotification,
|
ClientNotification,
|
||||||
@@ -16,7 +16,7 @@ from mcp.types import (
|
|||||||
TextContent,
|
TextContent,
|
||||||
Tool,
|
Tool,
|
||||||
)
|
)
|
||||||
from pydantic import AnyUrl
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
from wf_sources_mcp.client import McpSourceClient
|
from wf_sources_mcp.client import McpSourceClient
|
||||||
from wf_sources_mcp.connections import McpSourceConnection
|
from wf_sources_mcp.connections import McpSourceConnection
|
||||||
@@ -44,7 +44,7 @@ class _FakeSession:
|
|||||||
name="echo",
|
name="echo",
|
||||||
title="Echo",
|
title="Echo",
|
||||||
description="Echo text.",
|
description="Echo text.",
|
||||||
inputSchema={"type": "object", "properties": {}},
|
input_schema={"type": "object", "properties": {}},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -53,11 +53,11 @@ class _FakeSession:
|
|||||||
return ListResourcesResult(
|
return ListResourcesResult(
|
||||||
resources=[
|
resources=[
|
||||||
Resource(
|
Resource(
|
||||||
uri=AnyUrl("fixture://docs/welcome"),
|
uri=("fixture://docs/welcome"),
|
||||||
name="resource.welcome",
|
name="resource.welcome",
|
||||||
title="Welcome",
|
title="Welcome",
|
||||||
description="Welcome resource.",
|
description="Welcome resource.",
|
||||||
mimeType="text/plain",
|
mime_type="text/plain",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -74,46 +74,37 @@ class _FakeSession:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
async def read_resource(self, uri: AnyUrl) -> Any:
|
# TODO investigate why these are uses type/3 for object creation
|
||||||
return type(
|
async def read_resource(self, uri: str) -> ReadResourceResult:
|
||||||
"ReadResourceResult",
|
return ReadResourceResult.model_validate(
|
||||||
(),
|
{"contents": [{"uri": str(uri), "text": "hello"}]}
|
||||||
{
|
)
|
||||||
"model_dump": lambda _self, **_kwargs: {
|
|
||||||
"contents": [{"uri": str(uri), "text": "hello"}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)()
|
|
||||||
|
|
||||||
async def get_prompt(
|
async def get_prompt(
|
||||||
self,
|
self,
|
||||||
prompt_name: str,
|
prompt_name: str,
|
||||||
arguments: dict[str, str] | None = None,
|
arguments: dict[str, str] | None = None,
|
||||||
) -> Any:
|
) -> GetPromptResult:
|
||||||
return type(
|
return GetPromptResult.model_validate(
|
||||||
"GetPromptResult",
|
|
||||||
(),
|
|
||||||
{
|
{
|
||||||
"model_dump": lambda _self, **_kwargs: {
|
"messages": [
|
||||||
"messages": [
|
{
|
||||||
{
|
"role": "user",
|
||||||
"role": "user",
|
"content": {
|
||||||
"content": {
|
"type": "text",
|
||||||
"type": "text",
|
"text": f"{prompt_name}:{arguments or {}}",
|
||||||
"text": f"{prompt_name}:{arguments or {}}",
|
},
|
||||||
},
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
}
|
)
|
||||||
},
|
|
||||||
)()
|
|
||||||
|
|
||||||
async def send_request(
|
async def send_request(
|
||||||
self,
|
self,
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
result_type: type[ClientResult],
|
result_type: type[ServerResult] | TypeAdapter[ServerResult],
|
||||||
) -> Any:
|
) -> Any:
|
||||||
assert result_type is ClientResult
|
assert isinstance(result_type, TypeAdapter)
|
||||||
self.requests.append(request)
|
self.requests.append(request)
|
||||||
return type(
|
return type(
|
||||||
"ClientResultModel",
|
"ClientResultModel",
|
||||||
@@ -131,7 +122,7 @@ class _FakeSession:
|
|||||||
) -> CallToolResult:
|
) -> CallToolResult:
|
||||||
return CallToolResult(
|
return CallToolResult(
|
||||||
content=[TextContent(type="text", text="ok")],
|
content=[TextContent(type="text", text="ok")],
|
||||||
structuredContent={"tool": tool_name, "payload": payload},
|
structured_content={"tool": tool_name, "payload": payload},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user