refactor: move mcp sdk converters to wf_sources_mcp
This commit is contained in:
@@ -100,3 +100,28 @@ def test_wf_sources_mcp_does_not_import_old_sdk_protocol_modules() -> None:
|
||||
"wf_sources_mcp still imports old wf_mcp SDK/runtime protocol modules:\n"
|
||||
+ "\n".join(f" {violation}" for violation in violations)
|
||||
)
|
||||
|
||||
|
||||
def test_wf_sources_mcp_does_not_import_old_sdk_converter_module() -> None:
|
||||
root = Path(__file__).resolve().parents[2] / "src" / "wf_sources_mcp"
|
||||
forbidden = {
|
||||
"wf_mcp.sdk.converters",
|
||||
}
|
||||
violations: list[str] = []
|
||||
|
||||
for py_file in sorted(root.rglob("*.py")):
|
||||
rel = py_file.relative_to(root.parent)
|
||||
module = str(rel.with_suffix("")).replace("/", ".").replace("\\", ".")
|
||||
tree = ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ImportFrom) and node.module in forbidden:
|
||||
violations.append(f"{module}:{node.lineno}: from {node.module} import ...")
|
||||
elif isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
if alias.name in forbidden:
|
||||
violations.append(f"{module}:{node.lineno}: import {alias.name}")
|
||||
|
||||
assert violations == [], (
|
||||
"wf_sources_mcp still imports old wf_mcp SDK converter module:\n"
|
||||
+ "\n".join(f" {violation}" for violation in violations)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from mcp.types import CallToolResult, TextContent, Tool
|
||||
|
||||
from wf_sources_mcp.sdk.converters import tool_result_to_call_result, tool_to_discovered
|
||||
|
||||
|
||||
def test_tool_without_output_schema_exposes_raw_content_schema() -> None:
|
||||
tool = Tool(
|
||||
name="echo",
|
||||
inputSchema={"type": "object", "properties": {}},
|
||||
)
|
||||
|
||||
discovered = tool_to_discovered(tool)
|
||||
|
||||
properties = discovered.output_schema["properties"]
|
||||
assert properties["content"]["type"] == "array"
|
||||
assert "text" not in properties
|
||||
|
||||
|
||||
def test_tool_with_content_only_output_schema_stays_raw() -> None:
|
||||
tool = Tool(
|
||||
name="echo",
|
||||
inputSchema={"type": "object", "properties": {}},
|
||||
outputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "array",
|
||||
"description": "Raw MCP content blocks.",
|
||||
}
|
||||
},
|
||||
"required": ["content"],
|
||||
},
|
||||
)
|
||||
|
||||
discovered = tool_to_discovered(tool)
|
||||
|
||||
properties = discovered.output_schema["properties"]
|
||||
assert properties["content"]["type"] == "array"
|
||||
assert "text" not in properties
|
||||
assert discovered.output_schema["required"] == ["content"]
|
||||
|
||||
|
||||
def test_tool_result_single_text_content_block_stays_in_content() -> None:
|
||||
result = CallToolResult(
|
||||
content=[TextContent(type="text", text="Echo: hello")],
|
||||
)
|
||||
|
||||
converted = tool_result_to_call_result(result)
|
||||
|
||||
assert converted.outcome == "ok"
|
||||
assert "text" not in converted.output
|
||||
assert converted.output["content"][0]["type"] == "text"
|
||||
assert converted.output["content"][0]["text"] == "Echo: hello"
|
||||
|
||||
|
||||
def test_tool_result_structured_content_is_not_rewritten() -> None:
|
||||
result = CallToolResult(
|
||||
content=[TextContent(type="text", text="ignored")],
|
||||
structuredContent={"value": "structured"},
|
||||
)
|
||||
|
||||
converted = tool_result_to_call_result(result)
|
||||
|
||||
assert converted.output["value"] == "structured"
|
||||
assert "text" not in converted.output
|
||||
Reference in New Issue
Block a user