# AGENTS.md — dreambot-mcp (dev guide)

> For any AI agent (Claude, Codex, …) **developing this MCP server**. Read this first. `README.md` is the
> user-facing run/config doc. This file is the architecture + conventions + build/validate loop.

## What this project is
The **Node MCP server only** that lets an agent play OSRS by driving a local **DreamBot** client. It
exposes ~127 tools (`dreambot_*` / `osrs_*`) and talks to the DreamBot **bridge** over HTTP. Pure Node,
**no runtime npm dependencies**.

```
Agent ⟷ dreambot-mcp (this) ⟷ DreamBot bridge (HTTP 127.0.0.1:8765) ⟷ DreamBot client ⟷ OSRS
```

- The **Java bridge** (the DreamBot script that opens the HTTP server on 8765 and runs commands on the
  game loop) is the **sibling folder `../bridge/`** in the `D:\dreambot-osrs` monorepo.
  **This folder does NOT contain or build the bridge.** It only speaks to it over HTTP.
- This folder (`mcp-server/`) is the MCP server. **Adding a tool usually touches BOTH folders** in one
  commit (bridge endpoint + MCP wrapper/definition) — see the root `../AGENTS.md`.
- The bridge must be running (DreamBot → script "DreamBot MCP Bridge" → Running) for any tool to work.

## Transports (`src/dreambot-mcp.js`)
The protocol core is transport-agnostic: `dispatch(method, params)` → result, wrapped by
`handleRpcRequest(request)` → response object (or `null` for a notification). Two entries use it:
- **stdio** (default): `startStdio()` — newline-delimited JSON-RPC on stdin/stdout. The agent spawns this
  as a child process. ⚠️ A client per-call timeout drops the whole pipe (no auto-reconnect) → all tools
  vanish until session restart.
- **HTTP** (`MCP_TRANSPORT=http` or `--http`): `src/http-transport.js` `startHttpServer()` — minimal
  Streamable-HTTP (POST `/mcp` → `application/json`; `Mcp-Session-Id` header on initialize; `202` for
  notifications; `GET` = 405; no deps). Long-lived → survives a client timeout (client reconnects) and is
  shareable by multiple agents / runnable as a service or Docker container. **Prefer HTTP for long runs.**

## Module layout (`src/`)
Dependency layering (lower = fewer deps):

| Module | Role | Depends on |
|---|---|---|
| `util.js` | PURE leaf helpers (RNG, clamp, sleep, item/string utils, `HUMANIZER`) | nothing |
| `bridge.js` | HTTP layer to the bridge (`fetchBridgeJson`/`postBridgeJson`/`runCommandAndWait`/`waitForCommand`/`ensureBridgeLoopFresh`/`BRIDGE_URL`/`USER_AGENT`/`FRESHNESS_EXEMPT_ROUTES`). Uses global `fetch` + `AbortSignal.timeout(3000)` | util |
| `commands.js` | Atomic action wrappers (equip/eat/interact/bank/GE/shop/walk/widget/prayer/magic/toggles + item_info/can_reach/inspect_tile/can_cast/deposit_box/make_x/click_tile_precise) + condition & inventory-wait helpers | bridge, util |
| `status.js` | `/context` wrapper for the bridge's human-facing overlay (`dreambot_get_context`/`dreambot_set_context`) | bridge |
| `routine-status.js` | Best-effort overlay updates for `run_task_plan` / routine progress (objective/activity/step/completion) | bridge |
| `background-jobs.js` | Non-routine background jobs (`run_task_plan`, `wintertodt_round`) with status/list/cancel polling | nothing |
| `dialogue.js` | talk/continue-dialogue autopilot | bridge, util |
| `wiki.js` / `wiki-parse.js` | OSRS wiki HTTP layer + pure parsers | bridge / nothing |
| `wintertodt.js` / `routines.js` / `loadout.js` | activity engines + the self-extensible routine DSL (persists to `data/routines/`) + gear/money engines | circular-require (see below) |
| `definitions.js` | `tools[]` schemas + `toolRoutes`/`actionRoutes`/`resourceRoutes` (pure data) | nothing |
| `http-transport.js` | HTTP Streamable transport (+ `GET /logs` debug) | mcp-log |
| `mcp-log.js` | in-memory ring buffer of tool calls (client-side log → `dreambot_mcp_logs`) | nothing |
| `dreambot-mcp.js` | **entry**: protocol core (`dispatch`/`handleRpcRequest`), `executeLeafStep`, `runTaskPlan`, humanizer actions, transport selection | all modules |

### Two require patterns (IMPORTANT)
1. **Clean leaves** (util, bridge, commands, dialogue, wiki): require bridge/util/wiki-parse directly → no
   circular require. **Prefer this for new modules.**
2. **Circular-require** (wintertodt, routines, loadout): the entry does `module.exports = { …shared infra… }`
   BEFORE `require('./<activity>')`; the activity does `const {..} = require('./dreambot-mcp')`. Works
   because function declarations are hoisted (resolve at call time).
- ⚠️ A missing dependency is silently `undefined` (destructuring an absent export) — NOT caught by
  `node --check` or smoke; only by a live call or a `_verify-*` script. Always verify the import set.

## How to add a new MCP tool
1. **(if it needs a new game action)** Coordinate with the **bridge repo** — the bridge must implement the
   `/command/<x>` endpoint (BridgeCommand factory → CommandRequestFactory → CommandDispatcher case). Reads
   are GET endpoints on the bridge. This MCP repo cannot add bridge endpoints.
2. JS wrapper in `commands.js` (or the right module) using `runCommandAndWait`/`fetchBridgeJson`. For a
   value-returning command, read `extractCommandResult(res)`.
3. `definitions.js`: add `{name, description, inputSchema}` to `tools[]` (+ `toolRoutes`/`actionRoutes` for
   thin pass-throughs).
4. `dreambot-mcp.js` `callTool`: `if (name === "dreambot_x") return callJsonTool(() => x(args));`.
5. Run the validation gauntlet.

## Build / validate loop (no build step — pure Node)
- `node --check src/<file>.js` — syntax, per file.
- `npm run smoke` (`src/_smoke.js`) — spawn server, initialize + tools/list → prints `TOOLS_COUNT`.
- `npm run verify` (`_verify-util`/`_verify-bridge`/`_verify-routines`) — require each module + call its
  functions (catches the silent missing-dep ReferenceError static checks miss).
- `node src/_test-http.js` — spawn `--http` server, POST initialize / notification / tools/list → asserts
  the HTTP transport (no live bridge needed).
- `node src/_test-call.js <tool> '<argsJson>'` — fresh server, ONE tool call against the LIVE bridge
  (true end-to-end). e.g. `node src/_test-call.js dreambot_health '{}'`.

## Conventions / gotchas
- **Command lag ≈ 1 game tick.** Wrappers post-then-poll `/command` (`waitForCommand`); never assume
  immediate effect.
- **Freshness gate:** before each `/command/*`, `ensureBridgeLoopFresh` checks `/health.loopFresh`.
  `FRESHNESS_EXEMPT_ROUTES` bypass it (stop/eat/drink/read-varbit).
- **Precision tools:** use `dreambot_inspect_tile` before `dreambot_click_tile_precise`; rotate first with
  `dreambot_rotate_to_tile` when a screen click reports no progress. Keep raw `dreambot_click_screen` as a
  last-resort interface/puzzle fallback.
- **NPC/object/ground-item ambiguity:** `/npcs`, `/objects`, and `/ground-items` are intentionally small
  (`radius:20`, `limit:50`) for normal observe loops and can be dominated by nearby clutter. For diagnostics or exact
  targeting, use `dreambot_scan_npcs`, `dreambot_scan_objects`, or `dreambot_scan_ground_items` with `limit`,
  `offset`, and filters such as `id`, `name`, `action`, or exact `x/y/z`. Omit `radius` unless intentionally limiting
  the scan; pagination is the normal safety control. Page through with `nextOffset` while
  `hasMore:true`, then prefer exact actions (`dreambot_interact_npc_by_index`, `dreambot_interact_object_at_tile`,
  `dreambot_pickup_ground_item_at_tile`) instead of nearest-by-id actions when reachability matters.
- **Prefer safe wrapper tools for agents:** public/quest agents should use `dreambot_entity_diagnostics`,
  `dreambot_entity_target_token` + `dreambot_interact_target`, `dreambot_shop_diagnostics` + `dreambot_shop_buy`,
  and `dreambot_ge_buy_safe`. Keep raw `dreambot_ge_open` + `dreambot_ge_buy` for dev/debug or explicit low-level
  control only. If `dreambot_ge_buy_safe` returns `insufficient_coins`, the agent should gather/mine/shop-buy/do
  F2P-safe money-making/switch quests, not walk to GE or talk to random NPCs.
- **Diagnostics before retrying:** when the game says `I can't reach that!` or an action returns success without
  visible progress, read `dreambot_tile_diagnostics`, `dreambot_widget_state`, `dreambot_walking_settings`, and the
  relevant bank/GE diagnostics before issuing the same command again.
- **Gameplay readiness:** `dreambot_observe.summary.gameplayReady` is the canonical in-world readiness flag.
  DreamBot can keep `login.stage` at `LOGIN_SCREEN_PLAY_NOW` even while `Client.isLoggedIn()`,
  `world.gameState:LOGGED_IN`, a valid player tile, and live gameplay are present. Do not classify that stage alone
  as logged out.
- **Text/chat tools:** `dreambot_type_text` types into the currently focused OSRS input; focus the widget first and
  never use it for secrets. `dreambot_get_chat` and `dreambot_observe.chat.recent` expose chat messages captured since
  the current bridge script started.
- **Keep tool calls short.** Long calls risk the client per-call timeout (HTTP transport makes it
  recoverable; it does not make a single 20-min call succeed). Use `background:true` for routines, task plans,
  and Wintertodt; poll status tools. `dreambot_take_break` is non-blocking by default and should be monitored
  with `dreambot_humanizer_status`.
- **Bridge request bodies** are parsed by flat regex on the bridge side (no nested JSON/arrays) — pass
  comma-separated strings.
- **No npm deps** — keep it that way (uses Node built-ins + global `fetch`, Node ≥18).

## Env / persistence
| Var | Default | Purpose |
|---|---|---|
| `DREAMBOT_BRIDGE_URL` | `http://127.0.0.1:8765` | bridge base URL |
| `MCP_TRANSPORT` | `stdio` | `http` for the HTTP transport |
| `MCP_HTTP_PORT` / `MCP_HTTP_HOST` | `8787` / `127.0.0.1` | HTTP bind |
| `DREAMBOT_DATA_DIR` | `<project>/data` | persistent data root; saved routines under `data/routines/*.json` (Docker-volume-friendly) |

Docker: `docker build -t dreambot-mcp .` then run with `MCP_TRANSPORT=http`, `MCP_HTTP_HOST=0.0.0.0`,
`DREAMBOT_BRIDGE_URL=http://host.docker.internal:8765` (bridge runs on the host), and a volume on
`/app/data`. See `README.md`.

## Consumers (do NOT edit from here)
- `D:\claude-osrs-worker` — the autonomous agent that *plays* via this server (its own `AGENTS.md`/`CLAUDE.md`).
- Bridge repo — the Java bridge + dev backlog for bridge-side features.
