Consider MCP ToolAnnotations on the 4 public tools (proposal + draft PR ready)

Open
#86 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
45/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Quiet
Tech stack
python
Domain
api, backend

Research direction

Start with Operator.get_public_tools() and server.handle_list_tools to trace how the four public tools are registered. Review the proposed annotations and tests/unit/test_annotations.py, then run the listed ruff, black, pyright, and unittest commands; done means all four public tools expose the agreed hints and the tests pass.

Written by the indexing model from the issue text.

Description

Background

The MCP 2025-06-18 spec defines optional ToolAnnotations on every
public tool (title, readOnlyHint, destructiveHint,
idempotentHint, openWorldHint). Leaving a hint unset signals
"unknown" to MCP clients, which forces them to be conservative —
typically prompting the user before executing the tool.

mcp-server-appwrite exposes 4 public tools via
Operator.get_public_tools() (registered through
server.handle_list_tools):

Tool Behavior
appwrite_get_context Reads account/org/project metadata
appwrite_search_tools Searches the in-memory tool catalog (local)
appwrite_call_tool Dispatches to Appwrite SDK (may mutate, gated by confirm_write=true)
appwrite_search_docs Searches the committed docs index (local, requires OPENAI_API_KEY)

Today none of them declare annotations, so MCP clients treat all four
as unknown and prompt the user even for read-only context queries.

Proposal

Add explicit ToolAnnotations to each of the 4 public tools:

  • appwrite_get_context: readOnly=True, destructive=False, idempotent=True, openWorld=True (calls Appwrite Cloud APIs for live data)
  • appwrite_search_tools: readOnly=True, destructive=False, idempotent=True, openWorld=False (local in-memory catalog)
  • appwrite_call_tool: readOnly=False, destructive=False (the destructive gate lives inside the tool via confirm_write=true, not at the MCP hint layer), idempotent=False, openWorld=True (dispatches to live Appwrite APIs)
  • appwrite_search_docs: readOnly=True, destructive=False, idempotent=True, openWorld=False (local docs index)

Trade-offs considered

  • Marking appwrite_call_tool as destructiveHint=True was rejected:
    it would force clients to require destructive-action confirmation
    even for read calls like users.list. The destructive gate is
    already enforced inside the tool via confirm_write=true, which is
    the right place for it (per-call context matters, not blanket MCP
    hints).
  • Marking appwrite_search_docs as openWorldHint=True was rejected:
    the search itself runs locally over the committed index. The
    OpenAI API call only happens when an embedder is configured, and
    that is an implementation detail of the search, not a side effect
    the caller needs to authorize.

Draft PR ready

I have a draft PR on my fork
(louzt/appwrite-mcp#2) with:

  • Inline-commented annotations on all 4 tools (matching the
    pattern established in louzt/serpapi-mcp-fork so
    reviewers can audit each hint at a glance).
  • 13 unittest-style tests in tests/unit/test_annotations.py
    covering shape (every hint is bool), semantic invariants
    (readOnly ⇒ not-destructive, readOnly ⇒ idempotent), and
    per-tool specifics.
  • All 111 unit tests pass locally on the 4-job CI matrix
    (ruff / black / pyright / unittest discover).

Before opening an upstream PR I wanted to check:

  1. Is this direction welcome? Or are there reasons to keep the
    surface annotation-free?
  2. Is the appwrite_call_tool annotation choice (explicit
    destructiveHint=False) consistent with how the Appwrite team
    thinks about the per-call confirm_write=true gate?
  3. Should the 25 hidden SDK services (registered dynamically by
    service.py) get annotations too, even though they never appear
    in list_tools()? My current draft does NOT touch them, on the
    grounds that annotations on hidden tools are unreachable by
    clients.

Validation snapshot

$ uv run --group dev ruff check src tests
All checks passed!

$ uv run --group dev black --check src tests
34 files would be left unchanged.

$ uv run --group dev pyright
0 errors, 0 warnings, 0 informations

$ uv run python -m unittest discover -s tests/unit
Ran 111 tests in 5.000s
OK
Dominant language
Python
Stars
73
Forks
18
Avg merge
7h 23m
Merged PRs (30d)
11

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from appwrite/mcp

All issues in appwrite/mcp

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.