Consider MCP ToolAnnotations on the 4 public tools (proposal + draft PR ready)
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 45/100
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 viaconfirm_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_toolasdestructiveHint=Truewas rejected:
it would force clients to require destructive-action confirmation
even for read calls likeusers.list. The destructive gate is
already enforced inside the tool viaconfirm_write=true, which is
the right place for it (per-call context matters, not blanket MCP
hints). - Marking
appwrite_search_docsasopenWorldHint=Truewas 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:
- Is this direction welcome? Or are there reasons to keep the
surface annotation-free? - Is the
appwrite_call_toolannotation choice (explicit
destructiveHint=False) consistent with how the Appwrite team
thinks about the per-callconfirm_write=truegate? - Should the 25 hidden SDK services (registered dynamically by
service.py) get annotations too, even though they never appear
inlist_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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from appwrite/mcp
-
Difficulty 1/5 Under an hour Newbie friendliness 20/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 55/100
Similar issues
-
documentation help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
simonw/sqlite-utils#872 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100