diff --git a/docs/design/699-hmm-mcp-demo-users/README.md b/docs/design/699-hmm-mcp-demo-users/README.md index ea4b393..ad582dd 100644 --- a/docs/design/699-hmm-mcp-demo-users/README.md +++ b/docs/design/699-hmm-mcp-demo-users/README.md @@ -24,6 +24,8 @@ vpd_token_presets.json (사용자 ID·역할·팀·테스트 문맥·mcp_token_e - 선택한 사용자는 자연어의 ‘나’, ‘내’, ‘우리 팀’을 해석하는 데모 문맥이다. 현재 HMM MCP의 gateway token은 공용이므로 이 선택 자체가 DB 행 수준 권한을 강제한다고 표시하지 않는다. - HMM MCP 허용 도구는 `resolve_hr_term`, `search_hr_data`, `search_hr_policy` 세 개다. +- 호출 인자는 `tools/list`의 schema를 기준으로 생성한다. 기본 도구에도 레거시 `prompt`/`limit`를 + 강제하지 않으며 `search_hr_data`·`search_hr_policy`는 `query`, `resolve_hr_term`은 `term`을 전달한다. ## 런타임 구성 diff --git a/poc4_active_source_20260714/src/mcp_tool_router.py b/poc4_active_source_20260714/src/mcp_tool_router.py index 3e09962..3dfae60 100644 --- a/poc4_active_source_20260714/src/mcp_tool_router.py +++ b/poc4_active_source_20260714/src/mcp_tool_router.py @@ -154,26 +154,32 @@ def build_mcp_tool_arguments( if not isinstance(properties, Mapping): properties = {} - if tool.name == preferred_tool: - return {"prompt": question, "limit": limit} - if "prompt" in properties: - args: dict[str, Any] = {"prompt": question} - if "limit" in properties: - args["limit"] = limit - elif "max_rows" in properties: - args["max_rows"] = limit - return args - if "question" in properties: - args = {"question": question} - if "max_rows" in properties: - args["max_rows"] = limit - elif "limit" in properties: - args["limit"] = limit - return args - if "query" in properties: - args = {"query": question} + # A server's default/preferred tool still has to obey its discovered schema. + # HMM tools use `query` and `term`; forcing the legacy `prompt`/`limit` shape + # makes an otherwise valid tool fail argument validation. + del preferred_tool + input_name = next( + (name for name in ("prompt", "question", "query", "term", "text") if name in properties), + "", + ) + if not input_name: + required = tool.schema.get("required") + if isinstance(required, list): + input_name = next( + ( + str(name) + for name in required + if isinstance(properties.get(str(name)), Mapping) + and properties[str(name)].get("type") == "string" + ), + "", + ) + if input_name: + args: dict[str, Any] = {input_name: question} if "max_evidence" in properties: args["max_evidence"] = min(limit, 10) + elif "max_rows" in properties: + args["max_rows"] = limit elif "limit" in properties: args["limit"] = limit return args diff --git a/poc4_active_source_20260714/tests/test_scenarios.py b/poc4_active_source_20260714/tests/test_scenarios.py index c2f0f87..5ca60be 100644 --- a/poc4_active_source_20260714/tests/test_scenarios.py +++ b/poc4_active_source_20260714/tests/test_scenarios.py @@ -8,6 +8,7 @@ from unittest.mock import patch from src.poc4.scenarios import ScenarioConfigError, load_demo_scenarios from src.agent_console.profile import load_app_profile +from src.mcp_tool_router import McpTool, build_mcp_tool_arguments class DemoScenarioConfigTest(unittest.TestCase): @@ -74,6 +75,42 @@ class DemoScenarioConfigTest(unittest.TestCase): with self.assertRaises(ScenarioConfigError): load_demo_scenarios(path) + def test_default_mcp_tool_arguments_follow_discovered_query_schema(self) -> None: + tool = McpTool( + name="search_hr_data", + description="", + schema={ + "type": "object", + "properties": {"query": {"type": "string"}}, + "required": ["query"], + }, + read_only=True, + ) + + arguments = build_mcp_tool_arguments( + tool, "직원 E1005의 휴가 신청 내역", 50, preferred_tool="search_hr_data" + ) + + self.assertEqual(arguments, {"query": "직원 E1005의 휴가 신청 내역"}) + + def test_term_tool_arguments_follow_discovered_term_schema(self) -> None: + tool = McpTool( + name="resolve_hr_term", + description="", + schema={ + "type": "object", + "properties": {"term": {"type": "string"}}, + "required": ["term"], + }, + read_only=True, + ) + + arguments = build_mcp_tool_arguments( + tool, "반차", 50, preferred_tool="search_hr_data" + ) + + self.assertEqual(arguments, {"term": "반차"}) + if __name__ == "__main__": unittest.main()