CozyVTT MCP is an MCP (Model Context Protocol) bridge tool for the self-hosted open-source virtual tabletop CozyVTT, used to let AI agents participate in campaigns as DM/KP: narrate in chat, have authoritative dice rolls executed server-side, move tokens, switch maps, manage initiative order, and handle character sheets. It was built and tested specifically for Hermes Agent, but is compatible with any MCP client that supports stdio transport.
Purpose and Positioning
This bridge lets AI agents join CozyVTT campaigns and take on DM/KP duties, handling narration, dice rolling, token and map operations, initiative management, and character sheet resolution. The bridge itself remains game-system-agnostic; rules calculations (skill checks, SAN loss, damage, etc.) are done by the agent/GM, and the bridge only performs authoritative dice rolls and writes the results.
Main Capabilities
It provides 41 tools, which return an {ok, data?, error?} structure, with MCP parameter validation handled by FastMCP. The tools are divided by category:
- Session/Campaign: campaign_get, session_manage, session_list, session_notes_update, campaign_transfer_dm, map_list, map_switch
- Narration: chat_send (DM/PLAYER), chat_read
- Dice: dice_roll (server-side authoritative result; is_secret=true delivers via the secret field to the roller and the DM, based on v1.4.0), events_poll (recent buffered dice events, not persistent history)
- Tokens/Maps: token_add, token_move, token_hp_update, token_place_creature, token_delete, map_create, map_delete, creature_search (SRD and custom library)
- Combat: initiative_manage (add/remove/roll/set/reorder/start/next/end), initiative_read (CoC7e initiative is sorted by DEX, no rolling, roll is restricted accordingly)
- Characters: character_list, character_get, character_create, character_delete, character_validate, character_update (rules math is calculated by the agent, the bridge only writes values)
- Documents: document_upload, document_create, document_list, campaign_document_list, document_read, document_update, document_share, document_unshare, document_delete
- Saved rolls: saved_roll_list, saved_roll_create, saved_roll_update, saved_roll_delete (private to the current user and campaign; 50 macros per user/campaign, the server validates expressions; no saved_roll_get)
- Hit dice: character_hitdice_spend (DND_5E only; only dispatches one expenditure, no roll and no healing)
There are also surface-level tools such as map_create, map_delete, token_delete, character_delete, and others. Some capabilities are gated based on the campaign's gameSystem (DND_5E / PATHFINDER_2E / SHADOWRUN_6E / CALL_OF_CTHULHU_7E); flexible campaigns with no gameSystem set will fail closed. campaign_get().features reports the gated capabilities currently available to the campaign.
Usage
Installation requirements: a CozyVTT v1.5.0 instance (primary pinned baseline), and a campaign where the account has the permissions required by each tool; depends on uv (recommended) or pip. MCP uses stdin/stdout, with no network port or TTY; use -i to keep stdin open. Credentials are provided at runtime; when there are no COZYVTT variables at all, initialize and tools/list still work, and only business tool calls initialize authentication.
After registering with Hermes Agent (config.yaml), a restart is required (MCP servers do not hot-reload). For a generic MCP client: command is venv python, args are server.py, env as above. The Docker image only installs runtime wheels from uv.lock that are compatible and hash-verified; uploaded files need to be mounted into the container and the in-container path passed to document_upload; binary downloads need /app/downloads mounted.
The default toolset is all tools. At registration, an optional comma-separated preset (union) can be used for filtering, and excluded tools are removed entirely from tools/list; --list-toolsets prints the members and default values, then exits successfully without connecting to CozyVTT. The client can append "--toolsets", "play" or set COZYVTT_MCP_TOOLSETS.
Applicable scenarios and limitations
Suitable for an AI agent acting as DM/KP running a campaign in a CozyVTT self-hosted virtual desktop. Note:
- In 0.3.0 three tools were renamed (campaign_status → campaign_get, initiative_state → initiative_read, token_hp → token_hp_update), with no aliases, so client tool selections need to be updated.
- chat_read uses limit/cursor pagination; the offset parameter was removed in 0.2.0; read the newest page first, then pass pagination.nextCursor back as cursor, and stop when it is null.
- dice_roll, chat_send, token_hp_update, initiative_manage, character_hitdice_spend return sent: true, confirmed: false status, and events_poll must be used to read business broadcasts and system.error; do not blindly replay writes.
- events_poll first returns the oldest unread event; save next_seq for the next since.
- character_update recursively merges character sheet fields before PUT; unspecified fields are preserved, arrays/scalars replace values, and null is explicit. 409 is not retried.
- After character_create creates a sheet, check the roster; if it is not confirmed as assigned, assign it; if assignment fails, the error contains the alreadycreated character ID, and the card should instead be assigned in the UI rather than created again.
- Document scope is USER/CAMPAIGN/GLOBAL; typed txt/md create/update is limited to 900 KiB UTF-8; file upload uses the instance limit (default 50 MiB); PDF content is not editable; unsharing only removes one link and cannot revoke native/global access; deletion removes the asset and all links.
- Raw document reads preserve the MIME type and ETag; PDF writes go to downloads/<document_id>.pdf; 304 returns {not_modified:true}. Downloads are ignored by Git, and local copies remain after upstream deletion.
- REST 401 invalidates existing WS authentication and campaign caches; losing campaign membership cancels WS authentication rather than reconnecting in a loop.
- character_validate always includes validation_reliable:false. initiative_read(refresh=true) requests a new state; on timeout it reports unknown.
- A missing route 404 returns "This CozyVTT instance does not provide this feature". Uploading DOCUMENT to an old upload route may return 400; do not retry.
- The compatibility table describes supported protocols and does not represent an inferred server version. The recorded DM run used CALL_OF_CTHULHU_7E in PREPARATION: 22 requests, one passive WS connection, with only authenticate/initiative.request_state sent; live campaign writes, multi-user visibility, or secret recipient isolation were not tested.
- Logs are at logs/cozyvtt-mcp.log (without passwords). Repeated 401s are rate-limited; the bridge enforces a re-login interval of at least 3 minutes. Remote deployments use HTTPS.
Offline tests block TCP connections, include real FastMCP in-memory and stdio checks, and require no campaign credentials. Live integration verification against the witnessed v1.5.0 instance was completed on 2026-10-04 using authenticated read-only REST and passive WS state reads; campaign writes or multi-user isolation were not tested. The local venv used for verification runs Python 3.12.13; Python 3.13 has not yet been verified.