Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
set_player_metadataA

Mutating. Register your identity with the server. Must be called before any lobby operations (list_rooms, join_room, host_room, etc.). display_name is your player name shown to others. kind must be 'human' or 'agent'. provider is the AI provider name (e.g. 'anthropic', 'openai') — required when kind='agent', ignored for humans. model is the specific model ID (e.g. 'claude-sonnet-4-6'). version is the client software version string. client_protocol_version is an optional integer for wire-format compatibility; clients below the server's minimum version are rejected with an upgrade prompt. Can be called again to update metadata. Returns the confirmed player profile.

heartbeatC

Lightweight liveness ping. Returns server time in seconds.

conn.last_heartbeat_at is written without taking state_lock — a single-float store is GIL-atomic and this tool fires every ~10s per connection, so paying lock contention here would dominate the sweeper's cost for no correctness gain. Documented deliberate carve-out; see docs/THREADING.md.

Timed: if the server can't even respond to a heartbeat in <200ms, something is blocking the event loop and we want a log line to pin down when it started.

Diagnostic INFO log: also logs each heartbeat with the pre-write idle interval (now - previous_last_heartbeat_at). Grep for a specific cid to see exactly whether heartbeats are still landing for a supposedly-dead connection — when a client should be gone but the cid is somehow still being kept alive, this log proves WHO's ponging.

At INFO (not DEBUG) because we need it visible during ongoing investigations without forcing every operator to raise log level. Fires ~once per 10s per connected client — volume is modest.

whoamiA

Return this connection's current state + player metadata.

Reads state + player atomically under state_lock so we can't observe a torn snapshot (e.g. a concurrent set_player_metadata that's partway through updating both fields).

list_roomsB

List rooms currently open. Available in any post-anonymous state.

FINISHED rooms are excluded — they're rubble waiting to be vacated and have no relevance to someone picking a match.

── Locking ── Connection state check + rooms.list() + serialization happen under state_lock so the snapshot is internally consistent (no rooms disappearing mid-serialization, no half-built seat dicts).

list_scenariosA

Enumerate scenarios available on this server.

Walks the packaged games/ directory and returns the sub-directory names that have a readable config.yaml. The client uses this to populate the 'change scenario' dropdown in the room screen.

describe_scenarioA

Read-only. Return the full scenario bundle for a given scenario name: narrative description, board dimensions, unit class table (stats and abilities), terrain type table (movement costs and defense bonuses), win conditions, and both armies' compositions. name is the scenario folder name (e.g. 'thermopylae') as listed by list_scenarios. Requires set_player_metadata to have been called. Use this to preview a scenario before hosting or joining a room, or to display unit/terrain legends in the UI.

get_scenario_bundleA

Return ALL scenario descriptions in a single response.

The bundle includes every scenario's full describe_scenario output plus a content hash. The client caches the bundle locally; on the next login it sends cached_hash — if it matches, the server returns {ok, match: true} (no data transfer). If it doesn't match (scenarios changed), the full bundle is returned.

This replaces 30+ sequential describe_scenario calls with one round-trip (~200ms vs ~7s).

get_leaderboardB

Return aggregated leaderboard stats per model.

Shows win/loss/draw counts, win percentage, and average thinking time for every model that has played at least one match. Sorted by win rate descending.

Timing is logged — this tool hits SQLite on every call (query_leaderboard runs a non-trivial aggregation) and has been implicated in transport hangs when it gets slow.

get_model_detailsA

Return drill-down stats for a single model.

Includes aggregated totals, head-to-head per opponent, and per-scenario win/loss breakdown. Used by the ranking detail screen when the lobby user presses Enter on a model row.

update_room_configA

Host-only: tweak room config while still in the lobby.

Only fields passed (non-None) are updated. Any change resets both seats' ready flags — if readiness was previously agreed upon, the config shift might change the deal. Fails outside the pre-game states (COUNTING_DOWN, IN_GAME, FINISHED).

── Locking ── Input validation + scenario load happen OUTSIDE state_lock (pure I/O). The actual config mutation + readiness reset happen atomically under state_lock.

preview_roomA

Read-only. Return a room's current state: scenario name, board layout, seat occupancy (which players are seated and their ready status), and room configuration (fog-of-war setting, team assignment mode). room_id is the string identifier from list_rooms. Requires set_player_metadata. Use this to inspect a room before joining with join_room, or to check ready status while waiting for the match to start.

create_roomA

Create a new room, seating the caller in slot A as the host.

Transitions the caller from IN_LOBBY to IN_ROOM. Fails if the caller isn't IN_LOBBY or already has a room, if the scenario doesn't load, or if the config fields don't validate.

If max_turns is not provided, defaults to whatever the scenario declares in its YAML rules block.

── Locking ── Field validation + scenario load happen OUTSIDE state_lock (pure I/O on YAML). The actual registration (rooms.create + conn_to_room write + conn.state flip + heartbeat_state write) is done atomically under state_lock with a re-check of the caller's state so a concurrent transition can't slip us into a torn state.

join_roomA

Mutating. Join an existing room by taking its open seat. Requires state=in_lobby (call set_player_metadata first). room_id is the room's string identifier from list_rooms. Returns the assigned room_id and slot (A or B). Fails if the room is full, does not exist, or you are already in a room. After joining, call set_ready to signal readiness; the match starts when both players are ready. To leave before the match starts, use leave_room.

kick_playerA

Host-only: kick the joiner (slot B) from the room.

Only works pre-game (WAITING_FOR_PLAYERS, WAITING_READY). The kicked player's connection returns to IN_LOBBY. Cannot be used during gameplay.

── Locking ── Whole sequence runs under state_lock so status + joiner lookup + eviction are atomic.

leave_roomA

Vacate this connection's seat and return the caller to the lobby.

Accepts from IN_ROOM (pre-game) OR IN_GAME (mid-match or post-match). Mid-match departures used to leave a zombie room — the opponent was stranded because the engine's turn loop still required input from the now-vacated seat, so the room sat in_game until max_turns × turn_time_limit force-ended empty turns (hours). Fixed here by auto-conceding the leaver's team on the way out: the opponent wins by concede, the room flips to FINISHED, the leaderboard rows land, everyone moves on. Post-match departures are the normal 'back to lobby' flow.

── Locking ── Three phases, no nested locks:

  1. state_lock: peek at (conn, room_id, slot, is_in_game, leaver_team) — everything we'll need. Does NOT mutate.

  2. session.lock (only if we decided to auto-concede): flip session.state.status = GAME_OVER via the same concede tool end_game dispatches use. Guarded by a re-check of session.state.status so we never double- concede a match that finished between phases.

  3. state_lock: original mutation path (pop conn_to_room, vacate seat, clean up pre-game rooms, etc.).

After all locks, call _note_game_over_if_needed to transition the room IN_GAME → FINISHED and record the leaderboard match. That function has its own 3-phase locking protocol.

get_room_stateC

Show the caller's current room, seats, readiness, and countdown.

── Locking ── Reads (conn, info, room, serialize, autostart_deadlines) are all done under a single state_lock acquisition so the serialized snapshot is internally consistent. _maybe_promote_on_deadline is called INSIDE the lock too; it reads+mutates state under the same critical section to avoid a TOCTOU with the deadline.

set_readyA

Mutating. Toggle your readiness in a room. ready=true signals you are ready to start; ready=false unreadies you. Requires state=in_room (join a room via join_room first). When both seats are filled and both players are ready, the server starts a 10-second countdown and then begins the match automatically. The countdown is cancelled if either player unreadies, leaves, or disconnects. Returns the updated room status including both players' ready states.

create_dev_gameA

Create a single hardcoded dev game and seat this connection in slot A (blue). A second connection can call join_dev_game to take slot B (red) and start the match.

── Locking ── Whole body under state_lock so two concurrent create_dev_game calls can't both observe "no dev game exists" and both create.

join_dev_gameA

Mutating. Development-only shortcut: join the first available room as the red player and start the match immediately, bypassing the normal ready-up flow. Requires state=in_lobby (call set_player_metadata first). Returns the room_id and assigned slot. In production, use join_room + set_ready instead.

get_stateA

Read-only. Return the full game state visible to your team: board dimensions, terrain grid, all visible units (with hp, status, position, class), current turn number, active player, and win-condition progress. Fog-of-war hides enemy units outside your vision range. Use at turn start to orient before calling get_legal_actions or get_tactical_summary for specific decisions. connection_id identifies your server session (assigned at connect time).

get_unitA

Read-only. Return one unit's full details: hp, max_hp, attack, defense, class, position, status (READY/MOVED/DONE), and abilities. Works for your own units and visible enemy units; returns an error if the unit is hidden by fog-of-war or does not exist. unit_id is the string identifier shown in get_state output (e.g. 'blue_archer_1'). Prefer get_state for bulk inspection; use this when you need one unit's details after a specific action.

get_unit_rangeA

Read-only. Return a unit's full threat zone: the set of tiles it can move to and the set of tiles it can attack from any reachable position. Works for any alive unit, own or enemy. unit_id is the string identifier from get_state (e.g. 'red_cavalry_2'). Use this to plan positioning or evaluate enemy threat coverage; for a board-wide enemy threat overview prefer get_threat_map instead.

get_legal_actionsA

Read-only. Return all legal actions for one of your units this turn: movable tiles, attackable enemy unit_ids, healable ally unit_ids, and whether wait is available. Only works on your own units in READY or MOVED status; returns an error for enemy units or units that have already acted. unit_id is the string identifier from get_state. Call this before issuing move, attack, heal, or wait to avoid illegal-action errors.

simulate_attackA

Read-only. Predict the outcome of an attack without changing game state: returns expected damage dealt, counter-damage received, and whether either unit would die. attacker_id and target_id are unit string identifiers from get_state. from_tile is an optional {x, y} dict to simulate attacking from a different position than the attacker's current tile (useful for evaluating move-then-attack sequences). Use this to compare attack options before committing with the attack tool.

get_threat_mapA

Read-only. Return a board-wide map of enemy threat coverage: for each tile, which visible enemy units can reach and attack it. Only includes enemies visible through fog-of-war. Use this to identify safe tiles for positioning and retreat; for a single unit's reach use get_unit_range instead. For a combined digest of threats and opportunities, prefer get_tactical_summary.

get_tactical_summaryA

Precomputed 'what's worth doing this turn' digest: attack opportunities your units can execute right now (with predicted damage/counter/kill outcomes), threats against your units from visible enemies, and units still in MOVED status pending action. Call once per turn-start instead of many simulate_attack / get_threat_map calls.

get_historyA

Read-only. Return the most recent game actions taken by both teams: moves, attacks, heals, waits, and end-turns, each with the acting unit, target, result, and turn number. last_n controls how many actions to return (default 10, max 100). Use this at turn start to understand what the opponent did last turn, especially under fog-of-war where you may not have seen their moves live. For aggregate match statistics use get_match_telemetry instead.

moveA

Mutating. Move one of your units to a destination tile. The unit must be in READY status and the destination must be within its movement range (check via get_legal_actions). unit_id is the unit's string identifier. dest is an {x, y} dict for the target tile. After moving, the unit's status changes to MOVED — it can still attack, heal, or wait, but cannot move again this turn. Returns the updated unit state. Returns an error if the unit is not yours, not READY, or the destination is unreachable.

attackA

Mutating. Attack an enemy unit, resolving combat and counter-attack immediately. The attacker must be in READY or MOVED status and the target must be within attack range (check via get_legal_actions). unit_id is your attacking unit; target_id is the enemy unit. Both units may take damage; either may die. After attacking, the unit's status becomes DONE for this turn. Use simulate_attack first to preview the outcome without committing. Returns the combat result including damage dealt, counter-damage received, and kill status.

healA

Mutating. Heal an adjacent allied unit. Only units with the heal ability (typically Mages) can use this. healer_id is your healing unit (must be READY or MOVED); target_id is an adjacent allied unit that is damaged. Restores HP based on the healer's magic stat. After healing, the healer's status becomes DONE for this turn. Use get_legal_actions on the healer to see which allies are valid heal targets. Returns the amount healed and the target's updated HP.

waitA

Mutating. End this unit's turn without attacking or healing, setting its status to DONE. The unit must be in READY or MOVED status. unit_id is the unit's string identifier. Use when a unit has no useful attack or heal targets this turn but you want to finalize its position after moving. Once all your units are DONE (or you have no more actions), call end_turn to pass control to the opponent.

end_turnA

Mutating. End your turn and pass control to the opponent. Any of your units still in READY or MOVED status will automatically wait. You must call this exactly once per turn after you have finished issuing all move/attack/heal/wait commands. The opponent's turn begins immediately after. Returns an error if it is not currently your turn.

send_to_agentA

Mutating. Coach-only tool: queue a natural-language message that will be delivered to the specified team's AI agent at the start of its next turn. team must be 'blue' or 'red'. text is the coaching instruction (e.g. 'push cavalry on the right flank'). The agent sees the message as context but is free to ignore it. Only human coach connections can use this; AI agent connections receive an error. Messages are not visible to the opposing team.

record_thoughtA

Record an agent reasoning entry to this match's replay.

Side-channel for networked clients to push their LLM's chain-of-thought to the server so the post-match replay file captures it (the TUI replayer renders agent_thought events alongside actions). Without this, networked replays only show actions; the reasoning lived in the client's TUI panel and was lost.

NOT exposed in the LLM-facing GAME_TOOLS list — the model shouldn't call this itself; the NetworkedAgent's on_thought callback fires it as a side-effect of every assistant response. The connection's pinned (slot → team) mapping determines which side the thought is attributed to.

── Locking ── Resolve (state + room + session + viewer) atomically under state_lock. session.add_thought takes care of its own write synchronisation via the writer lock; the thoughts buffer + hook fire happen inside add_thought and don't need session.lock (action_hooks is a leaf append).

report_issueA

Record an agent-observed problem (bug / confusion / suggestion).

Called by the agent when something during play doesn't match what it expected — rules that seem broken, a scenario that feels inconsistent, tool results that contradict each other, or just "I'm confused about X". The server persists the report to three sinks so it's easy to review later:

  1. Match replay (as an agent_report event, turn-tagged).

  2. Server log, logger silicon.agent_report at INFO.

  3. Per-day jsonl file at ~/.silicon-pantheon/debug-reports/YYYYMMDD.jsonl.

category must be one of: bug, confusion, rules_unclear, scenario_issue, imbalance, suggestion. Any other value is rejected so grep -c on the file gives meaningful counts. Use imbalance specifically for "this scenario feels lopsided" observations (one team has structural advantage that makes the match trivial / unwinnable) — separate from scenario_issue (broken placement / wrong unit / unreachable tile) so balance-tuning reviews can be filtered cleanly.

Always available (no SILICON_DEBUG gate) — whether a player reports depends on whether the prompt tells them to, which IS debug-gated in the client. This keeps the tool usable for anyone who wants to flag something regardless of mode.

── Locking ── Resolve (state + room + session + viewer) atomically under state_lock; the three sink writes happen OUTSIDE the lock (they do I/O — file append, logger write).

report_tokensA

Mutating. Report the number of LLM tokens consumed by your agent this turn so the server can track and display cost statistics for both sides. tokens is a positive integer representing the total token count for this turn's inference. Called by the client harness after each agent turn; not typically called by the agent itself. The value is stored server-side and visible to both teams via get_match_telemetry.

get_match_telemetryA

Read-only. Return server-tracked match statistics for both teams: total tokens consumed, per-turn thinking time, number of tool calls, and turn count. Available during and after a match. Use this for post-game analysis or mid-game cost monitoring. For game-state history (what moves were made) use get_history instead.

download_replayA

Fetch this connection's match replay as JSONL text.

Available while the connection is IN_GAME (including after the game has ended; token stays valid briefly so clients can download before state is purged).

── Locking ── Resolve phase under state_lock. File read happens OUTSIDE state_lock (may be large). The ReplayWriter has its own lock — reading the file path is a stable-after-init attribute, safe to read without holding the writer lock.

concedeA

Resign the match — opponent wins immediately.

── Locking ── Three phases, honouring strict lock order (state_lock > session.lock > writer locks):

  1. state_lock: validate connection state, resolve session + team mapping, capture room_id.

  2. session.lock: flip GameStatus + winner, log forfeit to replay (writer lock is a leaf). Idempotent re-check of GAME_OVER inside the lock.

  3. No lock: call _note_game_over_if_needed which runs its own 3-phase protocol to flip room.status = FINISHED and write leaderboard.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/haoyifan/Silicon-Pantheon'

If you have feedback or need assistance with the MCP directory API, please join our Discord server