Skip to main content
Glama

s2docs — Source 2 API/CLI docs + MCP

One searchable place for the undocumented surface of Valve's Source 2 games (Counter-Strike 2, Deadlock). Everything is extracted from each game build, then served three ways:

  • Web UI: a light vanilla-JS SPA for browsing and searching, plus operations pages (Hosts, Activity) for watching tests on remote hosts without SSHing anywhere, and an Orchestration page for the server's multi-step plans.

  • JSON API: FastAPI, with OpenAPI docs at /docs.

  • MCP server: tools, prompts and resources so coding agents can write cs_script, Hammer logic, configs and mods using verified names.

Data

CS2

Deadlock

Source

Console variables / commands

4,327 / 1,246

4,859 / 1,421

DumpSource2/convars.txt, commands.txt

Hammer entities (keyvalues, inputs, outputs)

857

2,181

DumpSource2/entities/{server,client}.fgd

Schema classes / enums (fields, metadata, inheritance)

3,753 / 634

9,264 / 880

DumpSource2/schemas/**.h

Game events

273

196

game/*/pak01_dir/resource/*.gameevents

Pulse bindings

2,488

2,646

DumpSource2/module_metadata/*/pulse_bindings.txt

cs_script API symbols

517

—

point_script.d.ts (Valve's official declarations)

Valve Hammer FGD classes (labels, help, defaults, choices)

430 (63 editor-only)

—

game/csgo/csgo.fgd + game/core/*.fgd

Valve sample scripts

7

—

content/csgo_addons/cs_script_demo/maps/scripts/*.js

Community docs

185 pages (all games)

source2.wiki (CC BY-SA 4.0)

Where the data comes from

Source

Authored by

How we get it

point_script.d.ts, tsconfig.json, sample *.js, *.gameevents, Hammer *.fgd, steam.inf

Valve, copied verbatim from the game install

SteamTracking GameTracking mirrors the files on every patch

Convars/commands, runtime entity lists, schema, Pulse bindings

Valve's binaries, extracted with DumpSource2

Same GameTracking repos (DumpSource2/)

Wiki pages

source2.wiki community, CC BY-SA 4.0

Shallow, sparse git clone of Source2Wiki/Source2Wiki

No game install is needed. Entity lookups merge both entity sources. Every member is tagged runtime (registered by the binary), valve_fgd (an editor-only key from Valve's FGD) or both. The Valve Developer Community wiki is not ingested: it sits behind an anti-bot proof-of-work wall (Anubis) that deliberately blocks scripted clients. See RESEARCH.md for the full source survey.

Quick start

uv sync
uv run s2docs sync              # sparse-clone GameTracking (~25 MB/game) and build data/s2docs.db (~5 s)
uv run s2docs serve             # http://127.0.0.1:8000  (UI) · /docs (OpenAPI) · /mcp (MCP over HTTP)

Other commands:

uv run s2docs sync --game cs2         # one game
uv run s2docs sync --no-fetch         # re-index the existing checkout (e.g. after parser changes)
uv run s2docs sync --docs-only        # only refresh source2.wiki   (--skip-docs to skip it)
uv run s2docs sync --check            # is the index older than the checkout? (read-only; --fail-if-stale exits 6)
uv run s2docs load cs2 /path/to/GameTracking-CS2   # index a full local clone
uv run s2docs stats
uv run s2docs prompt cs2_script task="teleport players who touch the lava"
uv run s2docs mcp                     # MCP over stdio

Environment variables

Variable

Default

Purpose

S2DOCS_DATA_DIR

the checkout's ./data when running from a source checkout, else a per-user dir (~/.local/share/s2docs, or %LOCALAPPDATA%\s2docs on Windows)

Directory holding the index, workspace and captured logs. Never relative to the cwd (see below).

S2DOCS_DB

$S2DOCS_DATA_DIR/s2docs.db

The SQLite index file.

S2DOCS_WORKSPACE

$S2DOCS_DATA_DIR/workspace

Local addon sources written by the build tools.

S2DOCS_AUTH_TOKEN

unset (auth off)

When set, mutating /api requests and every /mcp request must present this token (see below). Read once at startup — restart the server to rotate it.

S2DOCS_STUCK_SEC

1800

Phase age (seconds) after which a fresh host lock reads STUCK.

S2DOCS_STUCK_PHASES

built-in per-phase defaults

JSON object of per-phase stuck thresholds, merged over the built-ins.

S2DOCS_REAP_SEC

60

Seconds between orphan-run reaper passes (<= 0 disables the periodic task).

The data dir never depends on the working directory. With S2DOCS_DATA_DIR unset the default is a fixed path: a source checkout uses its own ./data, and anything else uses a per-user directory. This closes the split-database incident — a server and an MCP client that were launched from different working directories (an MCP config that predates the env fix spawns s2docs mcp with cwd set to the client's profile dir) used to read and write two different stores, leaving a shadow DB next to an unrelated cwd. serve and mcp still print a loud stderr warning when the variable is unset, now naming the resolved path. S2DOCS_DB and S2DOCS_WORKSPACE overrides are unchanged.

Auth is opt-in. Set S2DOCS_AUTH_TOKEN and the server requires Authorization: Bearer <token> or X-S2docs-Token: <token> on every non-GET/HEAD/OPTIONS request under /api and on every request to the HTTP MCP endpoint /mcp; a missing or wrong token is 401 (compared in constant time). GET routes and the static web UI stay open, and stdio MCP is unaffected (it is local). With the variable unset the behaviour is unchanged — no auth — and GET /api/health and the MCP server_info tool report auth_required: false (they report only the boolean, never the token).

deploy_push takes the host lease. A deploy now arbitrates the host through the same lock the build and test services use, so it can never copy onto a host another owner holds. Pass owner if you already hold the lease — the push reuses it and leaves it locked; otherwise a free host is claimed as deploy:<id> for the copy+verify window and released afterwards (owner-conditional, even on failure), and a host held by a different owner is a 409 (HostLocked). The effective owner is recorded on the deployment row. The deploy_verified orchestration gate no longer falls back to the host's latest deployment: the step must name the deployment id from deploy_push as an artifact, and it must belong to the project's host and be verified.

Related MCP server: open77-devkit

Web UI

s2docs serve mounts a small vanilla-JS SPA at / (hash routes, no framework, no build step). Besides the browse/search sections (#/<game>/…) it has install-wide operations pages for watching work on remote hosts, and a page for the server's orchestration plans:

  • Overview (#/overview) — a mission-control wallboard, not a copy of the four pages: a hero card for the one thing happening right now (running test leads, then builds, then the queue), a machine dock (one LED chip per host — pulsing gold = busy, red = STUCK, gray = idle), and three lanes: the queue rail, the verdict wall (one colour-coded tile per job, newest first, like a CI status board) and a compact feed of the newest runs. Read-only; every tile/line links to its record. Same auto-refresh cadence as Activity.

  • Hosts (#/hosts) — the registry plus live lock state: shell, GPU (from the last probe), how long ago it was probed, and the current lock owner with fresh/stale + heartbeat age, its phase (what the owner is doing) and a STUCK badge. A lock is STUCK when its heartbeat is fresh but its phase has not moved for longer than the server's threshold — liveness is not progress, so a worker that heartbeats faithfully while its compile hangs still shows up. Clicking a host opens #/hosts/<id> with its facts JSON, lock history (claim / takeover / release / phase-change events) and its recent builds, deployments and test runs. Read-only; auto-refreshes every 5 s while open.

  • Activity (#/activity) — the page to watch instead of tailing logs: a Now section (the running test run — the same name / step / state / elapsed fields as the in-game TEST STATUS HUD strip — and active/queued build runs, freshest first), a By host section grouping every recent build run, test run and deployment under the host it ran on, and a collapsed full history. Auto-refreshes every 5 s while a run is active, 15 s when idle.

  • Orchestration (#/<game>/projects) — the multi-step orchestration DAGs (distinct from runs): each project opens to a step-level view with a dependency graph, and per step its state, gate, evidence counts, updated time and notes. For the build/test/deploy runs those steps drive, use Activity.

  • Jobs (#/jobs) — the job ledger + queue: one record per requested test/demo/final run (intake, timeline, screenshots/logs, links, and a mandatory close-out summary). A per-host queue board (running on top, then queued by position, error/crash counts in red); #/jobs/<id> shows the intake, the screenshot gallery, linked records and the summary card (first on a finished job). Jobs never claim the host lease — the build/test/deploy tools still do; a job links to them. See docs/TEST_JOBS.md.

Activity is service-fed. A row appears on that page only when work goes through the s2docs build / test / deploy services (build_start / test_start / deploy_push over MCP, or the equivalent s2docs CLI) — it is not a view of any CS2 that happens to be running anywhere, so an empty page means nothing has been recorded yet, not that the page is broken; the empty state says so and points at the commands to start something. All these pages read the existing JSON API (/api/hosts, /api/hosts/{id}/locks, /api/builds, /api/deploys, /api/test-runs, /api/projects, /api/projects/{id}, /api/evidence); nothing there can mutate a host or a run.

MCP

Hermes Agent (native tools as mcp_s2docs_*; stdio, no server process needed)

hermes config set mcp_servers.s2docs.command /Users/you/source/s2docs/.venv/bin/s2docs
hermes config set mcp_servers.s2docs.args '["mcp"]'

Claude Code

claude mcp add s2docs -- uv --directory /Users/you/source/s2docs run s2docs mcp
# or, with `s2docs serve` running:
claude mcp add --transport http s2docs http://127.0.0.1:8000/mcp

Claude Desktop / other stdio clients

{
  "mcpServers": {
    "s2docs": {
      "command": "uv",
      "args": ["--directory", "/Users/you/source/s2docs", "run", "s2docs", "mcp"]
    }
  }
}

Docs tools (read-only)

search_docs

Full-text search over everything. Each hit's key feeds the matching detail tool.

get_convar, list_convars

Default, min/max, flags (cheat, replicated, …) and help text

get_entity, find_entities_with_io

Keyvalues (with choices), inputs and outputs including inherited ones, with defined_in

get_schema_class, list_schema_classes

Fields, types, defaults, metadata, inheritance chain and subclasses (VData, netvars)

get_game_event, list_game_events

The effective definition after core → game → mod overrides

get_script_api, list_script_types

The cs_script API (Instance = Domain), including inherited members

search_pulse

Pulse visual-scripting node bindings

diff_builds

What changed between two indexed builds, at member granularity: one row per added/removed/changed schema field, entity member, game-event field, script symbol, convar, Pulse binding or Hammer class, grouped under its parent class/entity/event/library. Sections: convars, entities, schema, events, script_api, pulse, hammer_classes. counts carries the exact per-section added/removed/changed breakdown, computed before the per-section limit cap (default 500), so it stays true when a section's entries are truncated to the first limit rows; totals[section] is the sum of its three counts

list_examples, get_example

Valve's cs_script sample scripts, each with the API it uses

list_docs, get_doc

source2.wiki pages as Markdown, with URL and license for citation

Build tools

validate_cs_script

Type-checks a script with tsc against this build's point_script.d.ts. Diagnostics are categorized (unknown_api, needs_type_narrowing with the cast to use, deprecated_api, …)

validate_cfg

Checks every cfg line against the indexed convars: unknown names (with suggestions), out-of-range values, cheat flags, client vs server

scaffold_addon, list_addons, get_addon, read_addon_file, write_addon_file

Local addon workspace ($S2DOCS_WORKSPACE, default data/workspace). Writes text sources only, with no path escapes and no overwrites unless asked

Host tools

list_hosts, get_host, register_host

Registry of test/build hosts (ssh target, shell flavor, probed facts: GPU/RAM/processes/CS2 build)

probe_host

SSH-probe a host and store its facts (scripts/windows/probe_host.ps1)

claim_host, heartbeat_host, release_host

Server-side host locks with owner + TTL + heartbeat + phase. A fresh lock blocks other owners; a stale lock (crashed worker) is taken over automatically. Report a phase (what you are doing) so the server can flag a lock STUCK when its heartbeat is fresh but the phase has not moved (liveness is not progress). Check list_hosts before driving any host

Stuck detection

The server, never the client, decides stuck-ness: a lock is stuck=true when fresh and its phase_age_sec exceeds a threshold (env S2DOCS_STUCK_SEC, default 1800 s) unless a per-phase override applies. compile:vis_rays and compile:vis_los ship with built-in 5400 s defaults (the long silent stretch of a CS2 compile is the visibility ray tracing — measured at ~1354 s and ~1409 s, against a ~2 min lighting bake — matching the driver's own 3 x 1800 s hang threshold; the driver's buffered log can leave the phase unchanged ~25-30 min on a healthy run), so no env is needed for them; env S2DOCS_STUCK_PHASES (a JSON object like {"test": 600}) merges over the built-in defaults per key rather than replacing them. Heartbeating the same phase does not clear it; report a new phase as work progresses. A lock with no phase (old clients) is never STUCK

The same operations exist on the CLI (s2docs host list|show|register|probe|lock|heartbeat|unlock, JSON out, exit 2 = locked; lock/heartbeat take --phase/--note, and host list/show print a STUCK: line on stderr) and the HTTP API (/api/hosts, /api/hosts/{id}/lock, …; 409 = locked; every host read carries lock.phase, lock.phase_age_sec and lock.stuck; /api/hosts/{id}/locks returns the claim/takeover/release/phase-change audit trail the Hosts page shows).

Build tools

build_start

Start a server-owned run on a host: acquires the host lease (build:<run-id>), runs the caller's command over SSH, returns a run id immediately. No client sleep-polling — use build_wait/build_status

build_status, build_list

A run's record (status, exit code, parsed summary counts, material guard, artifacts) and recent runs

build_log

Tail the run's log — the log is UTF-16 (resourcecompiler), so this decodes it and reports the detected encoding plus any material-guard warnings

build_wait

Block server-side until the run finishes (bounded), or fail on timeout. strict_materials=true fails a 'succeeded' run whose log flagged missing/reserved materials

Material guard

Scans the decoded log for referencing missing material, Failed to compile and reserved-path references (Valve's _vrf/ subtree, another map's maps/<other>/ subtree). Findings land in the run's summary.material_guard / warnings field (counts + first paths). A warned run still finishes succeeded — the resourcecompiler exits 0 — unless strict mode is on

build_cancel

Mark the run cancelled and release its host lease

Deploy tools

deploy_push

Copy files to a host per a manifest ({path, local}), then verify what landed by re-reading MD5s over SSH (Get-FileHash). Returns a deploy id with per-file md5/verified. Takes the host lease for the copy+verify window: owner reuses your existing lease, a free host is claimed as deploy:<id> and released afterwards, a host held by another owner is a HostLocked error

deploy_verify

Re-check a recorded deployment's MD5s and update its status

deploy_list

Recent deployments, optionally per host

CLI: s2docs build start|status|log|wait|cancel|list and s2docs deploy push|verify|list (global --json → one {"schema": "s2docs/v1", …} object on stdout, logs to stderr; exit codes 0 ok, 1 error, 2 locked, 3 not found, 6 partial, 7 timeout/cancelled, 8 material warnings under --strict-materials). HTTP: /api/hosts/{id}/builds, /api/builds/…, /api/deploys.

Evidence tools

add_evidence

Store a structured evidence row for a project step: kind (console_transcript / file_hash / pixel_diff / screenshot / note / other), ref (path / url / command) and data (JSON detail — md5s, pixel-diff pct + noise floor, transcript excerpt). A step with an evidence gate cannot be marked done until at least one non-note row exists for it

list_evidence

Evidence rows (newest first), filtered by project, step and/or kind

Test-run tools

test_start

Open a server-side test session (name, optional host / project); claims the host lease server-side. Record fields (name, step, state, elapsed) mirror the in-game TEST STATUS HUD strip

test_step, test_finish, test_clear

Set the current step, finish pass/fail (releases the lease; records a console-transcript evidence row on the linked project), or dismiss (iso_test off)

test_status, list_test_runs

One run (or the current active run) and recent runs, optionally by state

CLI: s2docs evidence add|list|show and s2docs test start|step|finish|clear|status|list. HTTP: /api/evidence, /api/evidence/{id}, /api/test-runs (+ /current, /{id}/step|finish|clear).

Job tools

job_enqueue

Enqueue a job (validated intake) — the test/demo/final run record with its pass criterion

job_list, job_get, job_next

Jobs (newest first, with error/crash counts), one job with its timeline/links/artifacts, and the head of a host's queue when its lease is free

job_assign, job_start, job_event

Set host/queue order, queued → running, and append timeline events (a phase updates the job; crash/error carry a log tail)

job_attach, job_request_screenshot

Copy a local file into the job's artifacts (append-only), or ask the runner for a screenshot

job_link, job_finish, job_restatus, job_cancel

Attach a test run/build/deploy/evidence/project, close out (all four summary fields required; inconclusive = ended with no pass/fail verdict), correct a finished job's status (terminal → terminal, reason recorded), or cancel

CLI: s2docs job enqueue|list|show|next|start|event|attach|shot-request|finish|restatus|cancel|watch (the global exit codes: 3 not found, 4 conflict, 9 validation). HTTP: /api/jobs (+ /next, /{id}, /{id}/assign|start|event|link|screenshot-request|finish|restatus|cancel, /{id}/artifacts/{name}), GET /api/artifacts/{job}/{name}. Every job event also appends <data_dir>/job_events.jsonl (/api/health → job_events_jsonl). Full notes: docs/TEST_JOBS.md.

Orchestration tools

list_workflows

Multi-step recipes: cs2_script_feature, cs2_camera_addon, cs2_game_template, cs2_live_test, server_config, or custom

start_project

Creates a persistent project (stored in SQLite) and returns the first ready step(s)

next_step

Ready steps, each with instructions, the tools to use, rendered prompt text, and its gate

update_step

in_progress / done / blocked / skipped, with notes and artifacts. done runs the step's gate on the server

get_project, list_projects, get_prompt_text

State, history, and prompt text for clients that can't fetch prompts mid-task

Orchestration

The server holds the plan and the quality gates. The client's model does the work. Start with the orchestrate prompt ("build an isometric Diablo-style viewer for CS2"), or call start_project directly:

start_project ─▶ brief (ready steps + tools + rendered prompt) ─▶ agent works ─▶ update_step(done)
                     ▲                                                          │ gate check
                     └──────────────── next ready steps (dependency DAG) ◀──────┘
  • Gates are checked on the server, not taken on trust: scripts_typecheck re-runs tsc on every .js file in the addon, cfgs_valid validates every .cfg, addon_exists, and evidence — which requires at least one stored non-note evidence row for the step (via add_evidence); notes and artifacts alone no longer pass. cs2_live_test adds host_claimed (the project really holds the host lease, owner project:<id>) and deploy_verified — the deployment id from deploy_push, recorded as an artifact of the step, must belong to the project's host and be verified in the deploy registry (there is no latest-for-host fallback, so a stale or unrelated deployment can never satisfy it). A failed gate returns the exact errors (HTTP 409 / MCP tool error).

  • Steps form a DAG. Independent steps (e.g. camera script and test cfg) become ready together, so a client can run them in parallel or hand them to subagents.

  • Projects persist. Any session can pick one up with next_step <id>, and the web UI's Orchestration page shows the step-level DAG, gates and evidence, and can start projects or move steps.

  • Steps are snapshotted when a project starts, so editing orchestration/workflows.py never changes a project in flight.

Prompts: orchestrate, cs2_script, hammer_io_logic, server_config, deadlock_mod, cs2_map_rebuild, explain_symbol. Each is a Markdown template in src/s2docs/prompts/templates/, filled at render time with live data from the database (e.g. the current Instance method index). Edit the .md files to tune them. The same prompts are available at GET /api/prompts and POST /api/prompts/{name}/render.

Resources: s2docs://cs2/cs_script/point_script.d.ts and tsconfig.json (put them next to your scripts for editor IntelliSense), plus s2docs://cs2/cs_script/examples/{name}.

Architecture

src/s2docs/
  config.py               game registry (repo, mod dir, sparse patterns), settings
  ingest/
    sources.py            shallow + blob-filtered + sparse git checkout
    parsers/              pure parsers: convars, fgd, schema (.h), gameevents (KV1), pulse, d.ts, steam.inf
    loader.py             one transaction per build → normalized tables + FTS rows
  db/
    migrations/*.sql      versioned schema (PRAGMA user_version)
    connection.py         read-only URI connections for readers, WAL for the writer
    fts.py                identifier splitting + injection-safe FTS5 query builder
    repository.py         every read query (parameterized, recursive CTEs for inheritance)
  models.py               Pydantic models shared by the API and MCP
  prompts/                prompt registry + Markdown templates
  api/                    FastAPI app + routes (MCP mounted at /mcp, SPA at /)
  build/                  validate_script (tsc), validate_cfg, workspace (addon files)
  orchestration/          workflows.py (registry) + engine.py (projects, DAG, gates)
  hosts.py                host registry + server-side leases (claim/heartbeat/release, TTL expiry) + lock-event audit (host_lock_events) for the Hosts page
  builds.py               server-owned build runs (SSH launch, UTF-16 log tail, host lease)
  deploys.py              deployment registry (scp push + Get-FileHash MD5 verification)
  evidence.py             structured evidence store — what the `evidence` gate checks (kind/ref/data)
  test_runs.py            in-game test sessions mirroring the TEST STATUS HUD strip (start/step/finish/off)
  mcp_server.py           MCP tools/prompts/resources (mcp SDK 2.x MCPServer)
  web/                    index.html, app.js, style.css (no build step)

SQLite design

  • Every data table hangs off builds(id) with ON DELETE CASCADE. A whole build is written in one transaction, so readers see either the old build or the complete new one. Re-indexing the same build number replaces it. The last N builds are kept (--keep, default 5) for diff_builds.

  • search_fts is an FTS5 index over the current build of each game. name keeps identifiers whole (_ is a token character, so mp_round* prefix-matches). name_parts and the body hold split forms (m_iHealth → m i health), so word searches find identifiers. Results are ranked by bm25 with column weights, with exact name matches first.

  • User input never becomes FTS syntax: it is reduced to quoted tokens and bound as a parameter. LIKE filters escape % and _. The only dynamic SQL is placeholder lists and fixed column names.

  • Entity and schema inheritance are resolved with recursive CTEs. row_number() keeps the nearest definition of an overridden member.

  • The API and MCP read docs data read-only (mode=ro), one connection per request or tool call. Only orchestration (projects, project_steps, project_events) writes, through the WAL writer connection.

Development

uv run pytest -q                     # 115 tests: parsers, repository, API, MCP, build tools, orchestration, hosts, builds/deploys (incl. material guard), evidence, test runs, web-UI visibility
uv run ruff check src tests && uv run ruff format src tests
uv run python scripts/check_frontend.py   # renders every SPA route in headless Chrome (server on :8765; extra routes as args)

Roadmap

  • Human docs: load the Valve Developer Community (MediaWiki API), source2.wiki (git), and the CounterStrikeSharp and Deadworks docs into the existing docs table, with license and source URL per page.

  • Scheduled sync: cron or CI running s2docs sync. It's cheap because the checkouts are incremental.

  • Deeper Deadlock data: hero, ability and item VData values (KV3) from the VPKs via the Source 2 Viewer CLI.

  • Semantic search: embeddings alongside FTS5 for natural-language questions.

Available Tools

73 tools
add_evidenceA

Record structured evidence for a step. This is what the evidence gate checks — a step with an evidence gate CANNOT be marked done until at least one non-note row exists for it. Use kind='note' only for a supplementary remark (it does not satisfy the gate).

    Pass `step_id` to attach the row to a project step, or omit it to record evidence at project
    level (a measurement, a console transcript, a negative result). A step-less row satisfies no
    step gate, by design.

Inputs: kind (required), project_id (required), ref, data, +1 more. Returns: id, ref, data, kind, +4 more

build_cancelA
DestructiveIdempotent

Cancel a run: mark it cancelled and release its host lease (terminates the process when this server owns it).

Inputs: run_id (required). Returns: id, kind, lock, notes, +13 more

build_listA
Read-onlyIdempotent

Recent build runs (newest first), optionally one host and/or active only.

Inputs: active, host_id. Returns: result

build_logA
Read-onlyIdempotent

Tail a build run's log. The log is UTF-16 (resourcecompiler) — this decodes it for you, returning the detected encoding, whether the tail was truncated, and any MATERIAL GUARD warnings found in the full log (see build_status).

Inputs: run_id (required), lines. Returns: size, lines, run_id, encoding, +2 more

build_modelA

Write a Source 2 model document (.vmdl) for a mesh that is already inside the addon.

    `mesh` is addon-relative (e.g. models/probe/probe.dmx). The document copies the shape the
    game's own compiler accepts — compile it on a host with resourcecompiler afterwards
    (build_start). Returns the written path. Use overwrite=true to replace an existing .vmdl.

Inputs: mesh (required), name (required), addon (required), game, +2 more. Returns: result

build_startA
Destructive

Start a server-owned build run on a host: acquires the host lease, runs the command over SSH and returns a run id immediately. Do NOT poll with sleeps — use build_wait(run_id) or build_status. The host lease is released automatically when the run reaches a terminal status. The lease's phase is the run kind (e.g. 'compile'), so a run whose compile hangs while its heartbeat stays fresh reads STUCK on list_hosts — the server, not the client, decides that.

Inputs: command (required), host_id (required), kind, notes, +1 more. Returns: id, kind, lock, notes, +13 more

build_statusA
Read-onlyIdempotent

One build run's record: status, exit code, parsed summary counts, material guard and artifacts.

    `warnings` carries the MATERIAL GUARD findings when the log referenced missing/reserved materials
    (Valve's `_vrf/` subtree, or another map's `maps/<other>/` subtree): counts per kind plus the first
    few offending paths. Such a run can still be 'succeeded' — the resourcecompiler exits 0 and the map
    then error-renders as a red/black checkerboard, so treat a non-None `warnings` as NOT clean.

Inputs: run_id (required). Returns: id, kind, lock, notes, +13 more

build_waitA
Read-onlyIdempotent

Block (server-side, no client sleep loop) until the run finishes, then return its record. Raises if it is still running after timeout_sec. With strict_materials, a run whose log has material-guard warnings is an error even though its status is 'succeeded'.

Inputs: run_id (required), timeout_sec, strict_materials. Returns: id, kind, lock, notes, +13 more

claim_hostA
Idempotent

Claim exclusive use of a host. Fails if another owner holds a fresh lock — a stale lock (heartbeat older than its TTL, e.g. from a crashed worker) is taken over automatically. Always report a phase (what you are doing); the server flags a lock STUCK when its heartbeat is fresh but the phase has not moved for too long, so keep the phase current (pass a new phase to heartbeat_host) as you progress. Heartbeat while you work; release with release_host when done.

Inputs: owner (required), host_id (required), note, force, +2 more. Returns: note, fresh, owner, phase, +9 more

deploy_listB
Read-onlyIdempotent

Recent deployments (newest first), optionally one host.

Inputs: host_id. Returns: result

deploy_pushA
Destructive

Copy files to a host and verify what landed (Get-FileHash over SSH). Returns a deploy id whose entries carry the server-checked md5 + verified flags. Call deploy_verify later to re-check.

    The push takes the host lease for the copy+verify window: a host held by another owner is a
    conflict (pass that owner if it is you), a free host is claimed as 'deploy:<id>' and released
    when the copy finishes. Inside an orchestration project the project already holds the lease as
    'project:<project_id>', so pass that as `owner` (the `cs2_live_test` deploy step does).

Inputs: files (required), host_id (required), dest_root (required), addon, +4 more. Returns: id, addon, notes, owner, +7 more

deploy_verifyB
Idempotent

Re-check a recorded deployment's MD5s on the host and update its status.

Inputs: deploy_id (required). Returns: id, addon, notes, owner, +7 more

diff_buildsA
Read-onlyIdempotent

Show what changed between two ingested builds, at member granularity: one row per added/ removed/changed schema field, entity member, game-event field, script symbol, convar, Pulse binding or Hammer class. Each entry carries group (the parent class/entity/event/library) so members cluster under it; before/after hold only that member's own value. counts gives the exact per-section added/removed/changed breakdown ({section: {added, removed, changed}}), taken BEFORE the per-section limit cap, so it stays true even when sections is truncated to the first limit entries; totals[section] is the sum of that section's three counts. Use it after a game patch to find code or configs that might break.

Inputs: game (required), to_build, from_build. Returns: game, limit, counts, totals, +3 more

find_entities_with_ioA
Read-onlyIdempotent

Find entity classes (including base classes) that declare an input or output with this name. Subclasses inherit it too, so check candidates with get_entity.

Inputs: game (required), name (required), side, limit. Returns: items, limit, total, offset

get_addonB
Read-onlyIdempotent

Show an addon's local root and file list.

Inputs: name (required), game. Returns: game, name, root, files

get_convarA
Read-onlyIdempotent

Get a console variable or command: default, min/max, flags (cheat, replicated, ...) and help text.

Inputs: game (required), name (required). Returns: kind, name, flags, max_value, +3 more

get_docA
Read-onlyIdempotent

Get a community documentation page (source2.wiki) as Markdown, with its URL and license for citation.

Inputs: key (required). Returns: key, url, body, game, +4 more

get_entityA
Read-onlyIdempotent

Get a Hammer entity: keyvalues (with choices), inputs and outputs, including inherited ones. defined_in shows which base declares each member.

Inputs: game (required), classname (required), side, include_inherited. Returns: side, bases, origin, members, +10 more

get_exampleA
Read-onlyIdempotent

Get the full source of an example script. Prefer adapting these Valve-written patterns.

Inputs: name (required), game. Returns: api, name, path, origin, +2 more

get_game_eventA
Read-onlyIdempotent

Get a game event's fields and types (the effective definition after core/game/mod overrides).

Inputs: game (required), name (required). Returns: name, fields, overrides, description, +1 more

get_hostB
Read-onlyIdempotent

One host's registry entry: ssh target, shell flavor, probed facts, and lock.

Inputs: host_id (required). Returns: id, os, lock, facts, +6 more

get_projectB
Read-onlyIdempotent

Full project state: every step's status, notes, artifacts and recent events.

Inputs: project_id (required). Returns: id, game, goal, addon, +9 more

get_prompt_textA
Read-onlyIdempotent

Render one of this server's prompts as text, for clients that can't fetch MCP prompts mid-task.

Inputs: name (required), arguments. Returns: result

get_schema_classA
Read-onlyIdempotent

Get an engine schema class or enum: fields with types, defaults and metadata, the inheritance chain and direct subclasses. Use it for networked fields, VData keys (Deadlock abilities) and plugin offsets.

Inputs: game (required), name (required), module, include_inherited. Returns: kind, name, bases, fields, +6 more

get_script_apiA
Read-onlyIdempotent

Look up the CS2 cs_script (point_script) JavaScript API. A type name returns the type with all members, including inherited ones. Anything else returns the matching members with signature and docs. Instance is the Domain type.

Inputs: symbol (required), game. Returns: result

heartbeat_hostA
Idempotent

Refresh your lock's heartbeat so it doesn't go stale mid-task. Heartbeating the SAME phase does not clear stuck-ness (that is the point); report a new phase as your work moves on.

Inputs: owner (required), host_id (required), note, phase. Returns: note, fresh, owner, phase, +9 more

job_assignB
Idempotent

Set (or move) a queued job's host and queue order.

Inputs: job_id (required), host_id (required), position. Returns: id, kind, links, phase, +21 more

job_attachA

Copy a local file into the job's artifacts (MCP runs on the Mac). Append-only: no overwrite. Images also record a screenshot event automatically.

Inputs: path (required), job_id (required), name. Returns: md5, url, name, size

job_cancelA

queued/running -> cancelled; the reason is recorded as a timeline event.

Inputs: job_id (required), reason. Returns: id, kind, links, phase, +21 more

job_enqueueA

Enqueue a job (validated intake -> queued). The intake is the gate: every missing required field comes back in one error with a field list. Set host_choice='ask' to queue unassigned.

Inputs: kind (required), title (required), target (required), purpose (required), +14 more. Returns: id, kind, links, phase, +21 more

job_eventA

Append one timeline event. phase updates the job's current phase; error/crash require a message and SHOULD carry data.log_tail so the job page can expand the failure in a .

Inputs: level (required), job_id (required), message (required), data, +1 more. Returns: id, data, level, phase, +3 more

job_finishA

Close out a running/queued job. Gate: summary must carry what, why, learned and next, each non-empty and <=600 chars; otherwise the call is rejected with the offending field names. status='inconclusive' is the honest close-out for a run that ended without a pass/fail verdict (a step-gated demo the user stopped answering) — it is NOT a pass.

Inputs: job_id (required), status (required), summary (required). Returns: id, kind, links, phase, +21 more

job_getB
Read-onlyIdempotent

One job with its full timeline (events), links to test runs/builds/deploys/evidence and artifacts.

Inputs: job_id (required). Returns: id, kind, links, phase, +21 more

job_listB
Read-onlyIdempotent

Jobs newest first. status='active' means queued + running. Each row carries error/crash counts.

Inputs: limit, status, host_id. Returns: result

job_nextA
Read-onlyIdempotent

The head queued job for a host, only when that host's lease is free. Errors (with the holder) when a fresh lock blocks it, and when the host's queue is empty.

Inputs: host_id (required). Returns: id, kind, links, phase, +21 more

job_request_screenshotA

Ask the runner for a screenshot: an info event plus params.pending_shot=true. The runner polls the job and answers by uploading an artifact, which clears the flag.

Inputs: job_id (required), note. Returns: id, kind, links, phase, +21 more

job_restatusA

Correct a FINISHED job's terminal status (terminal -> terminal), for a mis-recorded close-out — e.g. a run that finished 'passed' but never got a verdict. reason is mandatory and lands as a milestone timeline event. The job must already be terminal (a queued/running job finishes or is cancelled) and the target must differ from the current status, else the call is a conflict.

Inputs: job_id (required), reason (required), status (required). Returns: id, kind, links, phase, +21 more

job_screenshotsA
Read-onlyIdempotent

A job's screenshots tied to their step and verdict (seq, step, reason, url, web_url, verdict, dimensions) — the image context a reviewer pulls after a run. Filter by step/reason.

Inputs: job_id (required), step, reason. Returns: result

job_startB

queued -> running, recording the worker and started_at. A second call is a conflict.

Inputs: job_id (required), worker (required). Returns: id, kind, links, phase, +21 more

job_verdictA

Answer the running job's current step with a human verdict (pass | fail | skip). Writes a human-confirm evidence row through the same path as POST /api/evidence (so the confirm push files fire) and appends a milestone event. The job must be running and link a test run, else the call is a conflict.

Inputs: job_id (required), verdict (required), step, notes, +1 more. Returns: id, kind, links, phase, +21 more

list_addonsB
Read-onlyIdempotent

List addons in the local workspace.

Inputs: game. Returns: result

list_convarsB
Read-onlyIdempotent

List console variables/commands, filtered by substring and/or flag.

Inputs: game (required), flag, kind, limit, +2 more. Returns: items, limit, total, offset

list_docsA
Read-onlyIdempotent

Browse community documentation pages (titles, URLs) without bodies.

Inputs: game, limit, query. Returns: items, limit, total, offset

list_evidenceA
Read-onlyIdempotent

List evidence rows (newest first), optionally filtered by project, step and/or kind.

Inputs: kind, step_id, project_id. Returns: result

list_examplesA
Read-onlyIdempotent

List example scripts (Valve's cs_script_demo samples) with the API each one uses.

Inputs: game. Returns: result

list_game_eventsB
Read-onlyIdempotent

List game events, optionally filtered by a substring of the name or description.

Inputs: game (required), limit, query, offset. Returns: items, limit, total, offset

list_gamesA
Read-onlyIdempotent

List supported games with their currently indexed build and patch version.

Inputs: no named parameters. Returns: result

list_hostsA
Read-onlyIdempotent

List registered test/build hosts with their facts (shell, GPU, paths) and current lock state. CHECK THIS before driving any host: a fresh lock means another session/workstream owns it. Each lock carries phase (what the owner is doing), phase_age_sec and stuck — the SERVER marks a lock STUCK when its heartbeat is fresh but the phase has not moved past the configured threshold (liveness is not progress). Treat lock.stuck=true as "the owner may be hung".

Inputs: no named parameters. Returns: result

list_projectsB
Read-onlyIdempotent

List orchestration projects (most recently updated first).

Inputs: status. Returns: result

list_schema_classesB
Read-onlyIdempotent

List schema classes/enums by name substring, module, kind or declared field.

Inputs: game (required), kind, limit, query, +3 more. Returns: items, limit, total, offset

list_script_typesA
Read-onlyIdempotent

List every top-level cs_script type: the Instance const, classes, interfaces, enums and type aliases.

Inputs: game. Returns: result

list_test_runsB
Read-onlyIdempotent

Recent test sessions (newest first), optionally filtered by state.

Inputs: state. Returns: result

list_workflowsA
Read-onlyIdempotent

List orchestration workflows: multi-step recipes (research → design → build → validate → test → document) that chain this server's tools and prompts. Start one with start_project.

Inputs: game. Returns: result

next_stepA
Read-onlyIdempotent

Get the ready step(s) of a project: instructions, tools to use, rendered prompt text and the gate the server will check when the step is marked done.

Inputs: project_id (required). Returns: game, goal, addon, title, +6 more

probe_hostA
Idempotent

SSH to the host, run the probe script (GPU, RAM, processes, CS2 path/build) and store the facts. Use before planning work on a host — e.g. to see if a game or a heavy GPU process is running.

Inputs: host_id (required). Returns: id, os, lock, facts, +6 more

read_addon_fileB
Read-onlyIdempotent

Read a text file from an addon in the workspace.

Inputs: name (required), path (required), game. Returns: result

register_hostA
Idempotent

Register (or update) a test/build host. Upsert: omitted fields keep their previous values.

Inputs: host_id (required), ssh_target (required), os, notes, +3 more. Returns: id, os, lock, facts, +6 more

release_hostA
Idempotent

Release a host lock. Always call this when finished with a host.

Inputs: owner (required), host_id (required), force. Returns: result

scaffold_addonA

Create a new addon in the local workspace (content/csgo_addons// with scripts/, maps/, materials/, ...), a starter cs_script, Valve's point_script.d.ts + tsconfig.json, and a README with the Hammer setup steps. Fails if the addon already exists.

Inputs: name (required), game, with_script. Returns: game, name, root, files

search_docsA
Read-onlyIdempotent

Full-text search across everything. Each hit's key feeds the detail tool for its kind: convar/command→get_convar(name), entity→get_entity('side:classname'), schema_class/schema_enum→ get_schema_class('module:Name'), event→get_game_event, script→get_script_api, pulse→search_pulse, example→get_example(key), doc→get_doc(key). With game set, general docs are included too.

Inputs: query (required), game, kinds, limit. Returns: items, limit, total, offset

search_pulseC
Read-onlyIdempotent

Search Pulse (Valve's visual scripting) node bindings: entity inputs/outputs and functions.

Inputs: game (required), kind, limit, query, +1 more. Returns: items, limit, total, offset

server_infoA
Read-onlyIdempotent

This server's DB identity: db_path, data_dir, pid, started_at and version, plus status.

    Compare it against the other server's `GET /api/health` before writing so two servers pointed
    at different data dirs (a split store) are caught instead of silently diverging.

Inputs: no named parameters. Returns: DB identity of a running server (REST /api/health and MCP server_info return th…

start_projectA

Start a persistent orchestration project and get the first ready step(s). Progress is stored on the server, so any later session can continue with next_step(project_id).

Inputs: goal (required), workflow (required), game, addon, +3 more. Returns: game, goal, addon, title, +6 more

test_clearB
Idempotent

Dismiss a session (iso_test off): state=idle, releasing a still-held host lease.

Inputs: run_id (required). Returns: A server-side in-game test session, mirroring the iso-spectate TEST STATUS HUD…

test_finishA
Idempotent

Finish a running session pass/fail (the HUD's iso_test pass|fail): releases the host lease and records a console-transcript evidence row on the linked project when there is one.

Inputs: result (required), run_id (required), note. Returns: A server-side in-game test session, mirroring the iso-spectate TEST STATUS HUD…

test_startA

Start a server-side test session (state=running). Claims the host lease when a host is given. Mirror the state onto the in-game TEST STATUS HUD yourself via iso_test start over netcon.

Inputs: name (required), host_id, project_id. Returns: A server-side in-game test session, mirroring the iso-spectate TEST STATUS HUD…

test_statusA
Read-onlyIdempotent

One test run's record, or the current active run when no id is given.

Inputs: run_id. Returns: A server-side in-game test session, mirroring the iso-spectate TEST STATUS HUD…

test_stepA
Idempotent

Set the running session's current step (the HUD strip's iso_test step). Running runs only.

Inputs: text (required), run_id (required). Returns: A server-side in-game test session, mirroring the iso-spectate TEST STATUS HUD…

update_stepA

Record progress on a step and get the next brief. Marking 'done' runs the step's gate (e.g. all addon scripts must type-check); if it fails you get the problems back — fix them and retry.

Inputs: status (required), step_id (required), project_id (required), notes, +1 more. Returns: game, goal, addon, title, +6 more

validate_cfgA
Read-onlyIdempotent

Check every command in a .cfg against the indexed convars/commands: unknown names (with suggestions), out-of-range values, cheat/devonly flags, and client-vs-server context.

Inputs: game (required), text (required), context. Returns: ok, errors, commands, warnings, +1 more

validate_cs_scriptA
Read-onlyIdempotent

Type-check a cs_script with the TypeScript compiler against this build's Valve point_script.d.ts. Errors with category unknown_api mean the method/property does not exist — fix before shipping. needs_type_narrowing gives the subclass to cast to. ALWAYS run this before handing a script to the user.

Inputs: code, game, path, addon. Returns: ok, errors, checker, warnings, +2 more

write_addon_fileA

Write a text source file (.js, .cfg, .vmat, .vdata, panorama .xml/.css, ...) into an addon. Binary/compiled assets are produced by Valve's tools, not written here. Use overwrite=true to replace.

Inputs: name (required), path (required), content (required), game, +1 more. Returns: path, size

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 73 tools

    Disambiguation3/5

    Most tools target distinct resources and actions, and the long descriptions clarify boundaries between docs, builds, jobs, hosts, tests, and projects. However, with 73 tools there is meaningful cross-domain overlap and potential confusion among job/test/project/evidence lifecycle tools and among related build/deploy/test list/status/finish operations.

    Naming Consistency3/5

    All names use snake_case, which helps readability, and many tools are grouped by domain prefixes such as job_, build_, deploy_, and test_. But the convention mixes noun_verb domain-prefixed names (job_list, build_start) with generic verb_noun names (list_projects, get_host), so it is readable but not fully consistent.

    Tool Count1/5

    The server exposes 73 tools, which is far beyond a well-scoped set for a single MCP server. Even though the surface spans several subdomains, the count is extreme and creates a heavy selection and maintenance burden.

    Completeness4/5

    The surface is broad and covers most major workflows: documentation lookup, schema/entity/script queries, addon creation and editing, build/deploy/test lifecycle, host management, jobs, evidence, and projects. Minor gaps remain, such as addon/file deletion or project cleanup operations, but core workflows are well covered.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      B
      maintenance
      Enables AI clients to create, inspect, edit, and deploy Counter-Strike 2 maps and addons by working with Hammer map files, addon source files, Valve's resource compiler, and Workshop Tools locally.
      1
      -
    • A
      license
      A
      quality
      B
      maintenance
      Enables coding agents to develop Open77 server resources with the full Lua API, permissions, events, game data, guides, and schemas pinned to a specific server build, plus validation and live server tools.
      27
      62 npm
      1
      MIT
    • F
      license
      Not graded
      quality
      A
      maintenance
      Enables AI assistants to discover and invoke tools for saving and retrieving code snippets, generating style guides and wiki documentation, and orchestrating multi-agent documentation workflows with semantic search.
      -