Atlas / MCP servers / laurigates / Foundry

FoundryBLOCK

mcp/laurigates/foundry-2

A Model Context Protocol (MCP) server that integrates with FoundryVTT, allowing AI assistants to interact with your tabletop gaming sessions. Query actors, roll dice, generate content, and manage your game world through natural language.

Verdict
BLOCK
Grade
D
Trust score
63 /100
Exposed tools
25 19r · 4w · 2d
Transport
stdio
License
MIT
Stars
33
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

[](https://www.npmjs.com/package/foundryvtt-mcp) [](LICENSE)

A Model Context Protocol (MCP) server that integrates with FoundryVTT, allowing AI assistants to interact with your tabletop gaming sessions through natural language.

Features

  • Dice Rolling — standard RPG notation with any formula
  • Data Querying — search and inspect actors, items, scenes, journals
  • Game State — combat tracking, chat messages, user presence
  • Content Generation — NPCs, loot tables, rule lookups
  • World Search — full-text search across all game entities
  • Live Connection — Socket.IO loads complete world state on connect
  • MCP Resources — foundry:// URIs for direct data access
  • Diagnostics — optional server health monitoring (requires REST API module)

Quick Start

Prerequisites

  • Node.js 18+ (or Bun)
  • FoundryVTT server running with an active world
  • MCP-compatible AI client (Claude Desktop, Claude Code, VS Code, etc.)

Recommended: Create a Dedicated API User

It is recommended to create a separate FoundryVTT user account for the MCP server rather than using your own GM or player account. This provides better security and auditability.

In FoundryVTT:

  1. Go to Configuration → User Management
  2. Click Create User
  3. Set a username (e.g., mcp-api) and a strong password
  4. Assign the Assistant GM role (needed to read world data and roll dice)
  5. Use this account's credentials in your MCP configuration

Benefits:

  • Chat messages and actions from the MCP server are clearly attributed to a separate user
  • You can revoke access by disabling the API user without affecting your own account
  • Limits blast radius if credentials are ever exposed

Installation

Run directly without installing — no clone needed:

Read from source at commit f1026e365041OBSERVED · 2026-10-08
Advertisement
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code
claude mcp add foundryvtt-mcp --env FOUNDRY_API_KEY=${FOUNDRY_API_KEY} --env FOUNDRY_PASSWORD=${FOUNDRY_PASSWORD} -- npx -y [email protected]
claude-desktop
{
  "mcpServers": {
    "foundryvtt-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "[email protected]"
      ],
      "env": {
        "FOUNDRY_API_KEY": "${FOUNDRY_API_KEY}",
        "FOUNDRY_PASSWORD": "${FOUNDRY_PASSWORD}"
      }
    }
  }
}
03

Exposed tools (25)

19 read · 4 write · 2 destructive. Blast radius: 2 tools can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.

ToolRiskDescription
UsersreadList of users and their online status
apply_status_effectdestructiveApply or remove a status condition (e.g.
delete_actor_itemdestructivePermanently remove an item owned by an actor. Use when: the user explicitly asks to delete or discard a specific owned item, and has given you the itemId - no tool in this server lists owned-item ids, so never guess one. Do not use when: only the item
diagnose_errorsreadStub: returns a fixed
end_combatreadEnd the active combat encounter by deleting its Combat document, discarding the initiative order and round count. Use when: the fight is over and the user asks to end the encounter.
generate_npcreadGenerate a random NPC (name, race, class, HP, ability scores, background) as text. Use when: the user needs a throwaway NPC on the spot. Do not use when: the NPC must exist in FoundryVTT - this creates no documents, and the result still has to be entered into the world by hand.
get_actor_detailsreadGet details for one actor by id: type, level, current/max HP, AC and ability scores; no biography or description text is returned (the description line always reads
get_chat_messagesreadGet the most recent chat messages from the game log. Use when: you need recent in-game context - what players said, or roll results that already happened.
get_combat_statewriteGet the active combat encounter: initiative order with each combatant
get_health_statusreadGet a combined health report: MCP-to-FoundryVTT connection state, world title/system/core version, and the server
get_journalreadGet one journal entry by id with the text of its pages. Page bodies are HTML-stripped and each is truncated to its first 500 characters, marked with a trailing
get_recent_logsreadGet recent FoundryVTT server log entries, optionally filtered by level or since a timestamp. Use when: investigating an error or recent server behaviour. Do not use when: you have a specific term to look for - use search_logs. Requires the REST API module (FOUNDRY_API_KEY); fails without it.
get_scene_inforeadGet details of the active scene, or of a specific scene by id: name, scene id, active/navigation flags, pixel dimensions, padding and lighting (global light, darkness); no description text is returned unless a module has set a description flag on the scene (the description line otherwise reads
get_system_healthreadGet the FoundryVTT server
get_usersreadList the world
get_world_summaryreadGet world metadata (title, game system, core version) and per-collection document counts. Use when: orienting yourself in an unfamiliar world, or confirming the game system before system-specific edits.
lookup_rulereadStub: builds a templated placeholder from the query and consults no rules source, so the text it returns carries no rules content. No tool in this server looks rules up.
next_turnreadAdvance the active combat to the next turn, wrapping to the next round after the last combatant. When skipDefeated is true, defeated combatants are skipped. Use when: the current combatant has finished their turn. Do not use when: only reporting the turn order - use get_combat_state.
search_actorsreadSearch actors (player characters, NPCs) by name, optionally filtered by type. Returns a summary line per match: name, type, level and current/max HP. Use when: checking whether an actor exists, or getting a quick roster with HP. Do not use when: you already have the actorId and want that actor
search_itemsreadSearch item documents in the world by name, optionally filtered by type. Returns name, type and rarity per match (rarity shows
search_journalsreadSearch journal entries by name and page content. Use when: looking for notes, lore, or handouts by keyword. Do not use when: you already have the journalId - use get_journal.
search_logsreadSearch the FoundryVTT server logs for a query string and list the matching entries. Use when: hunting a specific error message, stack trace, or module name. Do not use when: you just want the latest entries - use get_recent_logs. The reported match count is the server\
set_initiativewriteSet a combatant
update_actor_attributeswriteUpdate fields on an actor\
update_actor_itemwriteApply a JSON merge patch to an item
04

Trust audit

BLOCKgrade D · trust 63/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeFAIL
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfaceWARN
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (1 observation(s))
Network
declared (9 observation(s))
Shell
declared (3 observation(s))
Dependencies
not all pinned
Secrets in source
none-found

Findings (25)

HIGHCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
justfile:239
if check 'eval($$$)'; then ((issues+=1)); else echo "None found"; fi
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
MEDIUMInformation disclosure · disclose.log_secret · CWE-209, CWE-532
scripts/diagnose-api.ts:77
console.log(`   API Key: ${API_KEY ? '✅ Configured' : '❌ Not set'}\n`);
MEDIUMInformation disclosure · disclose.log_secret · CWE-209, CWE-532
scripts/test-connection.ts:56
console.log(`   Transport: ${config.foundry.apiKey ? '🌐 REST API' : '🔌 Socket.IO'}`);
MEDIUMInformation disclosure · disclose.log_secret · CWE-209, CWE-532
scripts/test-connection.ts:57
console.log(`   API Key: ${config.foundry.apiKey ? '✅ Configured' : '❌ Not set'}`);
MEDIUMInformation disclosure · disclose.log_secret · CWE-209, CWE-532
scripts/test-connection.ts:144
console.log(`   - Data access: ${config.foundry.apiKey ? '🌐 REST (coverage depends on the target module; Socket.IO is the full-data path)' : '✅ Full worldData'}`);
MEDIUMInformation disclosure · disclose.log_secret · CWE-209, CWE-532
scripts/test-connection.ts:145
console.log(`   - Connection: ${config.foundry.apiKey ? '🌐 REST API' : '🔌 Socket.IO'}`);
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
.env.example:14
# FOUNDRY_URL=http://192.168.1.100:30000
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
scripts/setup-wizard.ts:122
'http://127.0.0.1:30000',
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
scripts/setup-wizard.ts:152
console.log('  • http://192.168.1.100:30000 (local network IP)\n');
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
scripts/setup-wizard.ts:178
console.log('  • http://192.168.1.100:30000 (network IP)');
MEDIUMObfuscation / stealth · obf.zero_width · CWE-506, CWE-94
scripts/setup-wizard.ts:39
console.log('🧙♂️ FoundryVTT MCP Setup Wizard\n');
MEDIUMFilesystem / path · mcp.destructive_tools · CWE-22, CWE-59
apply_status_effect, delete_actor_item
Why it matters. 2 tool(s) can delete or overwrite
Fix. prefer a read-only mode or scoped tokens; the page states the blast radius
LOWInventory / provenance · inv.hidden_file · CWE-1104
.env.integration.example
.env.integration.example
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.gitleaks.toml
.gitleaks.toml
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.project-standards.yaml
.project-standards.yaml
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.release-please-manifest.json
.release-please-manifest.json
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
docs/.nojekyll
.nojekyll
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/diagnostics/__tests__/client.test.ts:12
import type { FoundryClient } from '../../foundry/client.js';
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/diagnostics/__tests__/client.test.ts:17
vi.mock('../../foundry/client.js');
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/diagnostics/__tests__/client.test.ts:18
vi.mock('../../utils/logger.js', () => ({
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/foundry/__tests__/auth.test.ts:21
vi.mock('../../utils/logger.js', () => ({
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/foundry/__tests__/auth.test.ts:31
const { logger } = await import('../../utils/logger.js');
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
SETUP_GUIDE.md:75
FOUNDRY_URL=http://192.168.1.100:30000
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
package.json
@modelcontextprotocol/sdk, ajv, axios, dotenv, socket.io-client, zod, @biomejs/biome, @playwright/test
Why it matters. 16 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWPrompt injection · prompt.credential_read · CWE-94, CWE-1427
CHANGELOG.md:37
* **auth:** send the Foundry session as a Cookie header, and load .env before config ([#210](https://github.com/laurigates/foundryvtt-mcp/issues/210)) ([b3fd356](https://github.com/laurigates/foundryv
Why it matters. asks the agent to read credentials

Gates applied: no_behavioural_pass.

Audited 2026-10-08 · audit v0.4.1 · source sha f1026e365041full audit observations/trust-audit/mcp-server/laurigates__foundry-2.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-08f1026e365041BLOCKD63first audit
06

Questions

What is the Foundry MCP server?

A Model Context Protocol (MCP) server that integrates with FoundryVTT, allowing AI assistants to interact with your tabletop gaming sessions. Query actors, roll dice, generate content, and manage your game world through natural language.

What tools does Foundry expose?

25 in total: 19 read-only, 4 that write, and 2 that can delete or overwrite (apply_status_effect, delete_actor_item). Every one is listed on this page with its risk.

Is Foundry safe to connect to an agent?

No — not without reading the findings first. The audit graded it D (63/100) and found 1 critical or high issue in the source. Each one is listed on this page with the file and line it is on. Separately from the audit: 2 of its tools can destroy data, so scope the token you give it to what you actually need.

What credentials does Foundry need?

It reads FOUNDRY_API_KEY and FOUNDRY_PASSWORD from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How does Foundry run?

It speaks stdio, so it runs as a local process your client starts. It is published on npm as foundryvtt-mcp at 1.5.3.

How current is this page?

The grade is for one exact copy of the source (f1026e365041), read on 2026-10-08. The repository is watched and re-audited when it changes.

Advertisement