fix(mcp): tolerate legacy tool DTO fields
All checks were successful
Code Review / ai-code-review (push) Successful in 10s
CD Pipeline / tests (push) Successful in 1m9s
CD Pipeline / build-and-deploy (push) Successful in 3m18s
CD Pipeline / post-deploy-checks (push) Successful in 1m27s

This commit is contained in:
Your Name
2026-05-06 16:11:26 +08:00
parent 72811b967e
commit 60c00d7a5d
4 changed files with 42 additions and 6 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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`