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:
- Is this direction welcome? Or are there reasons to keep the
surface annotation-free?
- 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?
- 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
Background
The MCP 2025-06-18 spec defines optional
ToolAnnotationson everypublic 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-appwriteexposes 4 public tools viaOperator.get_public_tools()(registered throughserver.handle_list_tools):confirm_write=true)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
ToolAnnotationsto 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
appwrite_call_toolasdestructiveHint=Truewas rejected:it would force clients to require destructive-action confirmation
even for read calls like
users.list. The destructive gate isalready enforced inside the tool via
confirm_write=true, which isthe right place for it (per-call context matters, not blanket MCP
hints).
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:
pattern established in louzt/serpapi-mcp-fork so
reviewers can audit each hint at a glance).
tests/unit/test_annotations.pycovering shape (every hint is bool), semantic invariants
(readOnly ⇒ not-destructive, readOnly ⇒ idempotent), and
per-tool specifics.
(ruff / black / pyright / unittest discover).
Before opening an upstream PR I wanted to check:
surface annotation-free?
appwrite_call_toolannotation choice (explicitdestructiveHint=False) consistent with how the Appwrite teamthinks about the per-call
confirm_write=truegate?service.py) get annotations too, even though they never appearin
list_tools()? My current draft does NOT touch them, on thegrounds that annotations on hidden tools are unreachable by
clients.
Validation snapshot