xakto-mcp 1.1.0 → 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.
package/README.md CHANGED
@@ -20,32 +20,34 @@ to it (a service worker cannot listen for anything).
20
20
 
21
21
  ## Setting it up
22
22
 
23
- 1. Open Xakto's menu (its mark at the top of the layers) > **Agent** >
24
- **Connect your agent**, or **Settings** > **MCP server**,
25
- and turn it on. Settings is a modal over the page, but the page cannot read it:
26
- it is drawn in a frame of Xakto's own, since it holds the pairing
27
- token.
28
- 2. Run the line Settings shows, in a terminal:
23
+ Xakto calls this **Coding agent**: its menu row, its panel and its section in
24
+ Settings. This package is the MCP server behind it.
25
+
26
+ 1. Open Xakto's menu (its mark at the top of the layers) > **Coding agent** and
27
+ press **Connect a coding agent**, or turn on **Coding agent** in **Settings**.
28
+ The panel sits over the page, but the page cannot read it: it is drawn in a
29
+ frame of Xakto's own, since it holds the pairing key.
30
+ 2. **Run this in a terminal**, as the panel's first step says:
29
31
 
30
32
  ```bash
31
33
  npx -y xakto-mcp setup
32
34
  ```
33
35
 
34
- It asks which agent, and for the token (**Copy token** in Settings), then
36
+ It asks which agent, and for the pairing key (the panel's **Copy key**), then
35
37
  adds Xakto to that agent's own settings, keeping everything else there. A
36
38
  file it changes keeps a copy beside it, ending `.xakto-backup`; a settings
37
39
  file with comments is left alone, and the block to paste is printed
38
- instead. Name the agent to skip the first question, and pass the token to
39
- skip the second:
40
+ instead. Name the agent to skip the first question, and pass the key to
41
+ skip the second (`--token` works as well as `--key`):
40
42
 
41
43
  ```bash
42
- npx -y xakto-mcp setup cursor --token <token from Settings>
44
+ npx -y xakto-mcp setup cursor --key <pairing key>
43
45
  ```
44
46
 
45
47
  The agents it knows are `claude`, `codex`, `cursor`, `vscode`, `windsurf`
46
48
  and `gemini`. `--port` is needed only when Settings names a port.
47
49
 
48
- **By hand** (Settings › **Set it up by hand**): the relay is the same for
50
+ **By hand** (the panel's **Other agents, or set up by hand**): the relay is the same for
49
51
  every agent; only where the setup goes differs:
50
52
 
51
53
  | Agent | Where the setup goes |
@@ -61,7 +63,7 @@ to it (a service worker cannot listen for anything).
61
63
  For Claude Code it is:
62
64
 
63
65
  ```bash
64
- claude mcp add xakto --scope user --env XAKTO_TOKEN=<token from Settings> -- npx -y xakto-mcp
66
+ claude mcp add xakto --scope user --env XAKTO_TOKEN=<pairing key> -- npx -y xakto-mcp
65
67
  ```
66
68
 
67
69
  For Codex:
@@ -70,7 +72,7 @@ to it (a service worker cannot listen for anything).
70
72
  [mcp_servers.xakto]
71
73
  command = "npx"
72
74
  args = ["-y", "xakto-mcp"]
73
- env = { XAKTO_TOKEN = "<token from Settings>" }
75
+ env = { XAKTO_TOKEN = "<pairing key>" }
74
76
  ```
75
77
 
76
78
  And the `mcpServers` block, which VS Code reads under `servers` instead:
@@ -81,13 +83,15 @@ to it (a service worker cannot listen for anything).
81
83
  "xakto": {
82
84
  "command": "npx",
83
85
  "args": ["-y", "xakto-mcp"],
84
- "env": { "XAKTO_TOKEN": "<token from Settings>" }
86
+ "env": { "XAKTO_TOKEN": "<pairing key>" }
85
87
  }
86
88
  }
87
89
  }
88
90
  ```
89
- 3. Start a new agent session. The pill beside **MCP server** says
90
- **Connected** once the relay is running and the two have paired.
91
+ 3. Start a new agent session. The pill beside **Coding agent** says
92
+ **Connected** once the relay is running and the two have paired, and the
93
+ panel and the menu row name the agent: since 1.2.0 the relay passes on the
94
+ name the agent gives itself when it connects (MCP's `clientInfo`).
91
95
 
92
96
  Xakto answers about one page: the tab you switched it on in. Switch it on in
93
97
  another tab and that is the page the agent sees.
@@ -151,23 +155,23 @@ for a relay that says so.
151
155
  - It accepts a connection only from a `chrome-extension://` origin. Set
152
156
  `XAKTO_EXTENSION_ID` to accept only Xakto.
153
157
  - Before anything else is said, the relay and the extension each prove that
154
- they hold the same pairing token, by answering the other's fresh challenge
155
- with an HMAC-SHA256 of it. The token itself never goes over the socket, so
156
- whatever answers on the port first learns nothing from it.
157
- - The token is kept in the extension's own storage, which is closed to content
158
- scripts, and is shown only in Xakto's Settings, a frame of the extension's
159
- own that the page it sits over cannot read. The page can load a copy of that
160
- frame, but the token goes only to the frame Xakto opened and vouched for
161
- with a one-time code. **New token** makes the old one useless; run the
162
- setup again after it.
158
+ they hold the same pairing key (`XAKTO_TOKEN`), by answering the other's
159
+ fresh challenge with an HMAC-SHA256 of it. The key itself never goes over the
160
+ socket, so whatever answers on the port first learns nothing from it.
161
+ - The key is kept in the extension's own storage, which is closed to content
162
+ scripts, and is shown only in Xakto's Coding agent panel, a frame of the
163
+ extension's own that the page it sits over cannot read. The page can load a
164
+ copy of that frame, but the key goes only to the frame Xakto opened and
165
+ vouched for with a one-time code. **New pairing key** makes the old one
166
+ useless; run the setup again after it.
163
167
  - No dependencies: the parts of WebSocket and MCP it needs are in its own files.
164
168
 
165
169
  ## Settings
166
170
 
167
171
  | Variable | Default | |
168
172
  | --- | --- | --- |
169
- | `XAKTO_TOKEN` | none | The pairing token from Xakto's Settings. Without it the relay pairs with nothing. |
170
- | `XAKTO_PORT` | `47821` | The port to listen on. It has to be the one Xakto connects to, which the setup in Settings names when it is not the default. |
173
+ | `XAKTO_TOKEN` | none | The pairing key, from Xakto's Coding agent panel (**Copy key**). Without it the relay pairs with nothing. |
174
+ | `XAKTO_PORT` | `47821` | The port to listen on. It has to be the one Xakto connects to, which the setup line names when it is not the default. |
171
175
  | `XAKTO_EXTENSION_ID` | none | Accept only this extension. |
172
176
  | `XAKTO_CLAUDE_BIN` | found on `PATH` | Where Claude Code is, for the chat, when it is not on the `PATH` or in `~/.local/bin`. |
173
177
 
@@ -178,7 +182,7 @@ file itself: `node /path/to/Xakto/bridge/xakto-mcp.mjs` in place of
178
182
  `npx -y xakto-mcp`. In Claude Code:
179
183
 
180
184
  ```bash
181
- claude mcp add xakto --scope user --env XAKTO_TOKEN=<token from Settings> -- node /path/to/Xakto/bridge/xakto-mcp.mjs
185
+ claude mcp add xakto --scope user --env XAKTO_TOKEN=<pairing key> -- node /path/to/Xakto/bridge/xakto-mcp.mjs
182
186
  ```
183
187
 
184
188
  In an `mcpServers` block, `"command": "node"` and
@@ -189,11 +193,11 @@ stdout is the protocol.
189
193
 
190
194
  ## When it does not connect
191
195
 
192
- - **The agent hears "Xakto is not connected".** The MCP server switch is off,
193
- Chrome is not running, or the relay was started with another token.
194
- - **Settings says "Token refused".** The relay holds a different token from
195
- this browser's. Run `npx -y xakto-mcp setup` again with the token Settings
196
- shows, and restart the agent session.
196
+ - **The agent hears "Xakto is not connected".** Coding agent is switched off,
197
+ Chrome is not running, or the relay was started with another pairing key.
198
+ - **Coding agent says "Old key".** The relay holds a different pairing key from
199
+ this browser's. Run `npx -y xakto-mcp setup` again with the key the panel
200
+ gives (**Copy key**), and restart the agent session.
197
201
  - **The port is taken.** Only one relay can listen on it, and Xakto pairs
198
202
  with one relay at a time. A second agent session's relay tells its agent the
199
203
  port is held, and takes it over as soon as it is free: close the other
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xakto-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "The relay between a coding agent and the Xakto Chrome extension: an MCP server on stdio, a paired WebSocket on 127.0.0.1.",
5
5
  "homepage": "https://xakto.app",
6
6
  "type": "module",
package/xakto-mcp.mjs CHANGED
@@ -28,7 +28,7 @@ import { createInterface } from "node:readline"
28
28
  import { fileURLToPath } from "node:url"
29
29
  import { createChat } from "./xakto-chat.mjs"
30
30
 
31
- const VERSION = "1.1.0"
31
+ const VERSION = "1.2.0"
32
32
  export const DEFAULT_PORT = 47821
33
33
  const PORT = Number(process.env.XAKTO_PORT || DEFAULT_PORT)
34
34
  const TOKEN = (process.env.XAKTO_TOKEN ?? "").trim()
@@ -162,6 +162,8 @@ export class FrameReader {
162
162
  /* ---------- the extension's end ---------- */
163
163
 
164
164
  let extension = null
165
+ /** The agent that started this relay, as it named itself in MCP's initialize (clientInfo), for Xakto to show (1.2.0). */
166
+ let agentClient = ""
165
167
  let nextRequestId = 1
166
168
  const pending = new Map()
167
169
 
@@ -192,6 +194,7 @@ function onExtensionMessage(peer, message) {
192
194
  if (extension && extension !== peer) closePeer(extension, 4000, "replaced by a newer connection")
193
195
  extension = peer
194
196
  log("Xakto connected")
197
+ if (agentClient) sendTo(peer, { type: "client", name: agentClient })
195
198
  // Whether Claude Code is there and signed in is said only when Xakto asks (5 Nov), which it does only in a build
196
199
  // with the chat: a build without it never has the relay run Claude Code at all.
197
200
  return
@@ -351,8 +354,8 @@ function listen() {
351
354
  /* ---------- the agent's end: MCP over stdio ---------- */
352
355
 
353
356
  const NOT_CONNECTED = TOKEN
354
- ? "Xakto is not connected. In Chrome, open Xakto's menu > Agent > Connect your agent, or Settings, turn on MCP server, and make sure the token there is the one this server was started with."
355
- : "This server was started without XAKTO_TOKEN. Copy the setup for your agent from Xakto's menu > Settings, under MCP server; it carries the token."
357
+ ? "Xakto is not connected. In Chrome, open Xakto's menu > Coding agent and turn it on. If it says Old key, run npx -y xakto-mcp setup again with the pairing key there."
358
+ : "This server was started without a pairing key. Run npx -y xakto-mcp setup in a terminal, with the key from Xakto's menu > Coding agent > Copy key."
356
359
 
357
360
  const PORT_TAKEN = `Xakto is not connected: 127.0.0.1:${PORT} is held by something else - most likely another agent session's Xakto server, which Xakto talks to instead. This server takes the port over as soon as it is free; close that session, or ask there.`
358
361
 
@@ -615,6 +618,9 @@ async function answerAgent(message) {
615
618
 
616
619
  if (method === "initialize") {
617
620
  const asked = params?.protocolVersion
621
+ // Who started this relay - "claude-code", "cursor-vscode" and so on - passed on so Xakto can say what it is connected to.
622
+ agentClient = String(params?.clientInfo?.name ?? "").replace(/[^\p{L}\p{N} ._-]/gu, "").slice(0, 64)
623
+ if (agentClient && extension) sendTo(extension, { type: "client", name: agentClient })
618
624
  return {
619
625
  jsonrpc: "2.0",
620
626
  id,
@@ -734,7 +740,7 @@ if (isTheScript() && process.argv[2] === "setup") {
734
740
  const { runSetup } = await import("./xakto-setup.mjs")
735
741
  process.exitCode = await runSetup(process.argv.slice(3))
736
742
  } else if (isTheScript()) {
737
- if (!TOKEN) log("XAKTO_TOKEN is not set; Xakto will refuse to pair. Run `npx -y xakto-mcp setup`, or copy the setup from Xakto's Settings.")
743
+ if (!TOKEN) log("XAKTO_TOKEN (the pairing key) is not set; Xakto will refuse to pair. Run `npx -y xakto-mcp setup`, with the key from Xakto's menu > Coding agent.")
738
744
  process.on("exit", () => chat.stopAll())
739
745
  listen()
740
746
  serveAgent()
package/xakto-setup.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * `npx -y xakto-mcp setup` (8 Nov 2026): adds Xakto to a coding agent, so nobody has to find that agent's settings
3
- * file and paste a block into it. One command for every agent; it asks which, and for the token from Xakto's
4
- * Settings, and writes what Settings' "Set it up by hand" shows - `npx -y xakto-mcp` with the token - into that
3
+ * file and paste a block into it. One command for every agent; it asks which, and for the pairing key from Xakto's
4
+ * menu › Coding agent (Copy key), and writes what "Other agents, or set up by hand" shows - `npx -y xakto-mcp` with the token - into that
5
5
  * agent's own settings, keeping everything else there.
6
6
  *
7
- * npx -y xakto-mcp setup asks which agent, then the token
8
- * npx -y xakto-mcp setup cursor that agent; asks the token
9
- * npx -y xakto-mcp setup cursor --token T nothing to answer
10
- * ... --port 47900 only when Xakto's Settings names a port
7
+ * npx -y xakto-mcp setup asks which agent, then the pairing key
8
+ * npx -y xakto-mcp setup cursor that agent; asks the pairing key
9
+ * npx -y xakto-mcp setup cursor --key K nothing to answer (--token is the same)
10
+ * ... --port 47900 only when Xakto names a port
11
11
  *
12
12
  * It only writes files on this computer (and, for Claude Code, runs `claude mcp add`). A settings file it cannot read
13
13
  * as plain JSON - VS Code allows comments in its own - is left as it is, and the block is printed to paste by hand.
@@ -192,14 +192,15 @@ export function addToAgent(agent, token, port, { home = homedir(), env = process
192
192
  return { file, replaced: merged.replaced, next: NEXT_STEP[agent.id] }
193
193
  }
194
194
 
195
- /** --token and --port, wherever they are; the first other word is the agent. */
195
+ /** --key (or --token) and --port, wherever they are; the first other word is the agent. */
196
196
  export function parseArgs(argv) {
197
197
  const out = { agent: null, token: null, port: null, help: false }
198
198
  for (let i = 0; i < argv.length; i += 1) {
199
199
  const arg = argv[i]
200
200
  if (arg === "--help" || arg === "-h") out.help = true
201
- else if (arg === "--token") out.token = argv[(i += 1)] ?? ""
201
+ else if (arg === "--token" || arg === "--key") out.token = argv[(i += 1)] ?? ""
202
202
  else if (arg.startsWith("--token=")) out.token = arg.slice(8)
203
+ else if (arg.startsWith("--key=")) out.token = arg.slice(6)
203
204
  else if (arg === "--port") out.port = argv[(i += 1)] ?? ""
204
205
  else if (arg.startsWith("--port=")) out.port = arg.slice(7)
205
206
  else if (!out.agent) out.agent = arg
@@ -209,10 +210,10 @@ export function parseArgs(argv) {
209
210
 
210
211
  const HELP = `Adds Xakto to your coding agent.
211
212
 
212
- npx -y xakto-mcp setup asks which agent, then your token
213
+ npx -y xakto-mcp setup asks which agent, then your pairing key
213
214
  npx -y xakto-mcp setup <agent> ${AGENTS.map((agent) => agent.aliases[0]).join(", ")}
214
- npx -y xakto-mcp setup <agent> --token <token from Xakto's Settings>
215
- --port <port> only if Xakto's Settings names one
215
+ npx -y xakto-mcp setup <agent> --key <pairing key from Xakto's menu › Coding agent › Copy key>
216
+ --port <port> only if Xakto names one
216
217
  `
217
218
 
218
219
  /** A question; with `hidden`, what is typed shows as dots. */
@@ -253,7 +254,7 @@ export async function runSetup(argv, { input = process.stdin, output = process.s
253
254
  let token = (args.token ?? env.XAKTO_TOKEN ?? "").trim().toLowerCase()
254
255
  const interactive = Boolean(input.isTTY)
255
256
  if ((!agent || !token) && !interactive) {
256
- error.write(`Run it in a terminal to be asked, or name the agent and the token:\n\n${HELP}`)
257
+ error.write(`Run it in a terminal to be asked, or name the agent and the pairing key:\n\n${HELP}`)
257
258
  return 2
258
259
  }
259
260
 
@@ -277,9 +278,9 @@ export async function runSetup(argv, { input = process.stdin, output = process.s
277
278
  }
278
279
 
279
280
  while (!TOKEN_PATTERN.test(token)) {
280
- if (token) say("That is not a Xakto token: it is 36 letters and digits, from Xakto › Settings › MCP server › Copy token.")
281
+ if (token) say("That is not a Xakto pairing key: it is 36 letters and digits, from Xakto's menu › Coding agent › Copy key.")
281
282
  if (!interactive) return 2
282
- token = (await ask(rl, output, "\nPaste your token from Xakto › Settings › MCP server: ", true)).trim().toLowerCase()
283
+ token = (await ask(rl, output, "\nPaste your pairing key (Xakto's menu › Coding agent › Copy key): ", true)).trim().toLowerCase()
283
284
  }
284
285
 
285
286
  let result
@@ -291,7 +292,7 @@ export async function runSetup(argv, { input = process.stdin, output = process.s
291
292
  }
292
293
  say(`\n✔ ${result.replaced ? "Replaced the older Xakto setup in" : "Added Xakto to"} ${agent.name}${agent.kind === "claude" ? ", for all your projects" : ""}.`)
293
294
  if (result.file) say(` ${result.file}`)
294
- say(` ${result.next} Xakto's Settings turns green when it connects.`)
295
+ say(` ${result.next} Xakto's menu › Coding agent says Connected when it does.`)
295
296
  return 0
296
297
  } finally {
297
298
  rl?.close()