@snowluma/mcp gains execution alongside the read-only catalog: when SNOWLUMA_MCP_ENDPOINT is set it exposes query_action (read-only actions, readOnlyHint) and, in write mode, invoke_action (any known action, destructiveHint), proxied to a running OneBot instance over HTTP. Without an endpoint it degrades to docs-only. SNOWLUMA_MCP_MODE (docs|read|write) gates tool visibility AND is re-checked in execute(), so an unlisted tool cannot be called; unknown/legacy actions are non-invokable.
Read vs write is classified precisely at the source: defineAction/preset gain readOnly (default false = write, the safe default), flowed through describe() into the generated catalog snapshot. 58 actions are marked read-only by what their run() actually does -- name-traps like get_ai_record (publishes to the group), request_decrypt_key (returns a DB key), and download_file (arbitrary URL + disk write) stay writes.
The HTTP call is a ~20-line fetch bridge rather than @snowluma/sdk: the SDK's published dist is bundler-only ESM (extensionless imports) that native node cannot load, and the bin stays a plain tsc build.
onebot 332 tests pass (readOnly is metadata-only, no behavior change); mcp gains 21 tests for mode gating / read-write routing / envelope passthrough / error mapping. Full workspace typecheck clean; native node smoke verified.