fix(mcp): tolerate legacy tool DTO fields
This commit is contained in:
@@ -30,7 +30,9 @@ class MCPTool:
|
||||
name: str
|
||||
description: str
|
||||
input_schema: dict[str, Any]
|
||||
server_name: str
|
||||
# 2026-05-06 Codex: 部分舊 provider 的 list_tools() 尚未傳 server_name。
|
||||
# 先給 DTO 預設值,registry 會以 provider.name 補正,避免啟動登記直接 crash。
|
||||
server_name: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -21,8 +21,8 @@ MASTER §3.1.3 (B) AI 自主工具選擇
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field, replace
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
import structlog
|
||||
@@ -33,7 +33,7 @@ from src.plugins.mcp.registry import AuditedMCPToolProvider
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class SensorDimension(str, Enum):
|
||||
class SensorDimension(StrEnum):
|
||||
"""8D 感官維度分類"""
|
||||
D1_K8S_STATE = "d1_k8s_state"
|
||||
D2_LOGS = "d2_logs"
|
||||
@@ -111,6 +111,8 @@ class MCPToolRegistry:
|
||||
|
||||
count = 0
|
||||
for tool in tools:
|
||||
if not tool.server_name:
|
||||
tool = replace(tool, server_name=provider.name)
|
||||
audited_provider = AuditedMCPToolProvider(provider)
|
||||
reg = _classify_tool(tool, audited_provider)
|
||||
self._tools.append(reg)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from src.plugins.mcp.interfaces import MCPToolResult
|
||||
import pytest
|
||||
|
||||
from src.plugins.mcp.interfaces import MCPTool, MCPToolProvider, MCPToolResult
|
||||
from src.services.mcp_tool_registry import MCPToolRegistry
|
||||
|
||||
|
||||
def test_mcp_tool_result_accepts_legacy_data_alias() -> None:
|
||||
@@ -26,3 +29,31 @@ def test_mcp_tool_result_allows_failure_without_execution_id() -> None:
|
||||
|
||||
assert result.execution_id.startswith("mcp-")
|
||||
assert result.error == "blocked"
|
||||
|
||||
|
||||
def test_mcp_tool_allows_legacy_missing_server_name() -> None:
|
||||
tool = MCPTool(name="legacy_tool", description="legacy", input_schema={})
|
||||
|
||||
assert tool.server_name == ""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_registry_stamps_missing_server_name_from_provider() -> None:
|
||||
class LegacyProvider(MCPToolProvider):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "legacy_provider"
|
||||
|
||||
async def list_tools(self) -> list[MCPTool]:
|
||||
return [MCPTool(name="legacy_tool", description="legacy", input_schema={})]
|
||||
|
||||
async def execute(self, tool_name: str, parameters: dict) -> MCPToolResult:
|
||||
return MCPToolResult(success=True, output={"tool": tool_name, "parameters": parameters})
|
||||
|
||||
registry = MCPToolRegistry()
|
||||
|
||||
count = await registry.register_provider(LegacyProvider())
|
||||
|
||||
assert count == 1
|
||||
registered = registry.get_all_tools()[0]
|
||||
assert registered.tool.server_name == "legacy_provider"
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
**本次修補**:
|
||||
- `MCPToolResult` 對舊 provider 介面做向後相容:`data` 自動映射到 `output`。
|
||||
- 缺少 `execution_id` 時自動產生 `mcp-<uuid>`,避免失敗/blocked 回傳因建構 DTO 就爆掉。
|
||||
- 補回歸測試,鎖住 `data` alias、明確 `output` 優先,以及 failure without execution_id。
|
||||
- `MCPTool` 對舊 provider 介面做向後相容:允許 `server_name` 暫缺,並由 `MCPToolRegistry.register_provider()` 以 `provider.name` 補正。
|
||||
- 補回歸測試,鎖住 `data` alias、明確 `output` 優先、failure without execution_id,以及舊 provider 缺 `server_name` 時仍可登記工具。
|
||||
|
||||
**後續**:
|
||||
- 這是相容層止血;後續仍應逐步把 provider call-sites 改成明確 `output=` 與穩定 `execution_id`。
|
||||
|
||||
Reference in New Issue
Block a user