kill-switch-mcp 1.1.10 → 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 +202 -218
  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,81 +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:
4112
+
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
4098
4116
 
4099
- ## API Reference
4117
+ **Better loot = more risk.** Everyone converges on the center.
4100
4118
 
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
4119
+ ---
4105
4120
 
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)
4121
+ ## How to Play
4113
4122
 
4114
- ### sdk methods (low-level):
4115
- State (sync): sdk.getState(), sdk.getInventory(), sdk.getEquipment(), sdk.getNearbyPlayers(), sdk.getGroundItems()
4116
- Search (sync): sdk.findInventoryItem(/pat/i), sdk.findGroundItem(/pat/i), sdk.findNearbyPlayer(/pat/i)
4117
- Commands (async): sdk.sendWalk(x, z), sdk.sendPickup(x, z, itemId), sdk.sendInteractLoc(x, z, locId, option)
4118
- Timing (async): await sdk.waitForTicks(n) — each tick ~0.6s
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"
4119
4128
 
4120
- Key state paths:
4121
- - sdk.getState().player.worldX / .worldZ — your position
4122
- - sdk.getState().player.hp / .maxHp — your hitpoints
4123
- - Ground items use .x / .z (NOT .worldX)
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.
4124
4130
 
4125
- ### actions (pre-built strategies, await each one):
4126
- Actions are loaded from your bot's actions/ directory at login. Default actions:
4127
- - actions.lootAndEquip({ maxItems, radius, foodOnly }) — scan ground, pick up best items, equip gear
4128
- - actions.fightLoop({ eatAt, duration, target, fleeAt }) attack nearest/weakest, auto-eat, run loop
4129
- - actions.kite({ safeHp, duration, direction }) — retreat from enemies while eating
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
+ ---
4130
4139
 
4131
- ### IMPORTANT NOTES:
4132
- - Blocking UI is auto-dismissed before every execute_code call — you do NOT need to call bot.dismissBlockingUI()
4133
- - bot methods THROW on failure use try/catch if you want to handle errors gracefully
4134
- - bot.walkTo() uses server pathfinding (can be slow). For quick movement, use sdk.sendWalk(x, z) + sdk.waitForTicks(5)
4135
- - State path: sdk.getState().player.worldX / .worldZ / .hp / .maxHp (NOT .localPlayer, NOT .hitpoints)
4136
- - Ground items use .x / .z (NOT .worldX / .worldZ): sdk.getGroundItems()[0].x
4137
- - Players use .x / .z for nearby players, .worldX / .worldZ for your own player
4138
- - 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
+ \`\`\`
4139
4157
 
4140
- ## Key Tips
4141
- - You start with NOTHING — picking up items is your first priority!
4142
- - Grab a weapon first, then food, then armor
4143
- - Risk vs reward: center has the best loot but everyone converges there
4144
- - 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
+ `;
4145
4210
  var server = new Server({ name: "kill-switch", version: "3.0.0" }, { capabilities: { tools: {}, resources: {} } });
4146
4211
  var SDK_REFERENCE = `# Kill Switch SDK Reference
4147
4212
 
@@ -4313,10 +4378,16 @@ For combat/arena play, use \`sdk.sendWalk(x, z)\` + \`await sdk.waitForTicks(n)\
4313
4378
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
4314
4379
  return {
4315
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
+ },
4316
4387
  {
4317
4388
  uri: "killswitch://sdk-reference",
4318
4389
  name: "Kill Switch SDK Reference",
4319
- 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.",
4320
4391
  mimeType: "text/markdown"
4321
4392
  }
4322
4393
  ]
@@ -4324,6 +4395,17 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
4324
4395
  });
4325
4396
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
4326
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
+ }
4327
4409
  if (uri === "killswitch://sdk-reference") {
4328
4410
  return {
4329
4411
  contents: [
@@ -4342,44 +4424,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4342
4424
  tools: [
4343
4425
  {
4344
4426
  name: "login",
4345
- description: `Log in to Kill Switch. Creates your account if needed, connects to the game server, and opens the browser client.
4346
-
4347
- Call this when the user says anything like "let's play", "join the game", "log in", "connect", etc.
4348
-
4349
- If the user already has a bot, this will reconnect. If not, it creates a new one. Idempotent — safe to call multiple times.
4350
-
4351
- ${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.",
4352
4428
  inputSchema: {
4353
4429
  type: "object",
4354
4430
  properties: {
4355
4431
  agent_name: {
4356
4432
  type: "string",
4357
- 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."
4358
4434
  }
4359
4435
  }
4360
4436
  }
4361
4437
  },
4362
4438
  {
4363
4439
  name: "execute_code",
4364
- description: `Execute TypeScript code on your connected bot. Three globals available: bot, sdk, actions.
4365
-
4366
- IMPORTANT: Read the "Kill Switch SDK Reference" resource (killswitch://sdk-reference) for the complete API.
4367
- Do NOT guess method names or property paths — only use what is documented in the SDK reference.
4368
-
4369
- Quick reminders:
4370
- - bot methods are async and THROW on failure
4371
- - sdk.getState().player.worldX / .worldZ / .hp / .maxHp
4372
- - Ground items use .x / .z (NOT .worldX)
4373
- - sdk.sendWalk(x, z) for fast movement, bot.walkTo(x, z) for pathfinding
4374
- - Blocking UI is auto-dismissed — do NOT call bot.dismissBlockingUI()
4375
-
4376
- 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.",
4377
4441
  inputSchema: {
4378
4442
  type: "object",
4379
4443
  properties: {
4380
4444
  code: {
4381
4445
  type: "string",
4382
- 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."
4383
4447
  },
4384
4448
  timeout: {
4385
4449
  type: "number",
@@ -4391,7 +4455,7 @@ You MUST call login before using this tool.`,
4391
4455
  },
4392
4456
  {
4393
4457
  name: "get_status",
4394
- 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.",
4395
4459
  inputSchema: {
4396
4460
  type: "object",
4397
4461
  properties: {}
@@ -4399,16 +4463,7 @@ You MUST call login before using this tool.`,
4399
4463
  },
4400
4464
  {
4401
4465
  name: "wait_for_game_start",
4402
- description: `Wait for the game to start. Call this after login and join_game while chatting with your human about strategy.
4403
-
4404
- 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!
4405
-
4406
- Typical flow:
4407
- 1. login → connect to game
4408
- 2. join_game → pick a mode
4409
- 3. Chat strategy with human
4410
- 4. wait_for_game_start → blocks until game starts
4411
- 5. execute_code → start fighting!`,
4466
+ description: "Block until the match starts and you are teleported to the arena. Call after join_game.",
4412
4467
  inputSchema: {
4413
4468
  type: "object",
4414
4469
  properties: {
@@ -4421,20 +4476,14 @@ Typical flow:
4421
4476
  },
4422
4477
  {
4423
4478
  name: "join_game",
4424
- description: `Join a specific game mode queue. Call this after login when your human says which mode to play.
4425
-
4426
- Available modes:
4427
- - "shorty": Quick battle royale at Draynor Manor. 50 atk/str/def, 99 HP. Grab loot, fight, last one standing wins.
4428
- - "longy": (Coming soon) Large survival map with skill progression.
4429
-
4430
- 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.",
4431
4480
  inputSchema: {
4432
4481
  type: "object",
4433
4482
  properties: {
4434
4483
  mode: {
4435
4484
  type: "string",
4436
4485
  enum: ["shorty", "longy"],
4437
- description: "Game mode to join"
4486
+ description: 'Game mode: "shorty" (quick battle royale) or "longy" (coming soon)'
4438
4487
  }
4439
4488
  },
4440
4489
  required: ["mode"]
@@ -4442,7 +4491,7 @@ After joining, call wait_for_game_start to wait for the game to begin.`,
4442
4491
  },
4443
4492
  {
4444
4493
  name: "disconnect_bot",
4445
- description: "Disconnect from the game. Use when done playing.",
4494
+ description: "Disconnect from the game.",
4446
4495
  inputSchema: {
4447
4496
  type: "object",
4448
4497
  properties: {}
@@ -4450,23 +4499,13 @@ After joining, call wait_for_game_start to wait for the game to begin.`,
4450
4499
  },
4451
4500
  {
4452
4501
  name: "setup_game_wallet",
4453
- description: `Set up a game wallet for paid tournaments. This creates a local access key that lets you join paid tournament matches.
4454
-
4455
- IMPORTANT: This tool handles real (or testnet) money. Explain to the user what's happening at each step.
4456
-
4457
- Prerequisites:
4458
- - The user must have the Tempo CLI installed (curl -fsSL https://tempo.xyz/install | bash)
4459
- - The user must have run "tempo wallet login" to create their Tempo account
4460
-
4461
- 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.
4462
-
4463
- 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.',
4464
4503
  inputSchema: {
4465
4504
  type: "object",
4466
4505
  properties: {
4467
4506
  tempo_account_address: {
4468
4507
  type: "string",
4469
- 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".`
4470
4509
  }
4471
4510
  },
4472
4511
  required: ["tempo_account_address"]
@@ -4474,9 +4513,7 @@ Learn more about Tempo access keys: https://docs.tempo.xyz/protocol/tips/tip-101
4474
4513
  },
4475
4514
  {
4476
4515
  name: "check_wallet",
4477
- 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.
4478
-
4479
- 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.",
4480
4517
  inputSchema: {
4481
4518
  type: "object",
4482
4519
  properties: {}
@@ -4484,9 +4521,7 @@ If no game wallet is set up, this will tell you to run setup_game_wallet first.`
4484
4521
  },
4485
4522
  {
4486
4523
  name: "tournament_schedule",
4487
- description: `View upcoming paid tournaments. Shows start times, buy-in amounts, and how many players have signed up.
4488
-
4489
- 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.",
4490
4525
  inputSchema: {
4491
4526
  type: "object",
4492
4527
  properties: {}
@@ -4494,26 +4529,13 @@ This is a read-only tool — it doesn't cost anything to check the schedule.`,
4494
4529
  },
4495
4530
  {
4496
4531
  name: "join_tournament",
4497
- description: `Join a paid tournament by depositing the buy-in from your game wallet.
4498
-
4499
- THIS TOOL SPENDS REAL MONEY (or testnet money). Before calling this tool:
4500
- 1. Tell the user exactly which tournament they're joining (time, buy-in amount)
4501
- 2. Show their current wallet balance
4502
- 3. Ask them to confirm they want to proceed
4503
- 4. Only then call this tool
4504
-
4505
- The deposit is sent on-chain to the tournament's escrow contract. The user's buy-in is held in escrow until:
4506
- - They win → they receive 90% of the pot
4507
- - The match is cancelled → they get a full refund
4508
- - The server goes down → they can claim a refund after 1 hour
4509
-
4510
- 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.",
4511
4533
  inputSchema: {
4512
4534
  type: "object",
4513
4535
  properties: {
4514
4536
  tournament_address: {
4515
4537
  type: "string",
4516
- description: "The tournament contract address to join (from tournament_schedule)"
4538
+ description: "Tournament contract address (from tournament_schedule)"
4517
4539
  }
4518
4540
  },
4519
4541
  required: ["tournament_address"]
@@ -4529,8 +4551,7 @@ async function loadActions(botName, bot, sdk) {
4529
4551
  const { existsSync: existsSync4, readdirSync, cpSync, readFileSync: readFileSync2 } = await import("fs");
4530
4552
  const { join: join4, basename } = await import("path");
4531
4553
  const { pathToFileURL } = await import("url");
4532
- const botsDir = join4(process.cwd(), "bots");
4533
- const actionsDir = join4(botsDir, botName, "actions");
4554
+ const actionsDir = join4(BOTS_DIR, botName, "actions");
4534
4555
  const defaultsDir = join4(new URL(".", import.meta.url).pathname, "..", "defaults", "actions");
4535
4556
  console.error(`[Kill Switch] Loading actions from ${actionsDir}`);
4536
4557
  if (!existsSync4(actionsDir) && existsSync4(defaultsDir)) {
@@ -4619,20 +4640,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4619
4640
  const parts = [`Already connected as "${activeBotName}".`];
4620
4641
  if (state) {
4621
4642
  parts.push("");
4622
- parts.push("── Current State ──");
4623
4643
  parts.push(formatWorldState(state, existing.sdk.getStateAge()));
4624
4644
  }
4625
- parts.push("");
4626
- parts.push("Your bot is in the game. Talk strategy with your human while waiting for the tournament to start!");
4627
4645
  return { content: [{ type: "text", text: parts.join(`
4628
4646
  `) }] };
4629
4647
  }
4630
4648
  }
4631
- const botsDir = join3(process.cwd(), "bots");
4632
4649
  let username;
4633
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
+ }
4634
4658
  if (botName) {
4635
- const envPath = join3(botsDir, botName, "bot.env");
4659
+ const envPath = join3(BOTS_DIR, botName, "bot.env");
4636
4660
  if (existsSync3(envPath)) {
4637
4661
  console.error(`[Kill Switch] Found existing bot "${botName}"`);
4638
4662
  const env = parseEnv(await readFile2(envPath, "utf-8"));
@@ -4643,7 +4667,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4643
4667
  username = botName;
4644
4668
  password = generateRandomPassword();
4645
4669
  const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
4646
- const botDir = join3(botsDir, botName);
4670
+ const botDir = join3(BOTS_DIR, botName);
4647
4671
  mkdirSync2(botDir, { recursive: true });
4648
4672
  writeFileSync2(join3(botDir, "bot.env"), [
4649
4673
  `BOT_USERNAME=${username}`,
@@ -4654,21 +4678,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4654
4678
  `));
4655
4679
  }
4656
4680
  } else {
4657
- const { readdirSync } = await import("fs");
4658
- if (existsSync3(botsDir)) {
4659
- 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")));
4660
4694
  if (dirs.length > 0) {
4661
4695
  botName = dirs[0];
4662
4696
  console.error(`[Kill Switch] Reusing existing bot "${botName}"`);
4663
- 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"));
4664
4698
  username = env.BOT_USERNAME || botName;
4665
4699
  password = env.PASSWORD || "";
4666
4700
  } else {
4667
4701
  botName = generateRandomName();
4668
4702
  username = botName;
4669
4703
  password = generateRandomPassword();
4670
- const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
4671
- const botDir = join3(botsDir, botName);
4704
+ const botDir = join3(BOTS_DIR, botName);
4672
4705
  mkdirSync2(botDir, { recursive: true });
4673
4706
  writeFileSync2(join3(botDir, "bot.env"), [
4674
4707
  `BOT_USERNAME=${username}`,
@@ -4683,10 +4716,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4683
4716
  botName = generateRandomName();
4684
4717
  username = botName;
4685
4718
  password = generateRandomPassword();
4686
- const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
4687
- const botDir = join3(botsDir, botName);
4688
- mkdirSync2(botDir, { recursive: true });
4689
- writeFileSync2(join3(botDir, "bot.env"), [
4719
+ mkdirSync2(join3(BOTS_DIR, botName), { recursive: true });
4720
+ writeFileSync2(join3(BOTS_DIR, botName, "bot.env"), [
4690
4721
  `BOT_USERNAME=${username}`,
4691
4722
  `PASSWORD=${password}`,
4692
4723
  `SERVER=${SERVER_URL2}`,
@@ -4753,37 +4784,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4753
4784
  activeActionDescriptions = descriptions;
4754
4785
  const state = connection.sdk.getState();
4755
4786
  const parts = [
4756
- `Connected as "${username}"!`,
4757
- "",
4758
- "Your bot is in the game. A browser window should have opened showing the game view.",
4759
- "",
4760
- "You're in the tournament lobby (Barbarian Village) with:",
4761
- " - EMPTY inventory and no equipment",
4762
- " - Combat stats: 50 Attack, 50 Strength, 50 Defence, 99 Hitpoints",
4763
- "",
4764
- 'NEXT STEP: Call join_game with mode "shorty" (or "longy" when available) to queue up.',
4765
- "",
4766
- "Game modes:",
4767
- " - SHORTY: Quick battle royale at Draynor Manor. Loot, fight, last one standing.",
4768
- " - LONGY: (Coming soon) Large survival map with skill progression.",
4769
- "",
4770
- "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}/`
4771
4789
  ];
4772
- parts.push("");
4773
- parts.push("── IMPORTANT: Read the SDK Reference ──");
4774
- parts.push('Before writing any execute_code, read the "Kill Switch SDK Reference" resource (killswitch://sdk-reference).');
4775
- parts.push("It contains the complete API for bot, sdk, and actions. Do NOT guess method names — use only what is documented.");
4776
4790
  if (activeActionDescriptions.length > 0) {
4777
4791
  parts.push("");
4778
- parts.push("── Loaded Actions ──");
4779
- parts.push("Use these in execute_code via the `actions` object:");
4792
+ parts.push("Loaded actions:");
4780
4793
  parts.push(...activeActionDescriptions);
4781
4794
  }
4782
4795
  if (state) {
4783
4796
  parts.push("");
4784
- parts.push("── Current State ──");
4785
4797
  parts.push(formatWorldState(state, connection.sdk.getStateAge()));
4786
4798
  }
4799
+ parts.push("");
4800
+ parts.push('Next: call join_game with mode "shorty" to queue up.');
4787
4801
  return { content: [{ type: "text", text: parts.join(`
4788
4802
  `) }] };
4789
4803
  } finally {
@@ -4907,7 +4921,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4907
4921
  if (isLongCode) {
4908
4922
  parts.push("");
4909
4923
  parts.push("── Tip ──");
4910
- 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}/`);
4911
4925
  }
4912
4926
  const output = parts.length > 0 ? parts.join(`
4913
4927
  `) : "(no output)";
@@ -4940,33 +4954,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4940
4954
  console.error(`[Kill Switch] Tournament started! (detected after ${waitTime}s)`);
4941
4955
  const players = connection.sdk.getNearbyPlayers?.() || [];
4942
4956
  const parts = [
4943
- "TOURNAMENT STARTED!",
4957
+ "GAME STARTED! You are in the arena.",
4944
4958
  "",
4945
- `You've been teleported to Draynor Manor. The fight is ON!`,
4946
- `Position: ${state.player.worldX}, ${state.player.worldZ}`,
4959
+ `Position: (${state.player.worldX}, ${state.player.worldZ})`,
4947
4960
  `HP: ${state.player.hp}/${state.player.maxHp}`,
4961
+ `Nearby players: ${players.length > 0 ? players.map((p) => `${p.name} (dist ${p.distance})`).join(", ") : "scanning..."}`,
4948
4962
  "",
4949
- `Nearby players: ${players.length > 0 ? players.map((p) => `${p.name} (CB ${p.combatLevel}, dist ${p.distance})`).join(", ") : "scanning..."}`,
4950
- "",
4951
- "REMEMBER: You die = your agent dies FOREVER. No second chances.",
4952
- "",
4953
- "IMMEDIATELY use execute_code to start your strategy:",
4954
- "1. Pick up weapons and food from the ground",
4955
- "2. Equip best weapon found, then start fighting",
4956
- "3. Eat when HP gets low — you have 99 HP but no food yet!",
4957
- "",
4958
- "QUICK START: await actions.lootAndEquip({ maxItems: 5 }); await actions.fightLoop({ eatAt: 25, duration: 30000 });",
4959
- "",
4960
- "API REMINDERS:",
4961
- "- sdk.sendWalk(x, z) for quick movement (NOT bot.walkTo for combat)",
4962
- "- sdk.getState().player.worldX / .worldZ / .hp",
4963
- "- Blocking UI is auto-dismissed — do NOT call bot.dismissBlockingUI()"
4963
+ "Act now use execute_code to loot and fight."
4964
4964
  ];
4965
- if (activeActionDescriptions.length > 0) {
4966
- parts.push("");
4967
- parts.push("AVAILABLE ACTIONS:");
4968
- parts.push(...activeActionDescriptions);
4969
- }
4970
4965
  return { content: [{ type: "text", text: parts.join(`
4971
4966
  `) }] };
4972
4967
  }
@@ -4994,22 +4989,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4994
4989
  return errorResponse(data.error);
4995
4990
  }
4996
4991
  const parts = [
4997
- `Joined ${mode.toUpperCase()} queue!`,
4998
- "",
4999
- `Queue position: ${data.queuePosition}`,
4992
+ `Joined ${mode.toUpperCase()} queue.`,
5000
4993
  `Players in queue: ${data.queuePlayers?.join(", ") || "just you"}`,
5001
- ""
4994
+ "",
4995
+ "Next: call wait_for_game_start to wait for the match."
5002
4996
  ];
5003
- if (mode === "shorty") {
5004
- parts.push("SHORTY: Quick battle royale at Draynor Manor.");
5005
- parts.push("Stats: 50 Attack, 50 Strength, 50 Defence, 99 Hitpoints");
5006
- parts.push("You start with NOTHING — grab weapons and food from the ground!");
5007
- } else {
5008
- parts.push("LONGY: Large survival map (coming soon).");
5009
- }
5010
- parts.push("");
5011
- parts.push("Now call wait_for_game_start to wait for the game to begin.");
5012
- parts.push("Chat strategy with your human while you wait!");
5013
4997
  return { content: [{ type: "text", text: parts.join(`
5014
4998
  `) }] };
5015
4999
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kill-switch-mcp",
3
- "version": "1.1.10",
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": {