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 +37 -33
- package/package.json +1 -1
- package/xakto-mcp.mjs +10 -4
- package/xakto-setup.mjs +16 -15
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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
|
|
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 --
|
|
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** (
|
|
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=<
|
|
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 = "<
|
|
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": "<
|
|
86
|
+
"env": { "XAKTO_TOKEN": "<pairing key>" }
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
```
|
|
89
|
-
3. Start a new agent session. The pill beside **
|
|
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
|
|
155
|
-
with an HMAC-SHA256 of it. The
|
|
156
|
-
whatever answers on the port first learns nothing from it.
|
|
157
|
-
- The
|
|
158
|
-
scripts, and is shown only in Xakto's
|
|
159
|
-
own that the page it sits over cannot read. The page can load a
|
|
160
|
-
frame, but the
|
|
161
|
-
with a one-time code. **New
|
|
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
|
|
170
|
-
| `XAKTO_PORT` | `47821` | The port to listen on. It has to be the one Xakto connects to, which the setup
|
|
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=<
|
|
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".**
|
|
193
|
-
Chrome is not running, or the relay was started with another
|
|
194
|
-
- **
|
|
195
|
-
this browser's. Run `npx -y xakto-mcp setup` again with the
|
|
196
|
-
|
|
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
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.
|
|
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 >
|
|
355
|
-
: "This server was started without
|
|
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`,
|
|
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
|
|
4
|
-
*
|
|
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
|
|
8
|
-
* npx -y xakto-mcp setup cursor that agent; asks the
|
|
9
|
-
* npx -y xakto-mcp setup cursor --
|
|
10
|
-
* ... --port 47900 only when Xakto
|
|
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
|
|
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> --
|
|
215
|
-
--port <port> only if Xakto
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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()
|