kill-switch-mcp 1.2.3 → 1.2.5

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 +198 -83
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -150,7 +150,7 @@ class BotSDK {
150
150
  const status = await this.checkBotStatus();
151
151
  const shouldLaunch = this.shouldLaunchBrowser(status);
152
152
  if (shouldLaunch) {
153
- console.log(`[BotSDK] Launching browser...`);
153
+ console.error(`[BotSDK] Launching browser...`);
154
154
  this.browserLaunched = true;
155
155
  await this.launchBrowser();
156
156
  await this.waitForBotConnection();
@@ -212,7 +212,7 @@ class BotSDK {
212
212
  this.browserLaunched = false;
213
213
  }
214
214
  this.waitForReady(15000).then(() => {
215
- console.log("[BotSDK] Connected and game state ready");
215
+ console.error("[BotSDK] Connected and game state ready");
216
216
  resolve();
217
217
  }).catch((error) => {
218
218
  console.warn("[BotSDK] Connected but game state not ready:", error.message);
@@ -246,21 +246,21 @@ class BotSDK {
246
246
  }
247
247
  scheduleReconnect() {
248
248
  if (this.reconnectAttempt >= this.config.reconnectMaxRetries) {
249
- console.log(`[BotSDK] Max reconnection attempts (${this.config.reconnectMaxRetries}) reached, giving up`);
249
+ console.error(`[BotSDK] Max reconnection attempts (${this.config.reconnectMaxRetries}) reached, giving up`);
250
250
  this.setConnectionState("disconnected");
251
251
  return;
252
252
  }
253
253
  this.reconnectAttempt++;
254
254
  this.setConnectionState("reconnecting", this.reconnectAttempt);
255
255
  const delay = Math.min(this.config.reconnectBaseDelay * Math.pow(2, this.reconnectAttempt - 1), this.config.reconnectMaxDelay);
256
- console.log(`[BotSDK] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
256
+ console.error(`[BotSDK] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
257
257
  this.reconnectTimer = setTimeout(async () => {
258
258
  this.reconnectTimer = null;
259
259
  try {
260
260
  await this.connect();
261
- console.log(`[BotSDK] Reconnected successfully after ${this.reconnectAttempt} attempt(s)`);
261
+ console.error(`[BotSDK] Reconnected successfully after ${this.reconnectAttempt} attempt(s)`);
262
262
  } catch (e) {
263
- console.log(`[BotSDK] Reconnection attempt ${this.reconnectAttempt} failed`);
263
+ console.error(`[BotSDK] Reconnection attempt ${this.reconnectAttempt} failed`);
264
264
  }
265
265
  }, delay);
266
266
  }
@@ -304,17 +304,17 @@ class BotSDK {
304
304
  async checkBotStatus() {
305
305
  const statusUrl = this.getStatusUrl();
306
306
  try {
307
- console.log(`[BotSDK] Checking bot status via URL: ${statusUrl}`);
307
+ console.error(`[BotSDK] Checking bot status via URL: ${statusUrl}`);
308
308
  const response = await fetch(statusUrl);
309
309
  if (!response.ok) {
310
- console.log(`[BotSDK] Status check HTTP error: ${response.status} ${response.statusText} (URL: ${statusUrl})`);
310
+ console.error(`[BotSDK] Status check HTTP error: ${response.status} ${response.statusText} (URL: ${statusUrl})`);
311
311
  throw new Error(`Status check failed: ${response.status}`);
312
312
  }
313
313
  const data = await response.json();
314
314
  return data;
315
315
  } catch (error) {
316
316
  const errorMsg = error instanceof Error ? error.message : String(error);
317
- console.log(`[BotSDK] Status check failed: ${errorMsg} (URL: ${statusUrl})`);
317
+ console.error(`[BotSDK] Status check failed: ${errorMsg} (URL: ${statusUrl})`);
318
318
  return {
319
319
  status: "dead",
320
320
  inGame: false,
@@ -334,15 +334,15 @@ class BotSDK {
334
334
  return false;
335
335
  }
336
336
  if (status.status === "dead") {
337
- console.log(`[BotSDK] Bot not connected`);
337
+ console.error(`[BotSDK] Bot not connected`);
338
338
  return true;
339
339
  }
340
- console.log(`[BotSDK] Active client detected, skipping browser launch`);
340
+ console.error(`[BotSDK] Active client detected, skipping browser launch`);
341
341
  return false;
342
342
  }
343
343
  async launchBrowser() {
344
344
  const url = this.buildClientUrl();
345
- console.log(`[BotSDK] Opening browser: ${url}`);
345
+ console.error(`[BotSDK] Opening browser: ${url}`);
346
346
  const { exec } = await import("child_process");
347
347
  const command = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
348
348
  return new Promise((resolve, reject) => {
@@ -360,14 +360,14 @@ class BotSDK {
360
360
  const startTime = Date.now();
361
361
  const pollInterval = 500;
362
362
  let attemptCount = 0;
363
- console.log(`[BotSDK] Waiting for bot to connect and load game (timeout: ${timeoutMs}ms)...`);
363
+ console.error(`[BotSDK] Waiting for bot to connect and load game (timeout: ${timeoutMs}ms)...`);
364
364
  while (Date.now() - startTime < timeoutMs) {
365
365
  attemptCount++;
366
366
  const elapsed = Date.now() - startTime;
367
367
  const status = await this.checkBotStatus();
368
- console.log(`[BotSDK] Poll attempt ${attemptCount} (${elapsed}ms): status="${status.status}", inGame=${status.inGame}, controllers=${status.controllers.length}, observers=${status.observers.length}`);
368
+ console.error(`[BotSDK] Poll attempt ${attemptCount} (${elapsed}ms): status="${status.status}", inGame=${status.inGame}, controllers=${status.controllers.length}, observers=${status.observers.length}`);
369
369
  if (status.status !== "dead" && status.inGame) {
370
- console.log(`[BotSDK] Bot connected and in-game!`);
370
+ console.error(`[BotSDK] Bot connected and in-game!`);
371
371
  return;
372
372
  }
373
373
  await new Promise((resolve) => setTimeout(resolve, pollInterval));
@@ -402,7 +402,7 @@ class BotSDK {
402
402
  username: this.config.botUsername,
403
403
  password: this.config.password
404
404
  }));
405
- console.log(`[BotSDK] Sent login command for ${this.config.botUsername} through gateway`);
405
+ console.error(`[BotSDK] Sent login command for ${this.config.botUsername} through gateway`);
406
406
  }
407
407
  async waitForConnection(timeout = 60000) {
408
408
  if (this.isConnected()) {
@@ -573,7 +573,7 @@ class BotSDK {
573
573
  }
574
574
  async sendAction(action) {
575
575
  if (this.connectionState === "reconnecting") {
576
- console.log(`[BotSDK] Waiting for reconnection before sending action: ${action.type}`);
576
+ console.error(`[BotSDK] Waiting for reconnection before sending action: ${action.type}`);
577
577
  await this.waitForConnection();
578
578
  }
579
579
  if (!this.isConnected()) {
@@ -723,7 +723,7 @@ class BotSDK {
723
723
  }
724
724
  async sendScreenshot(timeout = 1e4) {
725
725
  if (this.connectionState === "reconnecting") {
726
- console.log(`[BotSDK] Waiting for reconnection before requesting screenshot`);
726
+ console.error(`[BotSDK] Waiting for reconnection before requesting screenshot`);
727
727
  await this.waitForConnection();
728
728
  }
729
729
  if (!this.isConnected()) {
@@ -765,22 +765,22 @@ class BotSDK {
765
765
  return this.findPath(destX, destZ, maxWaypoints);
766
766
  }
767
767
  async waitForReady(timeout = 15000) {
768
- console.log("[BotSDK] Waiting for game state to be ready...");
768
+ console.error("[BotSDK] Waiting for game state to be ready...");
769
769
  try {
770
770
  const state = await this.waitForCondition((s) => {
771
771
  const validPosition = !!(s.player && s.player.worldX !== 0 && s.player.worldZ !== 0);
772
772
  const inGame = s.inGame;
773
773
  const hasEntities = s.nearbyNpcs.length > 0 || s.nearbyLocs.length > 0 || s.groundItems.length > 0;
774
774
  if (!validPosition) {
775
- console.log(`[BotSDK] Waiting - invalid position: (${s.player?.worldX}, ${s.player?.worldZ})`);
775
+ console.error(`[BotSDK] Waiting - invalid position: (${s.player?.worldX}, ${s.player?.worldZ})`);
776
776
  } else if (!inGame) {
777
- console.log("[BotSDK] Waiting - not in game");
777
+ console.error("[BotSDK] Waiting - not in game");
778
778
  } else if (!hasEntities) {
779
- console.log("[BotSDK] Waiting - no entities loaded yet");
779
+ console.error("[BotSDK] Waiting - no entities loaded yet");
780
780
  }
781
781
  return inGame && validPosition && hasEntities;
782
782
  }, timeout);
783
- console.log("[BotSDK] Game state ready!");
783
+ console.error("[BotSDK] Game state ready!");
784
784
  return state;
785
785
  } catch (error) {
786
786
  console.error("[BotSDK] Timeout waiting for game state to be ready");
@@ -1721,7 +1721,7 @@ class BotActions {
1721
1721
  if (!blockedDoors.has(key)) {
1722
1722
  blockedDoors.add(key);
1723
1723
  blockDoor(level, nearest.x, nearest.z);
1724
- console.log(`[walkTo] Blocked impassable door at (${nearest.x}, ${nearest.z}) — re-routing`);
1724
+ console.error(`[walkTo] Blocked impassable door at (${nearest.x}, ${nearest.z}) — re-routing`);
1725
1725
  }
1726
1726
  }
1727
1727
  return false;
@@ -1732,6 +1732,9 @@ class BotActions {
1732
1732
  await this.sdk.waitForTicks(1);
1733
1733
  path = await this.sdk.sendFindPath(x, z, 500);
1734
1734
  if (!path.success || !path.waypoints?.length) {
1735
+ if (path.error?.includes("zone not allocated")) {
1736
+ return this._walkToServerSide(x, z, tolerance);
1737
+ }
1735
1738
  console.error(`[walkTo] PATHFINDING FAILED: ${path.error ?? "no waypoints"} - from (${pos.x}, ${pos.z}) to (${x}, ${z})`);
1736
1739
  return { success: false, message: `No path to (${x}, ${z}) from (${pos.x}, ${pos.z}): ${path.error ?? "no waypoints"}` };
1737
1740
  }
@@ -1761,7 +1764,7 @@ class BotActions {
1761
1764
  blockedDoors.add(doorKey);
1762
1765
  blockDoor(door.level, door.x, door.z);
1763
1766
  requiredDoorKeys.delete(doorKey);
1764
- console.log(`[walkTo] Blocked impassable door at (${door.x}, ${door.z}) — re-routing`);
1767
+ console.error(`[walkTo] Blocked impassable door at (${door.x}, ${door.z}) — re-routing`);
1765
1768
  break;
1766
1769
  }
1767
1770
  break;
@@ -1795,6 +1798,45 @@ class BotActions {
1795
1798
  }
1796
1799
  return { success: false, message: `Could not reach (${x}, ${z}) - stopped at (${pos.x}, ${pos.z})` };
1797
1800
  }
1801
+ async _walkToServerSide(x, z, tolerance) {
1802
+ const distTo = (px, pz) => this.helpers.distance(px, pz, x, z);
1803
+ const MAX_ATTEMPTS = 20;
1804
+ let stuckCount = 0;
1805
+ for (let i = 0;i < MAX_ATTEMPTS; i++) {
1806
+ const state = this.sdk.getState();
1807
+ if (!state?.player)
1808
+ return { success: false, message: "No player state" };
1809
+ const { worldX: px, worldZ: pz } = state.player;
1810
+ if (distTo(px, pz) <= tolerance) {
1811
+ return { success: true, message: "Arrived" };
1812
+ }
1813
+ await this.sdk.sendWalk(x, z);
1814
+ const prevX = px;
1815
+ const prevZ = pz;
1816
+ await this.sdk.waitForTicks(3);
1817
+ const after = this.sdk.getState();
1818
+ if (after?.player) {
1819
+ const movedDist = this.helpers.distance(prevX, prevZ, after.player.worldX, after.player.worldZ);
1820
+ if (movedDist < 1) {
1821
+ stuckCount++;
1822
+ if (stuckCount >= 3) {
1823
+ return { success: false, message: `Stuck at (${after.player.worldX}, ${after.player.worldZ}) trying to reach (${x}, ${z})` };
1824
+ }
1825
+ await this.helpers.tryOpenBlockingDoor();
1826
+ await this.sdk.waitForTicks(1);
1827
+ } else {
1828
+ stuckCount = 0;
1829
+ }
1830
+ }
1831
+ }
1832
+ const final = this.sdk.getState();
1833
+ const fx = final?.player?.worldX ?? 0;
1834
+ const fz = final?.player?.worldZ ?? 0;
1835
+ if (distTo(fx, fz) <= tolerance) {
1836
+ return { success: true, message: "Arrived" };
1837
+ }
1838
+ return { success: false, message: `Could not reach (${x}, ${z}) - stopped at (${fx}, ${fz})` };
1839
+ }
1798
1840
  async closeShop(timeout = 5000) {
1799
1841
  const state = this.sdk.getState();
1800
1842
  if (!state?.shop.isOpen && !state?.interface?.isOpen) {
@@ -3828,12 +3870,42 @@ var tempoChain = defineChain({
3828
3870
  });
3829
3871
  var USDC_ADDRESS = process.env.USDC_ADDRESS || "0x20c0000000000000000000000000000000000000";
3830
3872
  var FACTORY_ADDRESS = process.env.FACTORY_ADDRESS;
3873
+ var PENDING_FILE = join2(WALLET_DIR, "pending-wallet.json");
3831
3874
  var SERVER_URL = (() => {
3832
3875
  const idx = process.argv.indexOf("--server");
3833
3876
  if (idx !== -1 && process.argv[idx + 1])
3834
3877
  return process.argv[idx + 1];
3835
3878
  return process.env.KILL_SWITCH_SERVER || "localhost";
3836
3879
  })();
3880
+ function getWebBase() {
3881
+ const isLocal = SERVER_URL === "localhost" || SERVER_URL === "127.0.0.1";
3882
+ return isLocal ? "http://localhost:8888" : `https://${SERVER_URL}`;
3883
+ }
3884
+ function generateLinkToken() {
3885
+ const bytes = new Uint8Array(16);
3886
+ crypto.getRandomValues(bytes);
3887
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
3888
+ }
3889
+ function savePendingWallet(pending) {
3890
+ mkdirSync(WALLET_DIR, { recursive: true });
3891
+ writeFileSync(PENDING_FILE, JSON.stringify(pending, null, 2), { mode: 384 });
3892
+ }
3893
+ function loadPendingWallet() {
3894
+ try {
3895
+ if (existsSync2(PENDING_FILE)) {
3896
+ return JSON.parse(readFileSync(PENDING_FILE, "utf-8"));
3897
+ }
3898
+ } catch {}
3899
+ return null;
3900
+ }
3901
+ function clearPendingWallet() {
3902
+ try {
3903
+ if (existsSync2(PENDING_FILE)) {
3904
+ const fs = __require("fs");
3905
+ fs.unlinkSync(PENDING_FILE);
3906
+ }
3907
+ } catch {}
3908
+ }
3837
3909
  var ERC20_ABI = [
3838
3910
  {
3839
3911
  name: "approve",
@@ -3961,40 +4033,48 @@ async function getWalletInfo() {
3961
4033
  balanceRaw
3962
4034
  };
3963
4035
  }
3964
- function generateAccessKey() {
4036
+ function initiateWalletSetup() {
3965
4037
  const privateKey = generatePrivateKey();
3966
4038
  const account = privateKeyToAccount(privateKey);
3967
- const instructions = [
3968
- "A new game access key has been generated.",
3969
- "",
3970
- `Access Key Address: ${account.address}`,
3971
- "",
3972
- "To authorize this key on your Tempo account, you need to call the",
3973
- "Account Keychain precompile. This requires your passkey (biometric).",
3974
- "",
3975
- "You can do this via the Tempo wallet web interface or by running:",
3976
- "",
3977
- ` cast send 0xAAAAAAAA00000000000000000000000000000000 \\`,
3978
- ` "authorizeKey(address,uint8,uint64,bool,(address,uint256)[])" \\`,
3979
- ` ${account.address} 0 ${Math.floor(Date.now() / 1000) + 90 * 86400} true \\`,
3980
- ` "[(${USDC_ADDRESS},1000000000)]" \\`,
3981
- ` --rpc-url ${TEMPO_RPC}`,
3982
- "",
3983
- "This authorizes the key to spend up to 1000 USDC on game transactions,",
3984
- "expiring in 90 days. You can revoke it anytime from your Tempo account.",
3985
- "",
3986
- "Learn more about Tempo access keys:",
3987
- "https://docs.tempo.xyz/protocol/tips/tip-1011"
3988
- ].join(`
3989
- `);
3990
- return { accessKeyAddress: account.address, privateKey, instructions };
4039
+ const linkToken = generateLinkToken();
4040
+ const chain = TEMPO_CHAIN_ID === 4217 ? "mainnet" : "testnet";
4041
+ const webBase = getWebBase();
4042
+ const authorizeUrl = `${webBase}/authorize?key=${account.address}&t=${linkToken}&chain=${chain}`;
4043
+ const pending = {
4044
+ accessKeyPrivate: privateKey,
4045
+ accessKeyAddress: account.address,
4046
+ linkToken,
4047
+ authorizeUrl,
4048
+ createdAt: new Date().toISOString()
4049
+ };
4050
+ savePendingWallet(pending);
4051
+ return {
4052
+ accessKeyAddress: account.address,
4053
+ linkToken,
4054
+ authorizeUrl
4055
+ };
4056
+ }
4057
+ async function checkWalletCallback(linkToken) {
4058
+ try {
4059
+ const webBase = getWebBase();
4060
+ const response = await fetch(`${webBase}/api/wallet-callback?t=${linkToken}`);
4061
+ const data = await response.json();
4062
+ if (data.status === "complete" && data.tempoAddress) {
4063
+ return { tempoAddress: data.tempoAddress, txHash: data.txHash };
4064
+ }
4065
+ } catch {}
4066
+ return null;
3991
4067
  }
3992
- function saveAccessKey(privateKey, accountAddress) {
4068
+ function finalizeWalletSetup(tempoAddress) {
4069
+ const pending = loadPendingWallet();
4070
+ if (!pending)
4071
+ throw new Error("No pending wallet setup found.");
3993
4072
  saveWallet({
3994
- accessKeyPrivate: privateKey,
3995
- accountAddress,
4073
+ accessKeyPrivate: pending.accessKeyPrivate,
4074
+ accountAddress: tempoAddress,
3996
4075
  createdAt: new Date().toISOString()
3997
4076
  });
4077
+ clearPendingWallet();
3998
4078
  }
3999
4079
  async function joinTournament(tournamentAddress, username) {
4000
4080
  const wallet = loadWallet();
@@ -4087,7 +4167,7 @@ Call \`join_game\` with no arguments to see what's available:
4087
4167
  - **If no game is queued**: You'll see all available buy-in tiers (Free, $5, $15, $50, $100, $1000). Call \`join_game\` with a \`tier_id\` to start a new game.
4088
4168
  - **If a game is active**: You must wait for it to finish.
4089
4169
 
4090
- **Paid games** require a Tempo wallet (\`setup_game_wallet\`). Your buy-in is deposited on-chain when you join. No refunds once deposited — the only refund path is if the game is cancelled (not enough players).
4170
+ **Paid games** require a game wallet. Run \`setup_game_wallet\` and open the link in your browser — create a Tempo account with Face ID/Touch ID (no wallet app needed), authorize Kill Switch with one tap, and you're ready. Testnet accounts are auto-funded. Your buy-in is deposited on-chain when you join. No refunds once deposited — the only refund path is if the game is cancelled (not enough players).
4091
4171
 
4092
4172
  **Game start triggers:**
4093
4173
  - 12 players join → starts immediately
@@ -4498,16 +4578,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4498
4578
  },
4499
4579
  {
4500
4580
  name: "setup_game_wallet",
4501
- 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.',
4581
+ description: "Set up a game wallet for paid tournaments. Opens a browser page where the user creates a Tempo account with Face ID/Touch ID and authorizes Kill Switch. No wallet app needed.",
4502
4582
  inputSchema: {
4503
4583
  type: "object",
4504
- properties: {
4505
- tempo_account_address: {
4506
- type: "string",
4507
- description: `The user's Tempo account address (0x...). Get via "tempo wallet whoami".`
4508
- }
4509
- },
4510
- required: ["tempo_account_address"]
4584
+ properties: {}
4511
4585
  }
4512
4586
  },
4513
4587
  {
@@ -5042,10 +5116,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
5042
5116
  return successResponse({ message: `Disconnected "${name2}" from the game.` });
5043
5117
  }
5044
5118
  case "setup_game_wallet": {
5045
- const tempoAddress = args?.tempo_account_address;
5046
- if (!tempoAddress || !tempoAddress.startsWith("0x")) {
5047
- return errorResponse('A valid Tempo account address (0x...) is required. Run "tempo wallet whoami" to get yours.');
5048
- }
5049
5119
  if (hasGameWallet()) {
5050
5120
  try {
5051
5121
  const info = await getWalletInfo();
@@ -5065,28 +5135,62 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
5065
5135
  };
5066
5136
  } catch {}
5067
5137
  }
5068
- const { accessKeyAddress, privateKey, instructions } = generateAccessKey();
5069
- saveAccessKey(privateKey, tempoAddress);
5138
+ const pending = loadPendingWallet();
5139
+ if (pending) {
5140
+ const result = await checkWalletCallback(pending.linkToken);
5141
+ if (result) {
5142
+ finalizeWalletSetup(result.tempoAddress);
5143
+ return {
5144
+ content: [{
5145
+ type: "text",
5146
+ text: [
5147
+ "Wallet setup complete!",
5148
+ "",
5149
+ `Tempo account: ${result.tempoAddress}`,
5150
+ `Access key authorized: tx ${result.txHash.slice(0, 10)}...`,
5151
+ "",
5152
+ "Your wallet is ready. Use check_wallet to see your balance."
5153
+ ].join(`
5154
+ `)
5155
+ }]
5156
+ };
5157
+ }
5158
+ return {
5159
+ content: [{
5160
+ type: "text",
5161
+ text: [
5162
+ "Wallet setup is in progress. Open this link to complete it:",
5163
+ "",
5164
+ ` ${pending.authorizeUrl}`,
5165
+ "",
5166
+ "Once you've completed the steps in your browser, run setup_game_wallet again."
5167
+ ].join(`
5168
+ `)
5169
+ }]
5170
+ };
5171
+ }
5172
+ const { accessKeyAddress, authorizeUrl } = initiateWalletSetup();
5070
5173
  return {
5071
5174
  content: [{
5072
5175
  type: "text",
5073
5176
  text: [
5074
5177
  "Game wallet setup started!",
5075
5178
  "",
5076
- `Your Tempo account: ${tempoAddress}`,
5077
- `Generated access key: ${accessKeyAddress}`,
5179
+ `Access key: ${accessKeyAddress}`,
5180
+ "",
5181
+ "━━━ OPEN THIS LINK ━━━",
5078
5182
  "",
5079
- "── NEXT STEP ──",
5183
+ ` ${authorizeUrl}`,
5080
5184
  "",
5081
- "You need to authorize this access key on your Tempo account.",
5082
- "This requires your passkey (biometric confirmation).",
5185
+ "This will open a page where you:",
5186
+ "1. Create a Tempo account using Face ID / Touch ID (or sign in)",
5187
+ "2. Authorize Kill Switch to manage game deposits (one tap)",
5083
5188
  "",
5084
- instructions,
5189
+ "No wallet app needed — just your browser and biometrics.",
5085
5190
  "",
5086
- "── AFTER AUTHORIZATION ──",
5191
+ "━━━ AFTER YOU'RE DONE ━━━",
5087
5192
  "",
5088
- "Once authorized, your game wallet is ready. Check it with check_wallet.",
5089
- "Fund your Tempo account with USDC to start joining paid tournaments."
5193
+ "Come back here and run setup_game_wallet again to confirm."
5090
5194
  ].join(`
5091
5195
  `)
5092
5196
  }]
@@ -5094,13 +5198,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
5094
5198
  }
5095
5199
  case "check_wallet": {
5096
5200
  if (!hasGameWallet()) {
5097
- return errorResponse(`No game wallet found. Run setup_game_wallet first.
5201
+ const pendingCheck = loadPendingWallet();
5202
+ if (pendingCheck) {
5203
+ const callbackResult = await checkWalletCallback(pendingCheck.linkToken);
5204
+ if (callbackResult) {
5205
+ finalizeWalletSetup(callbackResult.tempoAddress);
5206
+ } else {
5207
+ return errorResponse(`Wallet setup is in progress. Complete it in your browser:
5208
+
5209
+ ${pendingCheck.authorizeUrl}
5098
5210
 
5099
- Prerequisites:
5100
- 1. Install Tempo CLI: curl -fsSL https://tempo.xyz/install | bash
5101
- 2. Create account: tempo wallet login
5102
- 3. Get your address: tempo wallet whoami
5103
- 4. Then call setup_game_wallet with your address`);
5211
+ Then run check_wallet again.`);
5212
+ }
5213
+ } else {
5214
+ return errorResponse("No game wallet found. Run setup_game_wallet to get started.");
5215
+ }
5104
5216
  }
5105
5217
  try {
5106
5218
  const info = await getWalletInfo();
@@ -5147,6 +5259,9 @@ function errorResponse(message) {
5147
5259
  isError: true
5148
5260
  };
5149
5261
  }
5262
+ process.on("unhandledRejection", (reason) => {
5263
+ console.error("[Kill Switch MCP] Unhandled rejection (suppressed):", reason instanceof Error ? reason.message : reason);
5264
+ });
5150
5265
  async function main() {
5151
5266
  console.error("[Kill Switch MCP] Starting Kill Switch MCP server v3.0...");
5152
5267
  console.error(`[Kill Switch MCP] Game server: ${SERVER_URL2}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kill-switch-mcp",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Kill Switch MCP Server — AI battle royale powered by Claude Code",
5
5
  "type": "module",
6
6
  "bin": {