"""Keep ARTEMIS tool descriptions stable across supported Python versions.""" import json from textwrap import dedent from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp.exceptions import ToolError from mcp.types import TextContent class ArtemisFastMCP(FastMCP): """Publish strict inputs and preserve structured legacy JSON results.""" def __init__(self, *args, strict_inputs=False, **kwargs): super().__init__(*args, **kwargs) self.strict_inputs = strict_inputs async def call_tool(self, name, arguments): tool = self._tool_manager.get_tool(name) if tool is not None and self.strict_inputs: unknown = set(arguments) - set(tool.parameters.get("properties", {})) if unknown: raise ToolError( f"未知参数 {sorted(unknown)};请按 {name} 的输入 schema 提交,未执行工具。" ) result = await super().call_tool(name, arguments) if isinstance(result, list) and len(result) == 1 and isinstance(result[0], TextContent): try: value = json.loads(result[0].text) except (ValueError, TypeError): return result if isinstance(value, dict): return result, value if isinstance(value, list): return result, {"items": value} return result def add_tool( self, fn, name=None, title=None, description=None, annotations=None, icons=None, meta=None, structured_output=None, ): if not description: # Python 3.13+ removes this indentation at compilation; 3.12 leaves # it in __doc__. Retain blank lines, including the final newline, # so the existing catalog and pinned schemas stay byte-for-byte equal. first, separator, body = (fn.__doc__ or "").expandtabs().partition("\n") description = first.lstrip() + separator + dedent(body) super().add_tool( fn, name=name, title=title, description=description, annotations=annotations, icons=icons, meta=meta, structured_output=structured_output, ) tool = self._tool_manager.get_tool(name or fn.__name__) if tool is not None and self.strict_inputs: tool.parameters["additionalProperties"] = False