kill-switch-mcp 1.1.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/server.js +241 -242
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  import { existsSync as existsSync3 } from "fs";
15
15
  import { readFile as readFile2 } from "fs/promises";
16
16
  import { join as join3 } from "path";
17
+ import { homedir as homedir3 } from "os";
17
18
 
18
19
  // src/sdk/types.ts
19
20
  var PRAYER_NAMES = [
@@ -3452,6 +3453,7 @@ class BotActions {
3452
3453
  import { readFile } from "fs/promises";
3453
3454
  import { join } from "path";
3454
3455
  import { existsSync } from "fs";
3456
+ import { homedir } from "os";
3455
3457
 
3456
3458
  class BotManager {
3457
3459
  connections = new Map;
@@ -3471,9 +3473,9 @@ class BotManager {
3471
3473
  let gateway = gatewayUrl || this.defaultGatewayUrl;
3472
3474
  let showChat = false;
3473
3475
  if (!password) {
3474
- const envPath = join(process.cwd(), "bots", name, "bot.env");
3476
+ const envPath = join(homedir(), ".killswitch", "bots", name, "bot.env");
3475
3477
  if (!existsSync(envPath)) {
3476
- throw new Error(`Bot "${name}" not found. No bots/${name}/bot.env file exists. Call login with an agent_name to create one.`);
3478
+ throw new Error(`Bot "${name}" not found. No ~/.killswitch/bots/${name}/bot.env file exists. Call login with an agent_name to create one.`);
3477
3479
  }
3478
3480
  const envContent = await readFile(envPath, "utf-8");
3479
3481
  const env = this.parseEnv(envContent);
@@ -3801,7 +3803,7 @@ function formatWorldState(state, stateAgeMs) {
3801
3803
  // src/wallet/index.ts
3802
3804
  import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
3803
3805
  import { join as join2 } from "path";
3804
- import { homedir } from "os";
3806
+ import { homedir as homedir2 } from "os";
3805
3807
  import {
3806
3808
  createPublicClient,
3807
3809
  createWalletClient,
@@ -3812,7 +3814,7 @@ import {
3812
3814
  pad
3813
3815
  } from "viem";
3814
3816
  import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
3815
- var WALLET_DIR = join2(homedir(), ".killswitch");
3817
+ var WALLET_DIR = join2(homedir2(), ".killswitch");
3816
3818
  var WALLET_FILE = join2(WALLET_DIR, "access-key.json");
3817
3819
  var TEMPO_RPC = process.env.TEMPO_RPC_URL || "https://rpc.moderato.tempo.xyz";
3818
3820
  var TEMPO_CHAIN_ID = parseInt(process.env.TEMPO_CHAIN_ID || "42431");
@@ -4067,84 +4069,144 @@ var SERVER_URL2 = (() => {
4067
4069
  return process.env.KILL_SWITCH_SERVER || "localhost";
4068
4070
  })();
4069
4071
  console.error(`[Kill Switch MCP] Server: ${SERVER_URL2}`);
4070
- var GAME_DESCRIPTION = `Kill Switch is a Hunger Games-style battle royale where AI agents fight to the death.
4072
+ var KILLSWITCH_HOME = join3(homedir3(), ".killswitch");
4073
+ var BOTS_DIR = join3(KILLSWITCH_HOME, "bots");
4074
+ var GUIDE = `# Kill Switch — Player Guide
4075
+
4076
+ You are playing Kill Switch — a Hunger Games-style battle royale where AI agents fight to the death.
4077
+
4078
+ YOU are the brain of your bot. Your human talks strategy, you execute it in the game.
4079
+
4080
+ **PERMADEATH**: If your agent dies, it's dead forever. No respawns. Only the last one standing wins.
4081
+
4082
+ ---
4071
4083
 
4072
- YOU are the brain of your player's bot. The human talks to you about strategy, and you
4073
- execute it by sending code to the game via execute_code.
4084
+ ## Quick Start
4074
4085
 
4075
- ## How It Works
4076
- 1. Call login to register and connect (lands you in the lobby)
4077
- 2. Your bot spawns in Barbarian Village lobby with EMPTY inventory
4078
- 3. Call join_game with mode "shorty" (or "longy" when available) to queue up
4079
- 4. Chat with your human about strategy while waiting
4080
- 5. Call wait_for_game_start it blocks until the admin starts the match
4081
- 6. When it returns, you're in the arena — start fighting!
4086
+ 1. \`login\` connects to the game (creates account if needed)
4087
+ 2. \`join_game\` with mode \`"shorty"\` queues you up
4088
+ 3. \`wait_for_game_start\` blocks until teleported to the arena
4089
+ 4. \`execute_code\` fight! Use \`bot\`, \`sdk\`, and \`actions\` globals.
4090
+
4091
+ Before writing any execute_code, read \`killswitch://sdk-reference\` for the complete API.
4092
+
4093
+ ---
4082
4094
 
4083
4095
  ## Game Modes
4084
- - **SHORTY**: Quick battle royale at Draynor Manor. 50 atk/str/def, 99 HP. Loot & fight.
4085
- - **LONGY**: (Coming soon) Large survival map. Level 1 stats, skill up, craft, outlast.
4096
+
4097
+ ### Shorty (Quick Battle Royale)
4098
+ - **Arena**: Draynor Manor fenced grounds
4099
+ - **Stats**: 50 Attack, 50 Strength, 50 Defence, 99 Hitpoints
4100
+ - **Start**: Empty inventory — everything comes from ground loot
4101
+ - **Win condition**: Last agent alive
4102
+
4103
+ ### Longy (Coming Soon)
4104
+ - Large map: Falador to Port Sarim
4105
+ - Level 1 everything — skill up, craft gear, fish food, outlast everyone
4106
+
4107
+ ---
4086
4108
 
4087
4109
  ## The Arena (Shorty)
4088
- - You spawn inside Draynor Manor's fenced arena with nothing
4089
- - Items are scattered on the ground in a cornucopia pattern:
4090
- - Outer ring: bronze/iron weapons, leather armor, bread/meat
4091
- - Mid ring: mithril/adamant weapons, chainmail, trout/salmon
4092
- - Center: rune weapons, rune/adamant armor, lobster/swordfish
4093
- - Better items are closer to the center — but so is everyone else!
4094
4110
 
4095
- ## PERMADEATH
4096
- - If you die, your agent is DEAD FOREVER. No respawns, no second chances.
4097
- - Only the last agent standing wins. Winners earn party hats!
4111
+ You spawn on the perimeter of Draynor Manor with **nothing**. Items are scattered in a cornucopia pattern:
4098
4112
 
4099
- ## API Reference
4113
+ - **Outer ring** (safe, near spawn): Bronze/iron weapons, leather armor, bread/meat
4114
+ - **Mid ring**: Mithril/adamant weapons, chainmail, trout/salmon
4115
+ - **Center** (dangerous, high traffic): Rune weapons, rune/adamant armor, lobster/swordfish
4100
4116
 
4101
- ### Three globals in execute_code:
4102
- - **bot**: High-level actions (await required — they wait for the effect to complete)
4103
- - **sdk**: Low-level state access and direct commands
4104
- - **actions**: Pre-built strategy functions from your bot's actions/ directory
4117
+ **Better loot = more risk.** Everyone converges on the center.
4105
4118
 
4106
- ### bot methods (high-level, await each one):
4107
- - bot.pickupItem(/item name/i) — walk to and pick up a ground item
4108
- - bot.equipItem(/item name/i) — equip from inventory
4109
- - bot.eatFood(/item name/i) — eat food to heal
4110
- - bot.attackPlayer("name" or /pattern/i) — attack a player
4111
- - bot.attackNpc("name" or /pattern/i) — attack an NPC
4112
- - bot.walkTo(x, z) — pathfind and walk (complex, uses server pathfinding)
4119
+ ---
4120
+
4121
+ ## How to Play
4113
4122
 
4114
- ### sdk methods (low-level, instant):
4115
- - sdk.sendWalk(x, z) click a tile to walk there (fast, no pathfinding)
4116
- - sdk.getState() full game state object
4117
- - sdk.getState().player.worldX / .worldZ your position
4118
- - sdk.getState().player.hp your current HP
4119
- - sdk.getState().player.maxHp — your max HP
4120
- - sdk.getInventory() — array of inventory items (each has .name)
4121
- - sdk.getEquipment() — array of equipment items
4122
- - sdk.findInventoryItem(/pattern/i) — find item in inventory
4123
- - sdk.findGroundItem(/pattern/i) — find item on ground (returns { name, x, z, distance } — NOT .worldX!)
4124
- - sdk.getGroundItems() — all nearby ground items (each has .name, .x, .z, .distance, .id, .count)
4125
- - sdk.getNearbyPlayers() — nearby players (each has .name, .combatLevel, .distance, .x, .z)
4126
- - sdk.waitForTicks(n) — wait n game ticks (~0.6s each)
4123
+ ### Interactive Mode (default)
4124
+ Your human gives you strategy between rounds:
4125
+ - "Rush the center, grab the best gear"
4126
+ - "Play safe, loot the outer ring and wait"
4127
+ - "Focus the weakest player"
4127
4128
 
4128
- ### actions (pre-built strategies, await each one):
4129
- Actions are loaded from your bot's actions/ directory at login. Default actions:
4130
- - actions.lootAndEquip({ maxItems, radius, foodOnly }) — scan ground, pick up best items, equip gear
4131
- - actions.fightLoop({ eatAt, duration, target, fleeAt }) — attack nearest/weakest, auto-eat, run loop
4132
- - actions.kite({ safeHp, duration, direction }) — retreat from enemies while eating
4129
+ You turn their words into execute_code calls. Keep each call to **15-30 seconds**, then report back what happened so they can adjust.
4130
+
4131
+ ### Autonomous Mode (\`claude -p\`)
4132
+ Run a full game with no human input:
4133
+ \`\`\`
4134
+ claude -p 'Play Kill Switch. Create agent named Fury. Join shorty, play aggressively. Exit when done.'
4135
+ \`\`\`
4136
+ Claude makes all decisions. Same tools, same flow — just no human between rounds.
4137
+
4138
+ ---
4133
4139
 
4134
- ### IMPORTANT NOTES:
4135
- - Blocking UI is auto-dismissed before every execute_code call — you do NOT need to call bot.dismissBlockingUI()
4136
- - bot methods THROW on failure use try/catch if you want to handle errors gracefully
4137
- - bot.walkTo() uses server pathfinding (can be slow). For quick movement, use sdk.sendWalk(x, z) + sdk.waitForTicks(5)
4138
- - State path: sdk.getState().player.worldX / .worldZ / .hp / .maxHp (NOT .localPlayer, NOT .hitpoints)
4139
- - Ground items use .x / .z (NOT .worldX / .worldZ): sdk.getGroundItems()[0].x
4140
- - Players use .x / .z for nearby players, .worldX / .worldZ for your own player
4141
- - Keep execute_code calls to 15-30 seconds, then check state and adapt
4140
+ ## Pre-Built Actions
4141
+
4142
+ Your bot ships with action functions in the \`actions\` object. **Use these first** — they handle the common patterns:
4143
+
4144
+ \`\`\`typescript
4145
+ // Grab nearby items and equip best gear
4146
+ await actions.lootAndEquip({ maxItems: 5 });
4147
+
4148
+ // Fight nearest player with auto-eat (15 seconds)
4149
+ await actions.fightLoop({ eatAt: 25, duration: 15000 });
4150
+
4151
+ // Fight a specific player
4152
+ await actions.fightLoop({ target: /vex/i, eatAt: 20 });
4153
+
4154
+ // Retreat while eating to recover HP
4155
+ await actions.kite({ safeHp: 40 });
4156
+ \`\`\`
4142
4157
 
4143
- ## Key Tips
4144
- - You start with NOTHING — picking up items is your first priority!
4145
- - Grab a weapon first, then food, then armor
4146
- - Risk vs reward: center has the best loot but everyone converges there
4147
- - The human gives you strategy ("rush center", "play safe", "grab and run") — you turn it into code`;
4158
+ Combine them for a full turn:
4159
+ \`\`\`typescript
4160
+ const loot = await actions.lootAndEquip({ maxItems: 3 });
4161
+ const result = await actions.fightLoop({ eatAt: 25, duration: 15000 });
4162
+ if (result.hp < 15 && result.foodLeft > 0) {
4163
+ await actions.kite({ safeHp: 35 });
4164
+ }
4165
+ return result;
4166
+ \`\`\`
4167
+
4168
+ ### Custom Actions
4169
+ Create custom actions in \`~/.killswitch/bots/<name>/actions/\`. Each \`.ts\` file exports a default function that becomes \`actions.functionName()\`. Re-login to pick up new actions.
4170
+
4171
+ ---
4172
+
4173
+ ## Key Rules
4174
+
4175
+ 1. **Use MCP tools only** — never bash commands or scripts for gameplay
4176
+ 2. **Read killswitch://sdk-reference** before writing execute_code
4177
+ 3. **Pick up items first** — you start empty, looting is survival
4178
+ 4. **Equip before fighting** — weapon first, then armor
4179
+ 5. **Eat when HP < 25** — any food is better than no food
4180
+ 6. **Keep execute_code calls short** (15-30s) — return state, evaluate, adapt
4181
+ 7. **You cannot see other players' HP or inventory** — only name, combat level, position, distance
4182
+ 8. **bot methods throw on failure** — use try/catch if you want to handle errors
4183
+ 9. **sdk.sendWalk(x, z) for movement** — bot.walkTo() uses server pathfinding (slower, can fail)
4184
+ 10. **PERMADEATH** — play smart!
4185
+
4186
+ ---
4187
+
4188
+ ## Bot Data
4189
+
4190
+ Bot credentials and custom actions are stored in \`~/.killswitch/bots/<name>/\`:
4191
+ \`\`\`
4192
+ ~/.killswitch/
4193
+ bots/
4194
+ <name>/
4195
+ bot.env # BOT_USERNAME, PASSWORD, SERVER
4196
+ actions/ # Custom action files (.ts)
4197
+ \`\`\`
4198
+
4199
+ The \`login\` tool creates this automatically. You don't need to set up files manually.
4200
+
4201
+ ---
4202
+
4203
+ ## Troubleshooting
4204
+
4205
+ - **"Not connected"** — Call \`login\` again. It reconnects automatically.
4206
+ - **"No game state"** — The browser client needs a moment to load. Wait a few seconds.
4207
+ - **Agent is dead** — That agent is gone forever (permadeath). Create a new one with a different name.
4208
+ - **Items not picking up** — Check the SDK reference for correct method signatures and property names.
4209
+ `;
4148
4210
  var server = new Server({ name: "kill-switch", version: "3.0.0" }, { capabilities: { tools: {}, resources: {} } });
4149
4211
  var SDK_REFERENCE = `# Kill Switch SDK Reference
4150
4212
 
@@ -4181,7 +4243,7 @@ Wrap in try/catch if you want to handle failures gracefully.
4181
4243
  - \`await bot.talkTo(nameOrPattern)\` — Talk to an NPC.
4182
4244
  - \`await bot.interactLoc(nameOrPattern, option)\` — Interact with a game object.
4183
4245
  - \`await bot.interactNpc(nameOrPattern, option)\` — Interact with an NPC.
4184
- - \`await bot.openDoor(x, z)\` — Open a door at coordinates.
4246
+ - \`await bot.openDoor(target)\` — Open a door. Pass a NearbyLoc object, string name, or RegExp. With no args, opens nearest door.
4185
4247
 
4186
4248
  ### Dialog
4187
4249
  - \`await bot.navigateDialog(choices)\` — Navigate through NPC dialog.
@@ -4199,10 +4261,10 @@ Wrap in try/catch if you want to handle failures gracefully.
4199
4261
  - \`sdk.getState()\` — Returns the full game state object. See "State Object Shape" below.
4200
4262
  - \`sdk.getInventory()\` — Array of inventory items. Each has: \`.name\`, \`.id\`, \`.count\`, \`.slot\`
4201
4263
  - \`sdk.getEquipment()\` — Array of equipped items. Each has: \`.name\`, \`.id\`, \`.slot\`
4202
- - \`sdk.getNearbyPlayers()\` — Array of nearby players. Each has: \`.name\`, \`.combatLevel\`, \`.distance\`, \`.x\`, \`.z\`
4264
+ - \`sdk.getNearbyPlayers()\` — Array of nearby players. Each has: \`.index\`, \`.name\`, \`.combatLevel\`, \`.distance\`, \`.x\`, \`.z\`
4203
4265
  - \`sdk.getGroundItems()\` — Array of ground items. Each has: \`.name\`, \`.id\`, \`.count\`, \`.x\`, \`.z\`, \`.distance\`
4204
- - \`sdk.getNearbyLocs()\` — Array of nearby game objects.
4205
- - \`sdk.getNearbyNpcs()\` — Array of nearby NPCs.
4266
+ - \`sdk.getNearbyLocs()\` — Array of nearby game objects. Each has: \`.id\`, \`.name\`, \`.x\`, \`.z\`, \`.distance\`, \`.options\`
4267
+ - \`sdk.getNearbyNpcs()\` — Array of nearby NPCs. Each has: \`.index\`, \`.name\`, \`.combatLevel\`, \`.x\`, \`.z\`, \`.distance\`, \`.hp\`, \`.maxHp\`, \`.inCombat\`
4206
4268
 
4207
4269
  ### Search (instant, not async)
4208
4270
  - \`sdk.findInventoryItem(pattern)\` — Find first matching inventory item. Returns item or null.
@@ -4211,15 +4273,16 @@ Wrap in try/catch if you want to handle failures gracefully.
4211
4273
  - \`sdk.findNearbyNpc(pattern)\` — Find a nearby NPC. Returns NPC or null.
4212
4274
  - \`sdk.findNearbyLoc(pattern)\` — Find a nearby game object. Returns loc or null.
4213
4275
 
4214
- ### Direct Commands (instant, fire-and-forget)
4215
- - \`sdk.sendWalk(x, z)\` Click a tile to walk there. Fast, no pathfinding. Use for quick movement during combat.
4216
- - \`sdk.sendPickup(itemId, x, z)\` — Send raw pickup command.
4217
- - \`sdk.sendInteractPlayer(playerIndex, option)\` — Interact with a player directly.
4218
- - \`sdk.sendInteractNpc(npcId, option)\` — Interact with an NPC directly.
4219
- - \`sdk.sendInteractLoc(locId, x, z, option)\` — Interact with a game object directly.
4220
- - \`sdk.sendUseItem(slot)\` — Use an inventory item (e.g., eat food).
4276
+ ### Direct Commands (async, send action to server)
4277
+ All send* methods are async and return Promise<ActionResult>. Use await or fire-and-forget.
4278
+ - \`sdk.sendWalk(x, z)\` — Click a tile to walk there. Fast, no pathfinding. Use for quick movement.
4279
+ - \`sdk.sendPickup(x, z, itemId)\` — Pick up a ground item. Args are (x, z, itemId) — NOT (itemId, x, z).
4280
+ - \`sdk.sendInteractPlayer(playerIndex, option)\` — Interact with a player. Use player.index from getNearbyPlayers().
4281
+ - \`sdk.sendInteractNpc(npcIndex, option)\` — Interact with an NPC. Use npc.index from getNearbyNpcs(). NOT npcId.
4282
+ - \`sdk.sendInteractLoc(x, z, locId, option)\` — Interact with a game object. Args are (x, z, locId, option) — NOT (locId, x, z).
4283
+ - \`sdk.sendUseItem(slot, option)\` — Use an inventory item. option defaults to 1 (first option, e.g. eat/wear).
4221
4284
  - \`sdk.sendSetCombatStyle(style)\` — Change combat style (0-3).
4222
- - \`sdk.sendTogglePrayer(prayerId)\` — Toggle a prayer on/off.
4285
+ - \`sdk.sendTogglePrayer(prayer)\` — Toggle a prayer. Accepts prayer name string (e.g. 'protect_from_melee') or prayer ID number.
4223
4286
 
4224
4287
  ### Timing (async)
4225
4288
  - \`await sdk.waitForTicks(n)\` — Wait n game ticks. Each tick ≈ 0.6 seconds.
@@ -4242,16 +4305,23 @@ sdk.getState() returns:
4242
4305
  hp: number, // Current hitpoints
4243
4306
  maxHp: number, // Max hitpoints
4244
4307
  combatLevel: number,
4245
- animation: number, // Current animation ID (-1 = idle)
4308
+ animId: number, // Current animation ID (-1 = idle)
4309
+ combat: { // Combat sub-state
4310
+ inCombat: boolean,
4311
+ targetIndex: number,
4312
+ },
4246
4313
  },
4314
+ inGame: boolean, // Whether connected to game
4247
4315
  skills: [...], // Array of skill objects
4248
4316
  inventory: [...], // Array of inventory slots
4249
4317
  equipment: [...], // Array of equipment slots
4250
- groundItems: [...], // Nearby ground items (each has .name, .x, .z, .id, .count)
4251
- nearbyPlayers: [...], // Nearby players
4252
- nearbyNpcs: [...], // Nearby NPCs
4318
+ groundItems: [...], // Nearby ground items (each has .name, .x, .z, .id, .count, .distance)
4319
+ nearbyPlayers: [...], // Nearby players (each has .index, .name, .x, .z, .distance, .combatLevel)
4320
+ nearbyNpcs: [...], // Nearby NPCs (each has .index, .name, .x, .z, .distance, .hp, .maxHp)
4321
+ nearbyLocs: [...], // Nearby game objects (each has .id, .name, .x, .z, .distance, .options)
4253
4322
  dialog: { isOpen, ... }, // Dialog state
4254
4323
  modalOpen: boolean, // Whether a modal is open
4324
+ gameMessages: [...], // Recent game messages
4255
4325
  }
4256
4326
  \`\`\`
4257
4327
 
@@ -4267,10 +4337,20 @@ IMPORTANT PROPERTY NAMES:
4267
4337
 
4268
4338
  ### Quick loot (walk + pick up):
4269
4339
  \`\`\`
4270
- sdk.sendWalk(3106, 3353); // Click tile to walk there
4271
- await sdk.waitForTicks(5); // Wait to arrive
4272
- await bot.pickupItem(/lobster/i); // Pick up item at feet
4273
- await bot.equipItem(/scimitar/i); // Equip from inventory
4340
+ // Option A: Use bot.pickupItem (walks to item automatically, throws on failure)
4341
+ try { await bot.pickupItem(/lobster/i); } catch(e) { console.log("Missed lobster"); }
4342
+
4343
+ // Option B: Manual walk + raw pickup (more control)
4344
+ const item = sdk.findGroundItem(/lobster/i);
4345
+ if (item) {
4346
+ sdk.sendWalk(item.x, item.z); // Walk to item
4347
+ await sdk.waitForTicks(5); // Wait to arrive
4348
+ sdk.sendPickup(item.x, item.z, item.id); // Pick up (args: x, z, itemId)
4349
+ await sdk.waitForTicks(2); // Wait for server
4350
+ }
4351
+
4352
+ // Equip from inventory
4353
+ await bot.equipItem(/scimitar/i);
4274
4354
  \`\`\`
4275
4355
 
4276
4356
  ### Combat loop:
@@ -4298,10 +4378,16 @@ For combat/arena play, use \`sdk.sendWalk(x, z)\` + \`await sdk.waitForTicks(n)\
4298
4378
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
4299
4379
  return {
4300
4380
  resources: [
4381
+ {
4382
+ uri: "killswitch://guide",
4383
+ name: "Kill Switch Player Guide",
4384
+ description: "START HERE. Game overview, how to play, common patterns, and key rules. Read this before doing anything.",
4385
+ mimeType: "text/markdown"
4386
+ },
4301
4387
  {
4302
4388
  uri: "killswitch://sdk-reference",
4303
4389
  name: "Kill Switch SDK Reference",
4304
- description: "Complete API reference for bot, sdk, and actions objects used in execute_code. READ THIS FIRST before writing any game code.",
4390
+ description: "Complete API reference for bot, sdk, and actions objects used in execute_code. Read before writing game code.",
4305
4391
  mimeType: "text/markdown"
4306
4392
  }
4307
4393
  ]
@@ -4309,6 +4395,17 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
4309
4395
  });
4310
4396
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
4311
4397
  const { uri } = request.params;
4398
+ if (uri === "killswitch://guide") {
4399
+ return {
4400
+ contents: [
4401
+ {
4402
+ uri: "killswitch://guide",
4403
+ mimeType: "text/markdown",
4404
+ text: GUIDE
4405
+ }
4406
+ ]
4407
+ };
4408
+ }
4312
4409
  if (uri === "killswitch://sdk-reference") {
4313
4410
  return {
4314
4411
  contents: [
@@ -4327,44 +4424,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4327
4424
  tools: [
4328
4425
  {
4329
4426
  name: "login",
4330
- description: `Log in to Kill Switch. Creates your account if needed, connects to the game server, and opens the browser client.
4331
-
4332
- Call this when the user says anything like "let's play", "join the game", "log in", "connect", etc.
4333
-
4334
- If the user already has a bot, this will reconnect. If not, it creates a new one. Idempotent — safe to call multiple times.
4335
-
4336
- ${GAME_DESCRIPTION}`,
4427
+ description: "Log in to Kill Switch. Creates account if needed, connects to game server, opens browser client. Idempotent — safe to call again. Read killswitch://guide first for game overview.",
4337
4428
  inputSchema: {
4338
4429
  type: "object",
4339
4430
  properties: {
4340
4431
  agent_name: {
4341
4432
  type: "string",
4342
- description: "Name for the bot (max 12 chars, alphanumeric). If not provided, a random name is generated. If the user has played before and wants to reuse their name, use that."
4433
+ description: "Name for the bot (max 12 chars, alphanumeric). If not provided, reuses existing bot or generates random name."
4343
4434
  }
4344
4435
  }
4345
4436
  }
4346
4437
  },
4347
4438
  {
4348
4439
  name: "execute_code",
4349
- description: `Execute TypeScript code on your connected bot. Three globals available: bot, sdk, actions.
4350
-
4351
- IMPORTANT: Read the "Kill Switch SDK Reference" resource (killswitch://sdk-reference) for the complete API.
4352
- Do NOT guess method names or property paths — only use what is documented in the SDK reference.
4353
-
4354
- Quick reminders:
4355
- - bot methods are async and THROW on failure
4356
- - sdk.getState().player.worldX / .worldZ / .hp / .maxHp
4357
- - Ground items use .x / .z (NOT .worldX)
4358
- - sdk.sendWalk(x, z) for fast movement, bot.walkTo(x, z) for pathfinding
4359
- - Blocking UI is auto-dismissed — do NOT call bot.dismissBlockingUI()
4360
-
4361
- You MUST call login before using this tool.`,
4440
+ description: "Execute TypeScript code on your bot. Globals: bot, sdk, actions. Read killswitch://sdk-reference for API. Bot methods throw on failure. Blocking UI is auto-dismissed.",
4362
4441
  inputSchema: {
4363
4442
  type: "object",
4364
4443
  properties: {
4365
4444
  code: {
4366
4445
  type: "string",
4367
- description: "TypeScript code to execute. Has access to bot (BotActions) and sdk (BotSDK)."
4446
+ description: "TypeScript code to execute. Has access to bot (BotActions), sdk (BotSDK), and actions."
4368
4447
  },
4369
4448
  timeout: {
4370
4449
  type: "number",
@@ -4376,7 +4455,7 @@ You MUST call login before using this tool.`,
4376
4455
  },
4377
4456
  {
4378
4457
  name: "get_status",
4379
- description: "Check your bot's current state without executing any code. Returns position, HP, inventory, nearby players. Use this to assess the situation before deciding what to do.",
4458
+ description: "Check bot state (position, HP, inventory, nearby players) without executing code.",
4380
4459
  inputSchema: {
4381
4460
  type: "object",
4382
4461
  properties: {}
@@ -4384,16 +4463,7 @@ You MUST call login before using this tool.`,
4384
4463
  },
4385
4464
  {
4386
4465
  name: "wait_for_game_start",
4387
- description: `Wait for the game to start. Call this after login and join_game while chatting with your human about strategy.
4388
-
4389
- This tool blocks until the game starts and your bot is teleported to the arena. When it returns, the fight is ON — immediately start your combat loop!
4390
-
4391
- Typical flow:
4392
- 1. login → connect to game
4393
- 2. join_game → pick a mode
4394
- 3. Chat strategy with human
4395
- 4. wait_for_game_start → blocks until game starts
4396
- 5. execute_code → start fighting!`,
4466
+ description: "Block until the match starts and you are teleported to the arena. Call after join_game.",
4397
4467
  inputSchema: {
4398
4468
  type: "object",
4399
4469
  properties: {
@@ -4406,20 +4476,14 @@ Typical flow:
4406
4476
  },
4407
4477
  {
4408
4478
  name: "join_game",
4409
- description: `Join a specific game mode queue. Call this after login when your human says which mode to play.
4410
-
4411
- Available modes:
4412
- - "shorty": Quick battle royale at Draynor Manor. 50 atk/str/def, 99 HP. Grab loot, fight, last one standing wins.
4413
- - "longy": (Coming soon) Large survival map with skill progression.
4414
-
4415
- After joining, call wait_for_game_start to wait for the game to begin.`,
4479
+ description: "Join a game mode queue. Call after login. After joining, call wait_for_game_start.",
4416
4480
  inputSchema: {
4417
4481
  type: "object",
4418
4482
  properties: {
4419
4483
  mode: {
4420
4484
  type: "string",
4421
4485
  enum: ["shorty", "longy"],
4422
- description: "Game mode to join"
4486
+ description: 'Game mode: "shorty" (quick battle royale) or "longy" (coming soon)'
4423
4487
  }
4424
4488
  },
4425
4489
  required: ["mode"]
@@ -4427,7 +4491,7 @@ After joining, call wait_for_game_start to wait for the game to begin.`,
4427
4491
  },
4428
4492
  {
4429
4493
  name: "disconnect_bot",
4430
- description: "Disconnect from the game. Use when done playing.",
4494
+ description: "Disconnect from the game.",
4431
4495
  inputSchema: {
4432
4496
  type: "object",
4433
4497
  properties: {}
@@ -4435,23 +4499,13 @@ After joining, call wait_for_game_start to wait for the game to begin.`,
4435
4499
  },
4436
4500
  {
4437
4501
  name: "setup_game_wallet",
4438
- description: `Set up a game wallet for paid tournaments. This creates a local access key that lets you join paid tournament matches.
4439
-
4440
- IMPORTANT: This tool handles real (or testnet) money. Explain to the user what's happening at each step.
4441
-
4442
- Prerequisites:
4443
- - The user must have the Tempo CLI installed (curl -fsSL https://tempo.xyz/install | bash)
4444
- - The user must have run "tempo wallet login" to create their Tempo account
4445
-
4446
- This tool generates a local access key. The user then needs to authorize it on their Tempo account (requires biometric/passkey confirmation). After that, the key is stored locally in ~/.killswitch/ and used for all tournament deposits.
4447
-
4448
- Learn more about Tempo access keys: https://docs.tempo.xyz/protocol/tips/tip-1011`,
4502
+ description: 'Set up a Tempo wallet access key for paid tournaments. Requires Tempo CLI installed and "tempo wallet login" completed. Handles real money — explain each step to user.',
4449
4503
  inputSchema: {
4450
4504
  type: "object",
4451
4505
  properties: {
4452
4506
  tempo_account_address: {
4453
4507
  type: "string",
4454
- description: `The user's Tempo account address (0x...). Get this by running "tempo wallet whoami".`
4508
+ description: `The user's Tempo account address (0x...). Get via "tempo wallet whoami".`
4455
4509
  }
4456
4510
  },
4457
4511
  required: ["tempo_account_address"]
@@ -4459,9 +4513,7 @@ Learn more about Tempo access keys: https://docs.tempo.xyz/protocol/tips/tip-101
4459
4513
  },
4460
4514
  {
4461
4515
  name: "check_wallet",
4462
- description: `Check your game wallet status and USDC balance. Shows your Tempo account address and how much USDC you have available for tournament buy-ins.
4463
-
4464
- If no game wallet is set up, this will tell you to run setup_game_wallet first.`,
4516
+ description: "Check game wallet address and USDC balance for tournament buy-ins.",
4465
4517
  inputSchema: {
4466
4518
  type: "object",
4467
4519
  properties: {}
@@ -4469,9 +4521,7 @@ If no game wallet is set up, this will tell you to run setup_game_wallet first.`
4469
4521
  },
4470
4522
  {
4471
4523
  name: "tournament_schedule",
4472
- description: `View upcoming paid tournaments. Shows start times, buy-in amounts, and how many players have signed up.
4473
-
4474
- This is a read-only tool — it doesn't cost anything to check the schedule.`,
4524
+ description: "View upcoming paid tournaments start times, buy-ins, player counts. Read-only.",
4475
4525
  inputSchema: {
4476
4526
  type: "object",
4477
4527
  properties: {}
@@ -4479,26 +4529,13 @@ This is a read-only tool — it doesn't cost anything to check the schedule.`,
4479
4529
  },
4480
4530
  {
4481
4531
  name: "join_tournament",
4482
- description: `Join a paid tournament by depositing the buy-in from your game wallet.
4483
-
4484
- THIS TOOL SPENDS REAL MONEY (or testnet money). Before calling this tool:
4485
- 1. Tell the user exactly which tournament they're joining (time, buy-in amount)
4486
- 2. Show their current wallet balance
4487
- 3. Ask them to confirm they want to proceed
4488
- 4. Only then call this tool
4489
-
4490
- The deposit is sent on-chain to the tournament's escrow contract. The user's buy-in is held in escrow until:
4491
- - They win → they receive 90% of the pot
4492
- - The match is cancelled → they get a full refund
4493
- - The server goes down → they can claim a refund after 1 hour
4494
-
4495
- The user must be logged in (call login first) so we know their in-game username.`,
4532
+ description: "Join a paid tournament by depositing buy-in from game wallet. SPENDS REAL MONEY. Confirm with user before calling. Must be logged in.",
4496
4533
  inputSchema: {
4497
4534
  type: "object",
4498
4535
  properties: {
4499
4536
  tournament_address: {
4500
4537
  type: "string",
4501
- description: "The tournament contract address to join (from tournament_schedule)"
4538
+ description: "Tournament contract address (from tournament_schedule)"
4502
4539
  }
4503
4540
  },
4504
4541
  required: ["tournament_address"]
@@ -4514,8 +4551,7 @@ async function loadActions(botName, bot, sdk) {
4514
4551
  const { existsSync: existsSync4, readdirSync, cpSync, readFileSync: readFileSync2 } = await import("fs");
4515
4552
  const { join: join4, basename } = await import("path");
4516
4553
  const { pathToFileURL } = await import("url");
4517
- const botsDir = join4(process.cwd(), "bots");
4518
- const actionsDir = join4(botsDir, botName, "actions");
4554
+ const actionsDir = join4(BOTS_DIR, botName, "actions");
4519
4555
  const defaultsDir = join4(new URL(".", import.meta.url).pathname, "..", "defaults", "actions");
4520
4556
  console.error(`[Kill Switch] Loading actions from ${actionsDir}`);
4521
4557
  if (!existsSync4(actionsDir) && existsSync4(defaultsDir)) {
@@ -4604,20 +4640,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4604
4640
  const parts = [`Already connected as "${activeBotName}".`];
4605
4641
  if (state) {
4606
4642
  parts.push("");
4607
- parts.push("── Current State ──");
4608
4643
  parts.push(formatWorldState(state, existing.sdk.getStateAge()));
4609
4644
  }
4610
- parts.push("");
4611
- parts.push("Your bot is in the game. Talk strategy with your human while waiting for the tournament to start!");
4612
4645
  return { content: [{ type: "text", text: parts.join(`
4613
4646
  `) }] };
4614
4647
  }
4615
4648
  }
4616
- const botsDir = join3(process.cwd(), "bots");
4617
4649
  let username;
4618
4650
  let password;
4651
+ const legacyBotsDir = join3(process.cwd(), "bots");
4652
+ if (botName && !existsSync3(join3(BOTS_DIR, botName, "bot.env")) && existsSync3(join3(legacyBotsDir, botName, "bot.env"))) {
4653
+ const { mkdirSync: mkdirSync2, cpSync } = await import("fs");
4654
+ mkdirSync2(join3(BOTS_DIR, botName), { recursive: true });
4655
+ cpSync(join3(legacyBotsDir, botName), join3(BOTS_DIR, botName), { recursive: true });
4656
+ console.error(`[Kill Switch] Migrated bot "${botName}" from ./bots/ to ~/.killswitch/bots/`);
4657
+ }
4619
4658
  if (botName) {
4620
- const envPath = join3(botsDir, botName, "bot.env");
4659
+ const envPath = join3(BOTS_DIR, botName, "bot.env");
4621
4660
  if (existsSync3(envPath)) {
4622
4661
  console.error(`[Kill Switch] Found existing bot "${botName}"`);
4623
4662
  const env = parseEnv(await readFile2(envPath, "utf-8"));
@@ -4628,7 +4667,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4628
4667
  username = botName;
4629
4668
  password = generateRandomPassword();
4630
4669
  const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
4631
- const botDir = join3(botsDir, botName);
4670
+ const botDir = join3(BOTS_DIR, botName);
4632
4671
  mkdirSync2(botDir, { recursive: true });
4633
4672
  writeFileSync2(join3(botDir, "bot.env"), [
4634
4673
  `BOT_USERNAME=${username}`,
@@ -4639,21 +4678,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4639
4678
  `));
4640
4679
  }
4641
4680
  } else {
4642
- const { readdirSync } = await import("fs");
4643
- if (existsSync3(botsDir)) {
4644
- const dirs = readdirSync(botsDir).filter((d) => d !== "_template" && existsSync3(join3(botsDir, d, "bot.env")));
4681
+ const { readdirSync, mkdirSync: mkdirSync2, writeFileSync: writeFileSync2, cpSync } = await import("fs");
4682
+ if (existsSync3(legacyBotsDir)) {
4683
+ const legacyDirs = readdirSync(legacyBotsDir).filter((d) => d !== "_template" && existsSync3(join3(legacyBotsDir, d, "bot.env")));
4684
+ for (const legacyName of legacyDirs) {
4685
+ if (!existsSync3(join3(BOTS_DIR, legacyName, "bot.env"))) {
4686
+ mkdirSync2(join3(BOTS_DIR, legacyName), { recursive: true });
4687
+ cpSync(join3(legacyBotsDir, legacyName), join3(BOTS_DIR, legacyName), { recursive: true });
4688
+ console.error(`[Kill Switch] Migrated bot "${legacyName}" from ./bots/ to ~/.killswitch/bots/`);
4689
+ }
4690
+ }
4691
+ }
4692
+ if (existsSync3(BOTS_DIR)) {
4693
+ const dirs = readdirSync(BOTS_DIR).filter((d) => d !== "_template" && existsSync3(join3(BOTS_DIR, d, "bot.env")));
4645
4694
  if (dirs.length > 0) {
4646
4695
  botName = dirs[0];
4647
4696
  console.error(`[Kill Switch] Reusing existing bot "${botName}"`);
4648
- const env = parseEnv(await readFile2(join3(botsDir, botName, "bot.env"), "utf-8"));
4697
+ const env = parseEnv(await readFile2(join3(BOTS_DIR, botName, "bot.env"), "utf-8"));
4649
4698
  username = env.BOT_USERNAME || botName;
4650
4699
  password = env.PASSWORD || "";
4651
4700
  } else {
4652
4701
  botName = generateRandomName();
4653
4702
  username = botName;
4654
4703
  password = generateRandomPassword();
4655
- const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
4656
- const botDir = join3(botsDir, botName);
4704
+ const botDir = join3(BOTS_DIR, botName);
4657
4705
  mkdirSync2(botDir, { recursive: true });
4658
4706
  writeFileSync2(join3(botDir, "bot.env"), [
4659
4707
  `BOT_USERNAME=${username}`,
@@ -4668,10 +4716,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4668
4716
  botName = generateRandomName();
4669
4717
  username = botName;
4670
4718
  password = generateRandomPassword();
4671
- const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
4672
- const botDir = join3(botsDir, botName);
4673
- mkdirSync2(botDir, { recursive: true });
4674
- writeFileSync2(join3(botDir, "bot.env"), [
4719
+ mkdirSync2(join3(BOTS_DIR, botName), { recursive: true });
4720
+ writeFileSync2(join3(BOTS_DIR, botName, "bot.env"), [
4675
4721
  `BOT_USERNAME=${username}`,
4676
4722
  `PASSWORD=${password}`,
4677
4723
  `SERVER=${SERVER_URL2}`,
@@ -4738,37 +4784,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4738
4784
  activeActionDescriptions = descriptions;
4739
4785
  const state = connection.sdk.getState();
4740
4786
  const parts = [
4741
- `Connected as "${username}"!`,
4742
- "",
4743
- "Your bot is in the game. A browser window should have opened showing the game view.",
4744
- "",
4745
- "You're in the tournament lobby (Barbarian Village) with:",
4746
- " - EMPTY inventory and no equipment",
4747
- " - Combat stats: 50 Attack, 50 Strength, 50 Defence, 99 Hitpoints",
4748
- "",
4749
- 'NEXT STEP: Call join_game with mode "shorty" (or "longy" when available) to queue up.',
4750
- "",
4751
- "Game modes:",
4752
- " - SHORTY: Quick battle royale at Draynor Manor. Loot, fight, last one standing.",
4753
- " - LONGY: (Coming soon) Large survival map with skill progression.",
4754
- "",
4755
- "After joining a game, call wait_for_game_start to wait for the game to begin."
4787
+ `Connected as "${username}".`,
4788
+ `Bot data: ~/.killswitch/bots/${botName}/`
4756
4789
  ];
4757
- parts.push("");
4758
- parts.push("── IMPORTANT: Read the SDK Reference ──");
4759
- parts.push('Before writing any execute_code, read the "Kill Switch SDK Reference" resource (killswitch://sdk-reference).');
4760
- parts.push("It contains the complete API for bot, sdk, and actions. Do NOT guess method names — use only what is documented.");
4761
4790
  if (activeActionDescriptions.length > 0) {
4762
4791
  parts.push("");
4763
- parts.push("── Loaded Actions ──");
4764
- parts.push("Use these in execute_code via the `actions` object:");
4792
+ parts.push("Loaded actions:");
4765
4793
  parts.push(...activeActionDescriptions);
4766
4794
  }
4767
4795
  if (state) {
4768
4796
  parts.push("");
4769
- parts.push("── Current State ──");
4770
4797
  parts.push(formatWorldState(state, connection.sdk.getStateAge()));
4771
4798
  }
4799
+ parts.push("");
4800
+ parts.push('Next: call join_game with mode "shorty" to queue up.');
4772
4801
  return { content: [{ type: "text", text: parts.join(`
4773
4802
  `) }] };
4774
4803
  } finally {
@@ -4892,7 +4921,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4892
4921
  if (isLongCode) {
4893
4922
  parts.push("");
4894
4923
  parts.push("── Tip ──");
4895
- parts.push(`Long script detected. Consider writing to a .ts file and running with: bun run bots/${botName}/script.ts`);
4924
+ parts.push(`Long script detected. Consider writing to a .ts file in ~/.killswitch/bots/${botName}/`);
4896
4925
  }
4897
4926
  const output = parts.length > 0 ? parts.join(`
4898
4927
  `) : "(no output)";
@@ -4925,33 +4954,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4925
4954
  console.error(`[Kill Switch] Tournament started! (detected after ${waitTime}s)`);
4926
4955
  const players = connection.sdk.getNearbyPlayers?.() || [];
4927
4956
  const parts = [
4928
- "TOURNAMENT STARTED!",
4957
+ "GAME STARTED! You are in the arena.",
4929
4958
  "",
4930
- `You've been teleported to Draynor Manor. The fight is ON!`,
4931
- `Position: ${state.player.worldX}, ${state.player.worldZ}`,
4959
+ `Position: (${state.player.worldX}, ${state.player.worldZ})`,
4932
4960
  `HP: ${state.player.hp}/${state.player.maxHp}`,
4961
+ `Nearby players: ${players.length > 0 ? players.map((p) => `${p.name} (dist ${p.distance})`).join(", ") : "scanning..."}`,
4933
4962
  "",
4934
- `Nearby players: ${players.length > 0 ? players.map((p) => `${p.name} (CB ${p.combatLevel}, dist ${p.distance})`).join(", ") : "scanning..."}`,
4935
- "",
4936
- "REMEMBER: You die = your agent dies FOREVER. No second chances.",
4937
- "",
4938
- "IMMEDIATELY use execute_code to start your strategy:",
4939
- "1. Pick up weapons and food from the ground",
4940
- "2. Equip best weapon found, then start fighting",
4941
- "3. Eat when HP gets low — you have 99 HP but no food yet!",
4942
- "",
4943
- "QUICK START: await actions.lootAndEquip({ maxItems: 5 }); await actions.fightLoop({ eatAt: 25, duration: 30000 });",
4944
- "",
4945
- "API REMINDERS:",
4946
- "- sdk.sendWalk(x, z) for quick movement (NOT bot.walkTo for combat)",
4947
- "- sdk.getState().player.worldX / .worldZ / .hp",
4948
- "- Blocking UI is auto-dismissed — do NOT call bot.dismissBlockingUI()"
4963
+ "Act now use execute_code to loot and fight."
4949
4964
  ];
4950
- if (activeActionDescriptions.length > 0) {
4951
- parts.push("");
4952
- parts.push("AVAILABLE ACTIONS:");
4953
- parts.push(...activeActionDescriptions);
4954
- }
4955
4965
  return { content: [{ type: "text", text: parts.join(`
4956
4966
  `) }] };
4957
4967
  }
@@ -4979,22 +4989,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4979
4989
  return errorResponse(data.error);
4980
4990
  }
4981
4991
  const parts = [
4982
- `Joined ${mode.toUpperCase()} queue!`,
4983
- "",
4984
- `Queue position: ${data.queuePosition}`,
4992
+ `Joined ${mode.toUpperCase()} queue.`,
4985
4993
  `Players in queue: ${data.queuePlayers?.join(", ") || "just you"}`,
4986
- ""
4994
+ "",
4995
+ "Next: call wait_for_game_start to wait for the match."
4987
4996
  ];
4988
- if (mode === "shorty") {
4989
- parts.push("SHORTY: Quick battle royale at Draynor Manor.");
4990
- parts.push("Stats: 50 Attack, 50 Strength, 50 Defence, 99 Hitpoints");
4991
- parts.push("You start with NOTHING — grab weapons and food from the ground!");
4992
- } else {
4993
- parts.push("LONGY: Large survival map (coming soon).");
4994
- }
4995
- parts.push("");
4996
- parts.push("Now call wait_for_game_start to wait for the game to begin.");
4997
- parts.push("Chat strategy with your human while you wait!");
4998
4997
  return { content: [{ type: "text", text: parts.join(`
4999
4998
  `) }] };
5000
4999
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kill-switch-mcp",
3
- "version": "1.1.9",
3
+ "version": "1.2.0",
4
4
  "description": "Kill Switch MCP Server — AI battle royale powered by Claude Code",
5
5
  "type": "module",
6
6
  "bin": {