Skip to main content
Glama
The Fortuneteller's Hand

The Fortuneteller's Hand

The Fortuneteller's Hand — an RPG MCP Server

An MCP server that acts as a role playing game and GM using the rules of The Fortuneteller's Hand, an original tabletop RPG that I am currently working on. Claude reads the cards, casts the omens, and runs the table; the player and Claude will take turns narrating the story that develops.

What playing feels like

A short version, before you install anything: you sit with the Fortuneteller first — where you meet them is the one choice made before any card is drawn. Four suits become four facets of who you are; the Fortuneteller draws one card from each and speaks what it reveals, and you build a character from those four phrases. Once you have your character, you must interpret your Goal card before you set out.

From there, every scene works the same way: decide where you are, flip a card to see what's stirring, narrate forward. The moment the story reaches another heart, something hidden, or needs a turn of luck, you stop and ask fate directly by calling an Omen. Fate can turn against you, too; when it does, you can fight it.

This is, at heart, a creative journaling game. The real pleasure is in the narration you and the Fortuneteller build together, not in winning anything. Motivation, Ends, Seek, and Carry aren't rules to obey; they're seeds, four starting points for your character's psychology and the shape of the story ahead. Follow them where they lead, or let the story wander somewhere else. The more you describe, the more the story gives back.

That's the shape of it. Full mechanics in RULES.md; a gentler, fuller walkthrough in HOW_TO_PLAY.md; a full transcript of an actual session in 08-2026-Play-Example.md. If you ever lose track of your Goal or your four facets partway through a story, just ask the Fortuneteller to remind you.

Related MCP server: mcp-tarot-draw

Running it

Quick install

  1. Download fortunetellers-hand-0.1.1.mcpb somewhere findable.

  2. Double-click it (don't drag it into a chat window — just double-click the file itself). It'll open an install prompt in Claude Desktop.

  3. Confirm the install.

  4. Fully quit and reopen Claude Desktop.

  5. Start a new chat, and pull in the start_game prompt — it'll ask for a character name, then you're off.

Run from source

Requires Python and uv.

  1. uv sync inside this directory.

  2. In Claude Desktop: Settings → Developer → Edit Config, and add:

    {
      "mcpServers": {
        "fortuneteller": {
          "command": "uv",
          "args": ["run", "--directory", "/absolute/path/to/learnMCP", "main.py"]
        }
      }
    }
  3. Fully quit and restart Claude Desktop.

  4. Click the + icon near the message box → Connectors → enable "fortuneteller."

  5. Start a new chat and pull in the start_game prompt.

  6. When it asks for your character's name (player_name), fill that in.

  7. Once the prompt attaches as a chip above the message box, click send or hit enter — attaching it doesn't submit it on its own.

Influences

This project draws from a few places. The Omens' two-dice reading, comparing rather than summing, owes a real debt to Ironsworn by Shawn Tomkin, which I absolutely LOVE. The Defiance ritual, "Not Like This," borrows its five-dice, three-roll, keep-what-you-want shape from Yahtzee (which was stolen from the Puerto Rican game "Jenerala"). As a journaling game, this owes a lot to Thousand Year Old Vampire by Tim Hutchings, one of the games that proved solo, prompt-driven play could carry real weight. Incredible game! And finally, Max Moon has shaped the tone of this project throughout. I adore everything Max Moon creates.

Curious about the build?

This started as a way to learn the Model Context Protocol in a way that would interest me. The tool/resource/prompt choices, the state design, a few real bugs found and fixed along the way, these are all written up in DESIGN.md. If you have any questions or wish me to fix, or add anything, please contact me.

Privacy Policy

  • Data collection: This MCP collects nothing.

  • Usage and storage: Nothing is stored. All game state (your character, cards drawn, debt owed) exists only in memory for the length of your session and is discarded when it ends.

  • Third-party sharing: Nothing is shared with any third party, because nothing is collected in the first place.

  • Data retention: Nothing is retained.

  • Contact information: maria@mariasaha.com

Available Tools

11 tools
call_the_handCall the HandA

Trigger the Called Hand: an omen rolled doubles, so fate intrudes on the scene instead of answering the question.

Call immediately whenever cast_omen returns doubles=True — this isn't
the player's to invoke, Claude always calls it. Plays the oldest
outstanding debt card if any are owed (owed debt always comes due
first), otherwise deals a fresh card from the Fortuneteller's Hand
deck. Either way the landing must intrude on the scene — it costs
something, and the original question the omen asked stays unanswered.
If the player wants to fight the landed fortune, that's when they call
defiance (see defy_roll).

Takes no parameters. Not idempotent: mutates the shared debt row
(pops the oldest card) or the shared Hand deck (deals a fresh card).
Returns a dict with source ("debt_row" or "fresh_deal"), card (the
landed card's rank/suit/phrase/etc.), and a fixed note reminding you
the landing must intrude and the question stays unanswered.

Inputs: no named parameters.

cast_omenCast OmenA

Cast an omen: roll two ten-sided dice (light vs dark, compared not summed) to answer a real yes/no question with real stakes.

Call whenever a scene reaches something no one can know in advance —
other hearts, hidden things, or the turn of luck — or when a player
claims an uncertain, high-stakes action simply succeeded. At least one
omen must be asked before a scene can close. Not for material that just
needs new story content rather than a yes/no answer — use
draw_unclaimed_card for that instead.

hope: a plain sentence naming the real stakes being asked about — what
the asker hopes is true (e.g. "I hope the guard doesn't notice me").

Not idempotent: each call rolls fresh dice and increments an internal
counter that alternates who narrates the result. Doesn't mutate any
other game state. Returns a dict describing the roll: on doubles,
{hope, light, dark, doubles: True, reading} — no answer, the Hand is
called instead (see call_the_hand). Otherwise {hope, light, dark,
doubles: False, direction, gap, texture, complication_rank,
complication_meaning, grounds_by} — direction and gap give the answer's
shape, grounds_by says whether the player or Claude narrates the
complication.

Inputs: hope (required).

complete_goalComplete GoalA

Mark the current Sitting's Goal as completed, once the story has actually resolved it.

Call at most once per Sitting, only after an Omen's grounding has landed
and, checked against the Goal's saved interpretation (get_sheet's 'goal'
field), genuinely resolves what the Goal meant — never call this
preemptively, ahead of the grounding that earns it. Distinct from
draw_goal_card, which deals the Goal at the start of the Sitting;
complete_goal only marks it finished later in play.

Takes no parameters. Idempotent in effect (repeated calls leave the flag
True) but should only be invoked the one time the resolution actually
happens. Sets and returns goal_completed as True.

Inputs: no named parameters.

defy_resolveResolve DefianceA

Lock in the current defiance dice as final, deal debt, and reset the ritual.

Call once, after defy_roll has been called at least once and the
player is done rolling (whether by choice or because three rolls were
taken). Ends the current defiance ritual — a subsequent defy_roll call
starts a fresh one from scratch.

Takes no parameters. Raises ValueError if called before any defy_roll
in this ritual. Not idempotent: mutates the shared debt row, adding
one debt card per roll taken during the ritual (more rolls, more debt,
win or lose), and clears the ritual's dice/roll-count state. Returns a
dict with final_dice, rolls_taken, pattern and bend (None if no
scoring pattern was hit — otherwise three of a kind/"turn the blade",
small straight/"stay the hand", full house/"take the cup", or five of
a kind/"seize the pen"), and debt_dealt (the cards just added to the
debt row).

Inputs: no named parameters.

defy_rollRoll Defiance DiceA

Roll (or re-roll) the five defiance dice, Yahtzee-style, when a player wants to fight a landed fortune from call_the_hand.

Call once to make the first roll (keep=None or []), then optionally
call again up to two more times to re-roll, keeping whichever dice the
player wants to hold between rolls. Up to three rolls total; call
defy_resolve once the player is satisfied with the dice (or after the
third roll) to lock in the result and deal debt.

keep: the die face values (1-6) to hold onto from the current dice
before rolling the rest fresh — e.g. keep=[6, 6] to hold two sixes.
Must be a subset of what's actually currently showing (raises
ValueError otherwise), and must be empty/None on the very first roll of
a ritual (there are no dice to keep yet).

Not idempotent: mutates shared dice/roll-count state across calls
within one defiance ritual; each extra roll taken also means more debt
dealt later at defy_resolve. Returns a dict with dice (sorted current
values), rolls_taken, rolls_remaining, and current_pattern/current_bend
if the dice already match a scoring pattern (three of a kind, small
straight, full house, or five of a kind — see defy_resolve).

Inputs: keep.

draw_cross_cardDraw Cross CardA

Draw one card, blind, into a position on the Sitting's cross — the four-drive character-creation spread built once at the start of every campaign.

Call once per drive during the Sitting, after the player has chosen a
suit for that drive: Motivation, then Seek, then Carry, then Ends, in
that order. Each of the four positions and each of the four suits can
only be used once each — calling this with a position or suit already
used raises ValueError. "Blind" means the card is drawn face-down from
the matching suit's own 13-card pile without looking; don't reveal its
phrase to the player until all four drives have a card.

suit: which of the four card suits (♥ Hearts, ♦ Diamonds, ♣ Clubs,
♠ Spades) the player chose for this drive — each suit represents a
different domain of meaning (love/loyalty, wealth/ambition,
labor/growth, death/conflict).
position: which of the four drives this card fills — Motivation, Seek,
Carry, or Ends.

Not idempotent: mutates the shared cross and removes a card from the
suit's pile. Returns the drawn card as a dict with rank, suit,
suit_name, domain, rank_meaning, and phrase.

Inputs: suit (required), position (required).

draw_goal_cardDraw Goal CardA

Deal the Goal: draw one card face-up from whatever's left across all four suit piles, once all four drives have been drawn.

Call once, during the Sitting, right after all four calls to
draw_cross_card are done (one per drive: Motivation, Seek, Carry, Ends)
and the player has described their character. Unlike draw_cross_card,
this draws from the combined remainder of all four suit piles at once,
not one specific suit — and it's revealed to the player immediately,
not held back. After dealing it and asking what it means, pass the
player's answer to record_goal_interpretation.

Takes no parameters. Raises ValueError if called before all four cross
positions are filled, or if a Goal has already been dealt this Sitting.
Not idempotent: mutates the shared Goal and the relevant suit pile.
Returns the drawn card as a dict with rank, suit, suit_name, domain,
rank_meaning, and phrase.

Inputs: no named parameters.

draw_unclaimed_cardDraw Unclaimed CardA

Draw one card from the Fortuneteller's Hand deck to open or refuel a scene with new story material.

Call at the start of every new scene, and again mid-scene any time the
story genuinely needs new material rather than a yes/no answer — for a
yes/no question, use cast_omen instead. Read the returned phrase cold,
before narrating anything else; only afterward do you narrate the scene
forward and let its meaning land in the fiction.

Takes no parameters. Not idempotent: each call removes and returns a
different card from the shared, shuffled deck (auto-reshuffles when the
deck runs out). Returns a dict with rank, suit, suit_name, domain,
rank_meaning, and phrase.

Inputs: no named parameters.

get_sheetGet Character SheetA
Read-only

Read the current character sheet: the Sitting's cross, Goal, debt row, and whether the Goal is completed.

Call any time you need to check saved state rather than re-deriving
it — for example, checking the Goal's saved interpretation before
calling complete_goal, or checking goal_completed before deciding
whether to close the campaign at a scene's end. Read-only: never
mutates game state.

player_name: must exactly match the name already set via
name_character for the character currently in play; raises ValueError
if no character has been named yet, or if the name doesn't match the
current character.

Returns a dict with cross (the four drive-to-card mapping), goal (the
dealt Goal card plus its recorded interpretation, or None if not yet
dealt), debt_row (list of outstanding debt cards), and goal_completed
(bool).

Inputs: player_name (required).

name_characterName CharacterA

Set the name of the character being created for a new Sitting.

Already called automatically, once, when the start_game prompt runs —
you typically won't need to call this tool yourself. It exists as a
standalone tool mainly so get_sheet and other tools have a name to
validate against. Call it directly only if you need to (re)name a
character outside the normal start_game flow.

player_name: the player-chosen name for their character. No format
constraints.

Not idempotent: overwrites the shared character name each time it's
called, with no confirmation or uniqueness check. Returns
{"player_name": player_name}.

Inputs: player_name (required).

record_goal_interpretationRecord Goal InterpretationA

Save the player-and-Claude-agreed meaning of the Goal card onto the Goal, for later reference.

Call once, during the Sitting, right after the player has answered what
the Goal means and Claude has reflected that answer back in a sentence
or two — pass that reflected sentence in. This becomes the saved
reference that complete_goal later checks against (via get_sheet's
'goal' field) to judge whether a moment in play genuinely resolves the
Goal.

interpretation: a plain-language sentence or two capturing what the
Goal means for this character — Claude's own reflected summary, not a
verbatim quote of the player's answer.

Raises ValueError if called before draw_goal_card has dealt a Goal.
Not idempotent: overwrites any existing interpretation on the shared
Goal. Returns the full updated Goal card dict, including the new
'interpretation' field.

Inputs: interpretation (required).

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A4.9/5.0

    Scored across 11 tools

    Disambiguation5/5

    Each tool occupies a distinct step in the game flow—character creation, omen resolution, defiance, or state reading—and the descriptions explicitly state when to call each. Even though several tools draw cards or roll dice, their triggers and parameters are clearly separated, so an agent would not confuse them.

    Naming Consistency5/5

    All tools follow a consistent verb_noun pattern with lowercase and underscores, using clear action words like draw, cast, call, defy, get, complete. The only slight variation is 'call_the_hand' which includes an article, but it's still a verb-phrase pattern, so the naming is highly predictable.

    Tool Count5/5

    Eleven tools is appropriate for this game system, covering character building, scene resolution, and state management without being excessive. Each tool serves a concrete purpose, and the count falls squarely in the recommended 3-15 range.

    Completeness5/5

    The tool set covers the full lifecycle of a Sitting: naming, cross and goal drawing, interpretation recording, scene omen and hand mechanics, defiance rituals, unclaimed cards, state inspection, and goal completion. There are no obvious missing operations that would force an agent to work around gaps.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      An MCP server that draws tarot cards from the 78-card deck and interprets them for your situation, using StupidAPIs with an X-API-Key required.
      12 npm
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      MCP server providing true randomness capabilities to Claude, enabling cryptographically secure random number generation for games, decision-making, sampling, simulations, and any operation requiring genuine randomness.
      13
      2
      MIT
    • F
      license
      B
      quality
      D
      maintenance
      An MCP server that provides 18 tools for dice rolling, luck tests, character management, world state, combat, and save/load, enabling an AI game master to run a solo-play gamebook entirely through deterministic game logic.
      18
      1
      -