forked from HAL9000/cleveragents-core
b96138b88e
MCPToolAdapter.invoke() was reading error messages from result.get('error',
'unknown error'), but the MCP 1.4.0 protocol returns errors in the content
field as a list of content items with type and text keys. This caused every
error from a real MCP 1.4.0-compliant server to be silently replaced with
the string 'unknown error'.
Changes:
- src/cleveragents/mcp/adapter.py: extract error_text from content[0].text
with safe guards (isinstance check, length check) and fallback to
'unknown error' when content is absent or empty
- features/mocks/mock_mcp_transport.py: return MCP 1.4.0-compliant error
responses using content list format instead of the non-standard error key
- features/tdd_mcp_error_content_key.feature: Behave scenario verifying
correct error extraction from MCP 1.4.0 content arrays (written as TDD
issue-capture, @tdd_expected_fail removed after fix applied)
- features/steps/tdd_mcp_error_content_key_steps.py: step definitions for
the new scenario including _MCP14ErrorTransport mock subclass
All 51 MCP adapter scenarios pass. Typecheck: 0 errors. Lint: clean.
ISSUES CLOSED: #2158
72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
"""Step definitions for features/tdd_mcp_error_content_key.feature.
|
|
|
|
TDD issue-capture scenario for #2158: MCPToolAdapter.invoke() must extract
|
|
error messages from content[0].text per MCP 1.4.0 protocol.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from behave import given, then
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.mcp.adapter import MCPServerConfig, MCPToolAdapter
|
|
from features.mocks.mock_mcp_transport import MockMCPTransport
|
|
|
|
|
|
class _MCP14ErrorTransport(MockMCPTransport):
|
|
"""Mock transport that returns MCP 1.4.0-compliant error responses.
|
|
|
|
Returns ``{"isError": True, "content": [{"type": "text", "text": "..."}]}``
|
|
instead of the non-standard ``{"isError": True, "error": "..."}`` format.
|
|
"""
|
|
|
|
def __init__(self, tool_name: str, error_text: str) -> None:
|
|
tools = [
|
|
{
|
|
"name": tool_name,
|
|
"description": f"Mock tool {tool_name}",
|
|
"inputSchema": {},
|
|
}
|
|
]
|
|
super().__init__(tools=tools)
|
|
self._mcp14_error_tool = tool_name
|
|
self._mcp14_error_text = error_text
|
|
|
|
def call(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
|
if method == "tools/call":
|
|
tool_name = params.get("name", "")
|
|
if tool_name == self._mcp14_error_tool:
|
|
return {
|
|
"isError": True,
|
|
"content": [{"type": "text", "text": self._mcp14_error_text}],
|
|
}
|
|
return super().call(method, params)
|
|
|
|
|
|
@given(
|
|
'a connected MCP adapter with a tool returning MCP 1.4.0 content-based error "{tool_name}"'
|
|
)
|
|
def step_mcp14_error_tool(context: Context, tool_name: str) -> None:
|
|
"""Set up a connected MCP adapter whose tool returns a MCP 1.4.0 content-based error."""
|
|
transport = _MCP14ErrorTransport(
|
|
tool_name=tool_name,
|
|
error_text="File not found: /path/to/file",
|
|
)
|
|
config = MCPServerConfig(name="test-server", transport="stdio", command="echo")
|
|
context.mcp_adapter = MCPToolAdapter(config=config, transport=transport)
|
|
context.mcp_adapter.connect()
|
|
context.mcp_adapter.discover_tools()
|
|
|
|
|
|
@then('the invocation error should contain "{expected}"')
|
|
def step_invoke_error_contains(context: Context, expected: str) -> None:
|
|
"""Assert the invocation error message contains the expected substring."""
|
|
result = context.mcp_invoke_result
|
|
assert result is not None, "No invocation result found"
|
|
assert result.error is not None, "Expected an error but got None"
|
|
assert expected in result.error, (
|
|
f"Expected error to contain {expected!r}, got: {result.error!r}"
|
|
)
|