Skip to main content
Glama
foundry-rest-api-mcp-server

foundry-rest-api-mcp-server

foundry-rest-api-mcp-server

An MCP (Model Context Protocol) server that wraps the ThreeHats Foundry VTT REST API relay, so an LLM agent (Claude Code / Claude Desktop) can author and manage Foundry VTT content — primarily creating D&D 5e creatures — through native MCP tools instead of raw HTTP calls.

Status: V1 implemented. The stdio MCP server lives under src/ with unit tests under test/. See Install & configure below to run it. The original plan that drove the build is in IMPLEMENTATION_PLAN.md.


What this is

The relay (github.com/ThreeHats/foundryvtt-rest-api-relay, public host https://foundryrestapi.com) bridges a running Foundry world to a REST API over a WebSocket. A Foundry-side module connects out to the relay; external clients then call REST endpoints, authenticating with an x-api-key header and targeting a world via a clientId query param.

This MCP server is a thin, typed client of that relay. It does not talk to Foundry directly and requires no Foundry-side code of its own (the relay's module handles that). That is the key differentiator from existing Foundry MCP servers, which all ship a custom Foundry module + socket (see IMPLEMENTATION_PLAN.md § Prior Art).

Related MCP server: mcp-llm

v1 scope (locked)

v1 implements the relay's document-authoring surface — these five scopes only:

Scope

Endpoints

Capability

clients:read

GET /clients

Discover connected worlds → pick clientId

search

GET /search

Find actors/items/etc. by name (world + compendiums)

entity:read

GET /get

Read any document (by UUID or current selection)

entity:write

POST /create, PUT /update, DELETE /delete, /give, /remove, /increase, /decrease, /kill

Create / update / delete documents; tweak actor inventory & attributes

structure:write

POST /create-folder, DELETE /delete-folder

Organize content into folders

This delivers the headline use case end-to-end: build a dnd5e NPC/monster (abilities, HP, AC, CR, traits, embedded attacks) as a real Foundry actor, search/read existing creatures to clone-and-tweak, and file everything into folders.

Everything else in the relay (dice rolls, chat, encounters/combat, scenes, canvas/tokens, effects, files, users, macro execution, streaming events) is out of v1 scope and documented as clearly-marked Future Phases — each unlocks by adding one scope and one tool module.

Optional companion: adding structure:read (GET /structure, GET /get-folder) would let us find existing folders, not just create them. Cheap and read-only; deferred for now.

Architecture decisions (the short list)

  • Language/runtime: TypeScript, Node ≥ 18 (target Node 20 LTS), ESM-only.

  • SDK: @modelcontextprotocol/sdk@^1.29 (the shipped 1.x line). The renamed V2 SDK (@modelcontextprotocol/server) is not stable until ~mid-2026; migration later is mechanical. Build on 1.x now.

  • Transport: stdio.

  • HTTP: native fetch (no axios).

  • Validation: zod (peer of the SDK).

  • Build/package: tsup → single npx-runnable bin.

  • Config: env vars — FOUNDRY_API_KEY (the x-api-key), FOUNDRY_RELAY_URL (default https://foundryrestapi.com), FOUNDRY_CLIENT_ID (optional; auto-resolves if one world is online), FOUNDRY_USER_ID (optional; omit = GM-level).

  • Tool surface: small set of mostly single-purpose tools (~9 in v1), façades only where operations are homogeneous. Per-endpoint-explosion is explicitly avoided — LLM tool-selection reliability degrades past ~30–40 tools and every tool is permanent context cost.

Install & configure

Build from source

npm install
npm run build      # → dist/index.js (executable, #!/usr/bin/env node)
npm test           # unit tests (mocked fetch)
npm run inspect    # launch the MCP Inspector against dist/index.js

Environment variables

Var

Required

Default

Purpose

FOUNDRY_API_KEY

yes

—

Sent as the x-api-key header. Must include all five v1 scopes (see below).

FOUNDRY_RELAY_URL

no

https://foundryrestapi.com

Relay base URL. Set to http://localhost:3010 for a self-hosted relay.

FOUNDRY_CLIENT_ID

no

auto-resolves

Which connected world to target. Auto-resolves when exactly one world is online.

FOUNDRY_USER_ID

no

GM-level

Scope actions to a Foundry user's permissions. Omit for GM access.

On startup the server logs the online world(s) to stderr and disables foundry_create_creature if the active world's system isn't dnd5e.

Tools (v1)

foundry_list_worlds · foundry_search · foundry_get_entity · foundry_create_entity · foundry_update_entity · foundry_delete_entity · foundry_modify_actor · foundry_create_creature · foundry_manage_folder

Claude Code (.mcp.json)

Add to .mcp.json in your project root (or via claude mcp add):

{
  "mcpServers": {
    "foundry": {
      "command": "node",
      "args": ["/absolute/path/to/foundry-rest-api-mcp-server/dist/index.js"],
      "env": {
        "FOUNDRY_API_KEY": "your-scoped-key",
        "FOUNDRY_CLIENT_ID": "fvtt_8bfa06d76c0c1ac5"
      }
    }
  }
}

Claude Desktop (claude_desktop_config.json)

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "foundry": {
      "command": "node",
      "args": ["/absolute/path/to/foundry-rest-api-mcp-server/dist/index.js"],
      "env": {
        "FOUNDRY_API_KEY": "your-scoped-key",
        "FOUNDRY_CLIENT_ID": "fvtt_8bfa06d76c0c1ac5"
      }
    }
  }
}

Once published to npm, replace "command": "node", "args": ["…/dist/index.js"] with "command": "npx", "args": ["-y", "foundry-rest-api-mcp-server"].

Live smoke test (optional)

A gated end-to-end test creates then deletes a throwaway JournalEntry against the real relay:

FOUNDRY_LIVE_TEST=1 FOUNDRY_API_KEY=... FOUNDRY_CLIENT_ID=... npm test

How to use this repo (for the implementing agent)

  1. Read IMPLEMENTATION_PLAN.md top to bottom. Work the phases in order; each step lists its goal, the files it touches, the exact work, and an acceptance check.

  2. Read EXECUTION_STRATEGY.md for how to run the build — recommended model/effort per ticket, the sequential-foundation → parallel-fan-out → sequential-convergence order, and the branch-per-work-stream (trunk-based) strategy with worktrees + CI gates.

  3. Use API_SPEC.md as the authoritative endpoint reference (request/response shapes, scopes, quirks, the dnd5e NPC schema notes).

  4. Pull granular, ordered tasks from the V1 project board / issues (milestone V1 — Content Authoring). Each issue carries goal, endpoint+scope, file paths, acceptance criteria, and Depends on #N.

  5. The reference/ folder holds the relay's own docs/examples, mirrored verbatim from the upstream MIT-licensed repo for offline ground-truth (attribution in reference/SOURCE.md; regenerate with scripts/fetch-reference.sh). Filenames mirror their repo paths (e.g. docs_md_api_entity.md, docs_examples_dnd5e-examples.json).

Decision records & specs

  • decisions/ — architecture & management decisions as ADRs, numbered NNNN-*.md. Each captures one decision (status · context · decision · consequences) and is immutable once Accepted; a later ADR supersedes an earlier one by reference, so history is never edited in place. Index + convention: decisions/README.md.

  • specs/ — technical component specs, numbered NNNN-*.md — the internal contracts the code implements (relay client, tool surface, dnd5e NPC builder). Index: specs/README.md.

The top-level docs (IMPLEMENTATION_PLAN.md, EXECUTION_STRATEGY.md, API_SPEC.md) are the elaborated references; the ADRs are the atomic, supersede-able record of why.

Getting a properly-scoped API key (do this before any write works)

A read-only key returns 403 API key lacks required scope: entity:write. You cannot add scopes to an existing key value — mint a new one that includes all five v1 scopes:

  • Dashboard: log in at https://foundryrestapi.com → API Keys → Create Scoped Key → tick clients:read, search, entity:read, entity:write, structure:write → copy the key (shown once).

  • Programmatic (device flow): POST /auth/key-request with {"appName":"foundry-mcp","scopes":["clients:read","search","entity:read","entity:write","structure:write"]} → open the returned approvalUrl → poll GET /auth/key-request/:code/status until approved (the response then includes apiKey).

The known target world for this project is "Wilds Beyond the Witchlight", clientId fvtt_8bfa06d76c0c1ac5 (dnd5e 5.2.2, Foundry v13.351).

Available Tools

9 tools
foundry_create_creatureCreate dnd5e NPCA

Build a dnd5e 5th-edition NPC actor from a friendly schema and create it in Foundry. Returns the new actor UUID and document. Only available when the connected world is dnd5e. ⚠️ Attack schema targets dnd5e v5.2.2 activities model — verify against a live NPC if attacks look wrong.

Inputs: ac (required), cr (required), hp (required), name (required), +13 more.

foundry_create_entityCreate Foundry EntityA

Create a new Foundry entity (Actor, Item, JournalEntry, etc.) with a raw Foundry document. Returns the new entity UUID and document.

Inputs: data (required), entityType (required), folder, keepId, +3 more.

foundry_delete_entityDelete Foundry EntityA
Destructive

Permanently delete a Foundry entity by UUID or current selection. This action is irreversible.

Inputs: uuid, userId, clientId, selected.

foundry_get_entityGet Foundry EntityA
Read-only

Retrieve a Foundry entity by UUID, or the currently selected token/actor. Returns the full document including system data and embedded items.

Inputs: uuid, actor, userId, clientId, +1 more.

foundry_list_worldsList Foundry WorldsA
Read-only

List all Foundry VTT worlds/clients connected to the relay, including their online status, system, and version info.

Inputs: no named parameters.

foundry_manage_folderManage Foundry FolderA
Destructive

Create or delete a Foundry folder. After creating a folder, pass the returned uuid as the folder argument when creating entities. Limitation: finding existing folders requires structure:read (not in v1 scope) — only freshly-created folder UUIDs are known.

Inputs: action (required), name, userId, clientId, +4 more.

foundry_modify_actorModify Foundry ActorA

Give/remove items, increase/decrease numeric attributes, or kill (set HP to 0) an actor. Use action to select the operation.

Inputs: action (required), uuid, amount, toUuid, +9 more.

foundry_update_entityUpdate Foundry EntityA
Idempotent

Update an existing Foundry entity by UUID or current selection. Pass only the fields to change.

Inputs: data (required), uuid, actor, userId, +2 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 9 tools

    Disambiguation4/5

    Most tools have distinct purposes (search vs get vs delete vs create), but there is some overlap between generic update_entity and specialized modify_actor, and between create_entity and create_creature. Descriptions help clarify boundaries, though agents might occasionally misselect.

    Naming Consistency4/5

    Tool names follow a consistent 'foundry_' prefix with snake_case, and most use verb_noun patterns (list_worlds, create_entity, update_entity). Minor deviations: 'foundry_search' lacks a noun, and 'foundry_manage_folder' uses the vaguer verb 'manage' instead of create/delete.

    Tool Count5/5

    Nine tools is well-scoped for a Foundry REST API server, covering core entity operations, world listing, search, and specialized dnd5e helpers without unnecessary bloat.

    Completeness4/5

    The set provides full CRUD for entities, search, and world listing, plus useful specialized tools for actor modification and creature creation. Minor gaps exist like no generic list_entities operation, but agents can work around these.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers