feat: add MCP tool annotations, titles, and deterministic tool ordering - #402
Merged
AutomateIP merged 3 commits intoAug 7, 2026
Merged
Conversation
Adds MCP advisory tool annotations (readOnlyHint/destructiveHint/ idempotentHint/openWorldHint) and per-tool human-readable titles to every tool this server exposes, and makes tool discovery/registration order deterministic. Closes the compliance gap where clients received no behavioral hints and had to treat every tool as unknown-risk, and stabilizes tools/list for client-side prompt caching. - Add an annotate(...) decorator in utilities/tool.py, mirroring the existing tags() decorator, that attaches an mcp.types.ToolAnnotations instance (hints + title) to a tool function. - Apply @Annotate(...) to all 76 static tools, classifying each read-only vs. write vs. destructive. - Make utilities/tool.py:itertools() yield annotations alongside tags and discover tools in a stable, name-sorted order (sorted module files + name-sorted members); consume the new value in server/server.py:__init_tools__. - Give dynamic bindings (bindings/__init__.py:bind_to_tool()) a conservative default (destructiveHint=True, readOnlyHint=False, openWorldHint=True) plus a humanized title, since a bound automation's real-world effects are unknown at registration time. - Add tests: annotate-decorator unit tests, a completeness guard that fails if any discovered tool is unannotated, safety-invariant tests (no tool both read-only and destructive; the destructive set is correctly classified), deterministic-ordering tests, and binding-default assertions. No new dependencies (ToolAnnotations is from the already-pinned mcp SDK). Testing: make ci passes (2710 tests, ~98% coverage, unchanged baseline); verified live against a real Itential Platform (tools/list annotations/titles, stable ordering across two server instantiations, no schema regressions, live dynamic-binding conservative default, and a live get_health call). Implements roadmap Tier D1 items G7, G8, and G14.
start_adapter, stop_adapter, restart_adapter, start_application, stop_application, and restart_application were classified as destructive=False in the initial pass. All six cause a real service disruption on the live platform (traffic through a stopped adapter fails; an application is unavailable while stopped/restarting), so they should carry destructiveHint=True like the rest of the operationally-disruptive tool set. Also extends the safety-invariant test's explicit destructive-set to cover all six, so this classification is guarded against regression. Flagged by user review of PR itential#402.
Update contributor guidelines so future tool PRs don't silently regress the annotation coverage this PR establishes: - AGENTS.md (symlinked from CLAUDE.md): add a required step to the "New Static Tool" workflow requiring every tool function be decorated with annotate(...), with read-only and destructive usage examples pulled from the real codebase. Notes that this is CI-enforced, not just style guidance, citing the completeness guard and safety-invariant tests in tests/utilities/test_tool.py. Also corrects the stale "56+ tools" figure to the current live count of 76+. - CONTRIBUTING.md: add a checklist item to the "Before Submitting" section calling out tool-annotation classification for any PR that adds or changes a tool. No application code changed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds MCP advisory tool annotations (readOnlyHint/destructiveHint/idempotentHint/
openWorldHint) and per-tool human-readable titles to every tool this server
exposes, and makes tool discovery/registration order deterministic. Closes the
compliance gap where clients received no behavioral hints and had to treat
every tool as unknown-risk, and stabilizes
tools/listfor client-side promptcaching.
Changes
annotate(...)decorator inutilities/tool.py, mirroring theexisting
tags()decorator, that attaches anmcp.types.ToolAnnotationsinstance (hints + title) to a tool function.
@annotate(...)to all 76 static tools, classifying each read-only vs.write vs. destructive per a safety-first classification pass.
utilities/tool.py:itertools()yield annotations alongside tags anddiscover tools in a stable, name-sorted order (sorted module files +
name-sorted members); consume the new value in
server/server.py:__init_tools__.bindings/__init__.py:bind_to_tool()) a conservativedefault (destructiveHint=True, readOnlyHint=False, openWorldHint=True) plus a
humanized title, since a bound automation's real-world effects are unknown at
registration time.
any discovered tool is unannotated, safety-invariant tests (no tool both
read-only and destructive; the destructive set is correctly classified),
deterministic-ordering tests, and binding-default assertions.
No new dependencies (
ToolAnnotationsis from the already-pinnedmcpSDK).Testing
make ci: ruff, bandit, headers, 2710 tests, ~98% coverage, unchanged baseline)annotations/titles across a read-only/write/destructive sample, stable
ordering across two server instantiations, no schema regressions, a live
dynamic-binding registration confirming the conservative default, and a
live
get_healthcall)Related Issues
Implements roadmap Tier D1 items G7, G8, and G14 (MCP 2026-07-28 tool
annotations, per-tool titles, and deterministic tool ordering).