Fantasy World Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Fantasy World Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A Model Context Protocol (MCP) server for procedural fantasy world generation and evolution simulation. Starting from a simple event (like "a small cave"), it simulates anthropological and geographical changes over centuries, generating rich history, dungeons, cities, and events for fantasy settings.
loadWorld tool)# Clone or navigate to the project
cd fantasy-world-mcp
# Install dependencies
npm install
# Build the project
npm run buildStep 1: Build the project:
npm run buildStep 2: Add the MCP server to your opencode config (~/.config/opencode/opencode.json):
Add this to the "mcp" section of your config:
"mcp": {
"world-evolution": {
"type": "local",
"command": ["node", "/path/to/fantasy-world-mcp/dist/index.js"]
}
}Step 3: Verify it's working:
opencode mcp listYou should see:
✓ world-evolution connected
node /path/to/fantasy-world-mcp/dist/index.jsImportant: The resources parameter must be explicitly set (even to an empty object {}) when initializing a world, otherwise the API will fail.
Step 4: Start opencode and ask your AI to create a world!
Add to your MCP client configuration:
{
"mcpServers": {
"world-evolution": {
"command": "node",
"args": ["/path/to/fantasy-world-mcp/dist/index.js"],
"cwd": "/path/to/fantasy-world-mcp"
}
}
}{
"mcpServers": {
"world-evolution": {
"command": "npx",
"args": ["-y", "tsx", "/path/to/fantasy-world-mcp/src/index.ts"]
}
}
}Start by creating a world with initial conditions:
Tool: initializeWorld
Arguments:
{
"event": "A small cave discovered by 20 refugees fleeing a great war",
"locationType": "cave",
"region": "mountains",
"climate": "temperate",
"resources": {
"iron": 60,
"stone": 80,
"food": 40,
"water": 70
},
"population": {
"name": "The Exiles",
"size": 20,
"culture": "Mountain Folk",
"organization": "tribal"
}
}Parameter Guide:
event: Free-text description of the starting eventlocationType: cave, settlement, city, dungeon, fortress, temple, village, trade_post, ruins, landmarkregion: plains, mountains, forest, desert, swamp, hills, coastal, tundra, jungleclimate: arctic, temperate, tropical, arid, continentalresources: Object with resource names (iron, gold, silver, copper, wood, stone, food, water, magic, gems) and values 0-100population.name: Name of the starting grouppopulation.size: Initial population countpopulation.culture: Cultural identitypopulation.organization: nomadic, tribal, feudal, kingdom, empireSimulate history forward in time:
Tool: simulate
Arguments:
{
"worldId": "<returned from initializeWorld>",
"timespan": 500,
"stepSize": 10,
"complexity": "moderate",
"enableConflict": true,
"enableMigration": true,
"enableTechProgress": true
}Parameter Guide:
timespan: Years to simulate (100-2000 recommended)stepSize: Years per simulation step (1-50, smaller = more detailed)complexity:simple: Basic population and resource changesmoderate: + technology, migration, location evolutioncomplex: + conflict generation between populationsenableConflict: Allow wars and disputesenableMigration: Allow population movement and new settlementsenableTechProgress: Allow technological discoveriesRetrieve the historical events:
Tool: getTimeline
Arguments:
{
"worldId": "<world ID>",
"startYear": 0,
"endYear": 500
}View the world's current state:
Tool: getWorldState
Arguments:
{
"worldId": "<world ID>"
}Create specific locations like dungeons or cities:
Tool: generateLocation
Arguments:
{
"worldId": "<world ID>",
"locationType": "dungeon",
"name": "Dark Keep",
"description": "An ancient fortress abandoned after the great war"
}Location Types: dungeon, city, village, fortress, temple, landmark
Get the world in your preferred format:
Tool: exportWorld
Arguments:
{
"worldId": "<world ID>",
"format": "gm_notes",
"includeTimeline": true,
"includeLocations": true
}Export Formats:
json: Structured data for programmatic usemarkdown: Formatted documentationnarrative: Story-style chroniclegm_notes: Game master reference with adventure hooksWhen exporting worlds after long simulations (500+ years, 100+ events), the output may exceed token limits. Use file-based export instead:
Tool: exportWorldToFile
Arguments:
{
"worldId": "<world ID>",
"format": "gm_notes",
"includeTimeline": true,
"includeLocations": true,
"filePath": "exports/myworld_1524.md" // Optional, default: exports/{worldId}_{timestamp}.{format}
}What it does:
exports/ directoryDefault file paths:
exports/{worldId}_{timestamp}.md (markdown)exports/{worldId}_{timestamp}.json (JSON)exports/{worldId}_{timestamp}.narrative (narrative)exports/{worldId}_{timestamp}.gm_notes (GM notes)Reading exported files:
Tool: readExportFile
Arguments:
{
"filePath": "exports/myworld_1524.md",
"startLine": 1,
"endLine": 100 // Optional: read in chunks
}Pagination options:
startLine/endLine: Read specific line ranges (1-indexed)startByte/endByte: Read specific byte rangescontent, totalLines, totalBytes, lineRange, byteRange, hasMoreUse case example:
1. exportWorldToFile({ worldId: "abc-123", format: "gm_notes" })
→ Returns: { filePath: "exports/abc-123_1524.gm_notes", size: 45000 }
2. readExportFile({ filePath: "exports/abc-123_1524.gm_notes", startLine: 1, endLine: 50 })
→ Returns first 50 lines + metadata (hasMore: true)
3. readExportFile({ filePath: "exports/abc-123_1524.gm_notes", startLine: 51, endLine: 100 })
→ Returns next 50 lines + metadata (hasMore: true)
4. Continue until hasMore: falseThis allows you to export and read massive worlds (10k+ tokens) without hitting MCP response limits.
Add new populations (including monsters) to an existing world:
Tool: addPopulation
Arguments:
{
"worldId": "<world ID>",
"name": "Orc Warband",
"size": 50,
"race": "monster",
"culture": "Hill Dwellers",
"organization": "tribal",
"monsterType": "orc",
"dangerLevel": 4,
"behavior": "aggressive"
}Use cases:
Create magical items, weapons, books, artifacts, and lost heritage:
Tool: createCraft
Arguments:
{
"worldId": "<world ID>",
"name": "Sword of the Dawn",
"description": "A legendary blade forged in the first light of the new age",
"category": "weapon",
"rarity": "legendary",
"requiredTechLevel": 6,
"requiredResources": { "iron": 50, "magic": 30, "gems": 20 },
"creatorPopulationId": "<population ID>",
"location": "<location ID>",
"isHidden": false,
"effects": ["+3 damage", "glows in darkness", "burns undead"]
}Categories: weapon, armor, tool, artifact, book, jewelry, structure, relic Rarities: common, uncommon, rare, legendary, mythic
Hidden Heritage: Set isHidden: true to create lost items that become adventure hooks:
The simulation includes a complete religion/belief system that affects gameplay:
Belief Types:
Faith Mechanics:
Example:
Population: "The Dwarven Clan"
Belief: "The Stone Father" (Monotheism, domains: fortress, war)
Defense Bonus: +0.15 (organized religion with war domain)
Holy Site: "Mountain Temple" (+0.10 defense when defending this location)The simulation automatically generates serious quests when populations face problems they cannot solve:
Auto-Generated Quest Types:
Quest Properties:
Example:
CRITICAL QUEST: "Cure the Blazing Fever"
- A terrible plague is sweeping through the kingdom
- Physicians are powerless
- Heroes must find a cure in ancient texts or distant lands
- Deadline: 20 years
- Failure: Kingdom decimated, cities become ghost towns
- Success: Kingdom survives, heroes honored for generationsComplete Quests:
Tool: completeQuest
Arguments:
{
"worldId": "<world ID>",
"questId": "<quest ID>",
"success": true,
"completionNotes": "The heroes traveled to the Mountain Temple and retrieved the Sacred Herb"
}Quests appear in GM Notes exports and are prioritized by urgency. Critical quests are highlighted for immediate player attention.
MCP servers are in-memory only. When the server restarts, worlds are lost. Use loadWorld to restore previously saved worlds:
Step 1: Save world data after creation/simulation:
// After getWorldState or exportWorld, the AI should store the full world JSON
const worldData = JSON.stringify(world);
// AI stores this in its conversation contextStep 2: After restart, load the world:
Tool: loadWorld
Arguments: {
"worldData": "{\"id\":\"abc-123\",\"timestamp\":400,\"society\":{...},...}"
}Step 3: Continue simulation:
Tool: simulate
Arguments: {
"worldId": "abc-123",
"timespan": 100
}The AI automatically handles this - just say "resume my world" or "continue the simulation" and it will use loadWorld() with the saved data.
Here's a complete example of generating a fantasy setting:
// 1. Create the world with humans
const world = await initializeWorld({
event: "A city founded by refugees near a river",
locationType: "city",
region: "plains",
climate: "temperate",
resources: { food: 70, wood: 60, water: 80 },
population: {
name: "River's Edge",
size: 5000,
culture: "Riverfolk",
organization: "feudal"
}
});
// 2. Add orc threat (not available at creation)
await addPopulation({
worldId: world.worldId,
name: "Orc Warband",
size: 50,
race: "monster",
culture: "Hill Dwellers",
organization: "tribal",
monsterType: "orc",
dangerLevel: 4,
behavior: "aggressive"
});
// 3. Simulate 100 years of conflict
const history = await simulate({
worldId: world.worldId,
timespan: 100,
stepSize: 10,
complexity: "complex"
});
// 4. Export for your campaign
const campaignNotes = await exportWorld({
worldId: world.worldId,
format: "gm_notes"
});Key insight: Add monsters/populations with addPopulation() instead of trying to specify them at creation. This gives you control over timing and numbers.
The AI can create creative items during simulation:
// After simulating 100 years, the AI might create:
// 1. A magical weapon
await createCraft({
worldId: world.worldId,
name: "Dragonbane",
description: "A greatsword forged from dragon-forged steel, humming with ancient power",
category: "weapon",
rarity: "legendary",
requiredTechLevel: 7,
requiredResources: { iron: 80, magic: 50, gems: 30 },
creatorPopulationId: "pop_123",
effects: ["+5 damage vs dragons", "burns with blue flame"]
});
// 2. A lost book
await createCraft({
worldId: world.worldId,
name: "Tome of the First Kings",
description: "Ancient scroll containing the lost history of the first civilization",
category: "book",
rarity: "mythic",
requiredTechLevel: 5,
creatorPopulationId: "pop_456",
isHidden: true, // Players must find it!
effects: ["reveals hidden truths", "grants +2 to history checks"]
});
// 3. Defensive structure
await createCraft({
worldId: world.worldId,
name: "Iron Barricade of River's Edge",
description: "Massive fortified wall reinforced with magical wards",
category: "structure",
rarity: "uncommon",
requiredTechLevel: 4,
requiredResources: { iron: 60, wood: 40, stone: 50 },
creatorPopulationId: "pop_123",
effects: ["+50% defense against raids"]
});These crafts appear in GM Notes exports and generate adventure hooks like:
| Aspect | Traditional Script | AI + MCP Server |
|---|---|---|
| Input | Fixed parameters/JSON | Natural language |
| Flexibility | Predefined rules only | Understands intent, adapts |
| Iteration | Re-run with new config | "Make orcs more hostile" |
| Context | Stateless | Remembers, builds coherence |
| Output | Fixed format | Adapts to your needs |
| Creativity | Deterministic | Unexpected connections |
The AI stores world data in its own context, not in the MCP server:
worldId + full world JSONloadWorld() with the saved JSON dataWhy this design?
#### Script Approach (Traditional)
{ "monsterCount": 2, "timespan": 400, "enableConflict": true }→ Same output every time with same seed. Limited to predefined options.
#### AI Approach (This System)
Example 1: Dramatic Tension
"Create a world where ancient dragons are waking up after 500 years
of dormancy, and the human kingdom doesn't know the threat yet"→ AI adjusts monster behavior (dormant dragons), creates foreshadowing events, generates adventure hooks about "strange tremors" and "sheep disappearing"
Example 2: Iterative Refinement
"Actually, make the orc kingdom more aggressive and have them
raid the dwarven mines every 50 years"→ AI modifies monster behavior, adjusts raid frequency, creates specific conflict events without re-running everything
Example 3: Contextual Export
"Export this as handouts for my players, hiding the dragon threat"→ AI creates player-friendly version, omits DM-only information, formats as in-world documents
Example 4: Resuming After Restart
"Load my world from last session"→ AI retrieves saved JSON from context, calls loadWorld(savedData), continues simulation where it left off
This isn't just a generator—it's a simulation engine that:
loadWorld() restores any saved worldWithout AI: You'd need to write code to tweak anything With AI: You just ask, and it uses the tools
The engine simulates these anthropological and geographical processes:
Populations discover technologies through a dynamic progression system based on multiple factors:
Tech Progression Formula:
Complete Tech Tree:
| Level | Technologies | Era |
|---|---|---|
| 0 | Stone Tools, Fire Mastery, Basic Shelter | Stone Age |
| 1 | Language Development, Social Cooperation | Early Society |
| 2 | Agriculture, Pottery, Domestication, Basic Medicine | Neolithic |
| 3 | Bronze Working, Wheel, Writing, Irrigation, Mining | Bronze Age |
| 4 | Iron Working, Architecture, Mathematics, Law | Iron Age |
| 5 | Steel, Navigation, Philosophy, Advanced Medicine | Classical |
| 6 | Gunpowder, Printing, Telescope, Banking | Medieval |
| 7 | Industrial Revolution, Steam Power, Electricity | Early Modern |
| 8 | Telegraph, Railways, Mass Production | Industrial |
| 9 | Electricity Grid, Internal Combustion, Aviation | Modern |
| 10 | Modern Computing, Internet, Space Technology | Contemporary (cap) |
Tech Prerequisites:
Tech Milestones: When a population reaches a new tech level, a TECH_MILESTONE event is created in the timeline, marking significant societal advancement.
Tech Level Effects:
Relics and Tech: Populations with legendary or mythic crafts/artifacts receive a +5% bonus to tech progression, representing the knowledge and inspiration gained from powerful items.
Age of Discovery (0-50): Key developments: Beginning
Age of Settlement (50-100): Key developments: Earthquake, Agriculture
Age of Expansion (100-200): Key developments: Pottery, Migration
Age of Kingdoms (200-300): Key developments: Iron Working, Village Founded
Age of Empires (300-500): Key developments: City Founded, Writing1. The iron mines are running dry. Adventurers must find new sources.
2. Strange magical phenomena are appearing near the Luminary Depths.
3. The war between Mountain Clan and River Tribes is escalating.
4. Ancient ruins have been disturbed. Something ancient has awakened.Important: The MCP server maintains state during a single opencode session. Keep track of the worldId returned from initializeWorld and use it in subsequent calls.
resources: {}: "Create a fantasy world starting with a cave"With monsters:
"Create a world with dwarves and humans, enable 2 monsters (a dragon and orcs), simulate 400 years"The AI should call:
initializeWorld({
event: "a mysterious cave in the mountains",
locationType: "cave",
region: "mountains",
climate: "temperate",
population: [
{name: "Mountain Exiles", size: 25, race: "human", culture: "Highlanders", organization: "tribal"},
{name: "Dragon Horde", size: 15, race: "monster", monsterType: "dragon", dangerLevel: 8, behavior: "aggressive"}
],
resources: {}, // Required - can be empty
enableMonsters: true, // Enable monster spawning
monsterCount: 1 // Number of auto-generated monsters
})12bff51b-... "Simulate world 12bff51b-... for 500 years" "Export world 12bff51b-... as GM notes"Pro tip: Ask the AI to "remember the world ID" or "keep track of the world" to maintain context across multiple tool calls.
The world can include monster populations that act as threats to civilizations:
enableMonsters: true and monsterCount: 2 to auto-generate monstersMCP servers are in-memory only. When opencode restarts, worlds are lost. But the AI can restore them:
Step 1: AI saves world data after creation/simulation:
AI stores in context:
{
"myWorld": {
"id": "abc-123",
"timestamp": 400,
"society": {...},
"events": [...],
...
}
}Step 2: After restart, AI loads the world:
Tool: loadWorld
Arguments: {
"worldData": "{\"id\":\"abc-123\",\"timestamp\":400,...}"
}Step 3: Continue simulation:
Tool: simulate
Arguments: {
"worldId": "abc-123",
"timespan": 100
}The AI automatically handles this - just say "resume my world" or "continue the simulation".
If your opencode setup uses an external API:
.env.local with your API settings: API_BASE_URL=https://your-api-endpoint.com/v1
API_KEY=your-api-key
API_MODEL=your-model-nameseed parameter for reproducible resultstimespan or decrease stepSizecomplexity to "moderate" or "complex"complexity: "complex"json for data, markdown for readingincludeTimeline: false for shorter output# 1. Build the project
npm run build
# 2. Add to opencode config (see Configuration section above)
# 3. Verify:
opencode mcp list
# 4. Start opencode and ask your AI:
# "Create a fantasy world starting with a cave"# Build
npm run build
# Run tests
npm run test
# Start server directly
npm start
# Task Management
npm tasks # List all development tasks
node scripts/transition.cjs <id> in_progress # Mark task as in progress
node scripts/complete.cjs <id> "message" # Complete task and commitThis project uses a task-based workflow for development:
tasks/ directory (ignored by git).task.json files for each taskpending, in_progress, completednpm run task:transition <id> in_progressnpm run task:complete <id> "commit message"See tasks/README.md for full documentation.
ISC
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.