Add deterministic article search to MCP

This commit is contained in:
2026-09-25 15:13:02 +02:00
parent 6f224a1831
commit 1169df96a8
5 changed files with 284 additions and 2 deletions
+58 -2
View File
@@ -5,11 +5,12 @@ import unittest
from unittest.mock import patch
import mcp_server
from roll_calculation import calculate_roll, get_article
from roll_calculation import ArticleRepository, calculate_roll, get_article
from rollcalc_mcp_tools import (
analyze_transport_capacity_result,
calculate_roll_diameter_result,
get_article_result,
search_articles_result,
)
from transport_calculation import analyze_transport, transport_presets
@@ -39,6 +40,34 @@ class McpAdapterTests(unittest.TestCase):
get_article(**arguments),
)
def test_search_articles_delegates_to_domain_and_preserves_candidates(self):
query = "Bfix NSP 4900, 5,00 x 40 m"
result = search_articles_result(query)
self.assertEqual(result, ArticleRepository.load().search(query))
self.assertEqual(result["status"], "search_results")
self.assertEqual(
[candidate["article_number"] for candidate in result["candidates"]],
["180205", "8180205", "182815", "206900"],
)
self.assertEqual(
result["candidates"][1]["production_site"], "Malaysia"
)
def test_search_articles_preserves_ambiguous_and_not_found_domain_results(self):
ambiguous = search_articles_result("Bentofix NSP 4900")
missing = search_articles_result("not a RollCalc product")
self.assertGreater(ambiguous["total_matches"], 1)
self.assertGreater(len(ambiguous["candidates"]), 1)
self.assertEqual(missing, {
"status": "search_not_found",
"query": "not a RollCalc product",
"total_matches": 0,
"candidates": [],
})
def test_calculate_roll_diameter_delegates_for_normal_weighted_and_invalid_cases(self):
for arguments in (
{
@@ -111,7 +140,7 @@ class McpAdapterTests(unittest.TestCase):
self.assertEqual(result["analysis"]["final_rolls"], 24)
self.assertEqual(result["analysis"]["limiting"]["name"], "Weight")
def test_server_registers_exactly_the_three_stdio_poc_tools(self):
def test_server_registers_the_existing_tools_and_article_search(self):
mcp = types.ModuleType("mcp")
server = types.ModuleType("mcp.server")
fastmcp = types.ModuleType("mcp.server.fastmcp")
@@ -128,6 +157,7 @@ class McpAdapterTests(unittest.TestCase):
[tool.__name__ for tool in instance.tools],
[
"get_article",
"search_articles",
"calculate_roll_diameter",
"analyze_transport_capacity",
],
@@ -136,10 +166,16 @@ class McpAdapterTests(unittest.TestCase):
def test_server_schema_exposes_weight_and_transport_chaining_contract(self):
instance = mcp_server.create_server()
tools = instance._tool_manager._tools
search_tool = tools["search_articles"]
roll_schema = tools["calculate_roll_diameter"].parameters
transport_tool = tools["analyze_transport_capacity"]
transport_schema = transport_tool.parameters
self.assertEqual(search_tool.parameters["required"], ["query"])
self.assertEqual(
search_tool.parameters["properties"]["query"]["type"], "string"
)
self.assertNotIn("include_roll_weight", roll_schema["properties"])
preset_schema = transport_schema["properties"]["transport_preset"]
self.assertEqual(
@@ -195,6 +231,26 @@ class McpAdapterTests(unittest.TestCase):
):
self.assertIn(text, description)
search_description = search_tool.description
for text in (
"name, family, designation, or descriptive article text",
"Do not guess or invent an article number",
"deterministic candidates from the RollCalc article master data",
"total_matches is exactly 1",
"use that candidate's exact article_number",
"do NOT select the first or highest-ranked candidate",
"ask the user which article number is intended",
"do not calculate yet",
"no matching RollCalc article was found",
"more specific designation or article number",
"do not invent an article",
"production_site=Malaysia is descriptive metadata only",
"does not imply Bentofix, Bento 2, or any production machine",
"Ranking/order expresses relevance only",
"get_article -> calculate_roll_diameter -> optionally analyze_transport_capacity",
):
self.assertIn(text, search_description)
if __name__ == "__main__":
unittest.main()