Add dark theme, richer Markdown and readable plugin results. Improve Android input evidence, response integrity and diagnostics. Unify lifecycle, guidance receipts and evidence sources across clients. Keep direct action dispatch without an added AI quota approval layer. Align version metadata at 2.2.0 and bundle Helper 1.2.2. Validation: 3063 backend tests before the version bump; 44 after. Frontend: 317 tests and production build passed.
68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
"""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
|