@super-hands/connect 0.1.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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/client.mjs +192 -0
  4. package/package.json +33 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Superhands
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @super-hands/connect
2
+
3
+ Connect the coding agents on this machine to your team's
4
+ [Superhands](https://app.superhands.ai) MCP server, in one command.
5
+
6
+ Superhands setup generates the command for you, credential included:
7
+
8
+ ```
9
+ SUPERHANDS_MCP_TOKEN="…" SUPERHANDS_MCP_URL="…" npx -y @super-hands/connect@latest
10
+ ```
11
+
12
+ ## What it does
13
+
14
+ - **Cursor** — merges the `superhands` server into `~/.cursor/mcp.json`.
15
+ Every other server in the file is preserved; a file that does not parse is
16
+ left untouched. Restart Cursor and enable the server under Settings → MCP.
17
+ - **Claude Code** — registers the server through Claude Code's own CLI
18
+ (`claude mcp add`, user scope). A new `claude` session connects on start.
19
+
20
+ With no flags it connects every client it finds; `--cursor` or `--claude`
21
+ narrow it to one.
22
+
23
+ ## What it does not do
24
+
25
+ It reads no repository, runs none of your code, and uploads nothing. The one
26
+ secret it holds is the token in its own environment, and it is written only
27
+ into the client configs above. The token can be revoked at any time from the
28
+ Superhands MCP page.
29
+
30
+ This file is generated from
31
+ [`lib/connect-client-entry.ts`](https://github.com/superhandsai/superhandsmcp/blob/main/lib/connect-client-entry.ts)
32
+ in the Superhands repository — the published bytes are that commit's, and are
33
+ not minified.
package/client.mjs ADDED
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ // GENERATED by scripts/build-capture-toolkit.mjs — do not edit by hand.
3
+ //
4
+ // The Superhands connect client. Bundled from this commit's
5
+ // `lib/connect-client-entry.ts`.
6
+ //
7
+ // It writes this team's MCP server into the coding agents installed on this
8
+ // machine — Cursor's mcp.json, Claude Code through its own CLI — and nothing
9
+ // else. It reads no repository, uploads nothing, and the credential it
10
+ // carries arrived in its own environment. Read on — it is not minified.
11
+
12
+ // lib/connect-client-entry.ts
13
+ import { execFileSync } from "node:child_process";
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { dirname, join } from "node:path";
17
+
18
+ // lib/mcp-token.ts
19
+ var MCP_ACCESS_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
20
+
21
+ // lib/mcp-clients.ts
22
+ var CONNECT_TOKEN_ENV = "SUPERHANDS_MCP_TOKEN";
23
+ var CONNECT_URL_ENV = "SUPERHANDS_MCP_URL";
24
+ var CONNECT_CLIENT_PACKAGE = "@super-hands/connect";
25
+ var CONNECT_CLIENT_SPEC = `${CONNECT_CLIENT_PACKAGE}@latest`;
26
+ var MCP_SERVER_KEY = "superhands";
27
+ function mcpAuthorizationHeader(token) {
28
+ return `Bearer ${token}`;
29
+ }
30
+ function codexConfigBlock(args) {
31
+ return [
32
+ `[mcp_servers.${MCP_SERVER_KEY}]`,
33
+ `url = "${args.endpoint}"`,
34
+ `bearer_token = "${args.token}"`
35
+ ].join("\n");
36
+ }
37
+
38
+ // lib/connect-client-entry.ts
39
+ function say(line) {
40
+ process.stdout.write(`superhands: ${line}
41
+ `);
42
+ }
43
+ function stop(message) {
44
+ process.stdout.write(`superhands: connect failed \u2014 ${message}
45
+ `);
46
+ process.exit(1);
47
+ }
48
+ function mergedCursorConfig(existing, args) {
49
+ let config = {};
50
+ if (existing !== null && existing.trim() !== "") {
51
+ let parsed;
52
+ try {
53
+ parsed = JSON.parse(existing);
54
+ } catch {
55
+ throw new Error("not valid JSON");
56
+ }
57
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
58
+ throw new Error("not a JSON object");
59
+ }
60
+ config = parsed;
61
+ }
62
+ const serversRaw = config.mcpServers;
63
+ const servers = typeof serversRaw === "object" && serversRaw !== null && !Array.isArray(serversRaw) ? serversRaw : {};
64
+ const replaced = MCP_SERVER_KEY in servers;
65
+ servers[MCP_SERVER_KEY] = {
66
+ url: args.endpoint,
67
+ headers: { Authorization: mcpAuthorizationHeader(args.token) }
68
+ };
69
+ config.mcpServers = servers;
70
+ return { text: `${JSON.stringify(config, null, 2)}
71
+ `, replaced };
72
+ }
73
+ function appendedCodexConfig(existing, args) {
74
+ if (existing.includes(`[mcp_servers.${MCP_SERVER_KEY}]`)) {
75
+ return { text: existing, alreadyPresent: true };
76
+ }
77
+ const sep = existing === "" || existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
78
+ return { text: `${existing}${sep}${codexConfigBlock(args)}
79
+ `, alreadyPresent: false };
80
+ }
81
+ function hasCli(bin) {
82
+ try {
83
+ execFileSync(bin, ["--version"], { stdio: "ignore" });
84
+ return true;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ async function main() {
90
+ const token = process.env[CONNECT_TOKEN_ENV]?.trim();
91
+ const endpoint = process.env[CONNECT_URL_ENV]?.trim();
92
+ if (!token || !endpoint) {
93
+ stop(
94
+ `this command needs ${CONNECT_TOKEN_ENV} and ${CONNECT_URL_ENV} set on the same line. Copy the whole command from Superhands setup and run it unchanged.`
95
+ );
96
+ }
97
+ const flags = new Set(process.argv.slice(2));
98
+ const onlyCursor = flags.has("--cursor");
99
+ const onlyClaude = flags.has("--claude");
100
+ const onlyCodex = flags.has("--codex");
101
+ const autodetect = !onlyCursor && !onlyClaude && !onlyCodex;
102
+ let connected = 0;
103
+ let attempted = 0;
104
+ const cursorDir = join(homedir(), ".cursor");
105
+ if (onlyCursor || autodetect && existsSync(cursorDir)) {
106
+ attempted += 1;
107
+ const configPath = join(cursorDir, "mcp.json");
108
+ let existing = null;
109
+ try {
110
+ existing = readFileSync(configPath, "utf8");
111
+ } catch {
112
+ existing = null;
113
+ }
114
+ try {
115
+ const merged = mergedCursorConfig(existing, { endpoint, token });
116
+ mkdirSync(dirname(configPath), { recursive: true });
117
+ writeFileSync(configPath, merged.text);
118
+ say(
119
+ merged.replaced ? `Cursor \u2014 updated the ${MCP_SERVER_KEY} server in ${configPath}.` : `Cursor \u2014 added the ${MCP_SERVER_KEY} server to ${configPath}.`
120
+ );
121
+ say(" Restart Cursor, then enable superhands under Settings \u2192 MCP.");
122
+ connected += 1;
123
+ } catch {
124
+ say(`Cursor \u2014 ${configPath} is not valid JSON, so it was left untouched.`);
125
+ say(" Fix or remove that file, then run this command again.");
126
+ }
127
+ }
128
+ if (onlyClaude || autodetect && hasCli("claude")) {
129
+ attempted += 1;
130
+ try {
131
+ try {
132
+ execFileSync("claude", ["mcp", "remove", "--scope", "user", MCP_SERVER_KEY], {
133
+ stdio: "ignore"
134
+ });
135
+ } catch {
136
+ }
137
+ execFileSync(
138
+ "claude",
139
+ [
140
+ "mcp",
141
+ "add",
142
+ "--transport",
143
+ "http",
144
+ "--scope",
145
+ "user",
146
+ MCP_SERVER_KEY,
147
+ endpoint,
148
+ "--header",
149
+ `Authorization: ${mcpAuthorizationHeader(token)}`
150
+ ],
151
+ { stdio: "ignore" }
152
+ );
153
+ say(`Claude Code \u2014 added the ${MCP_SERVER_KEY} server (user scope).`);
154
+ say(" Open a new claude session and it connects on start.");
155
+ connected += 1;
156
+ } catch {
157
+ say("Claude Code \u2014 `claude mcp add` failed. Run it by hand from the Superhands MCP page.");
158
+ }
159
+ }
160
+ const codexDir = join(homedir(), ".codex");
161
+ if (onlyCodex || autodetect && (existsSync(codexDir) || hasCli("codex"))) {
162
+ attempted += 1;
163
+ const configPath = join(codexDir, "config.toml");
164
+ let existing = "";
165
+ try {
166
+ existing = readFileSync(configPath, "utf8");
167
+ } catch {
168
+ existing = "";
169
+ }
170
+ const result = appendedCodexConfig(existing, { endpoint, token });
171
+ if (result.alreadyPresent) {
172
+ say(`Codex \u2014 ${configPath} already names a ${MCP_SERVER_KEY} server, so it was left as it is.`);
173
+ say(" If that connection is stale, update bearer_token there by hand.");
174
+ connected += 1;
175
+ } else {
176
+ mkdirSync(dirname(configPath), { recursive: true });
177
+ writeFileSync(configPath, result.text);
178
+ say(`Codex \u2014 added the ${MCP_SERVER_KEY} server to ${configPath}.`);
179
+ say(" Start a new codex session and it connects on start.");
180
+ connected += 1;
181
+ }
182
+ }
183
+ if (connected === 0) {
184
+ stop(
185
+ attempted > 0 ? "nothing was connected. Fix the issue above, then run this command again." : `no supported client was found on this machine. Add it to yours by hand: the server URL is ${endpoint}, sent with the header "Authorization: ${mcpAuthorizationHeader(token)}".`
186
+ );
187
+ }
188
+ say("Done. Superhands notices the moment an agent connects \u2014 setup ticks by itself.");
189
+ }
190
+
191
+ // connect-client.ts
192
+ await main();
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@super-hands/connect",
3
+ "version": "0.1.0",
4
+ "description": "Connect the coding agents on this machine to your team's Superhands MCP server. Writes each client's own config; reads no repository, uploads nothing.",
5
+ "bin": {
6
+ "superhands-connect": "./client.mjs"
7
+ },
8
+ "files": [
9
+ "client.mjs",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "engines": {
14
+ "node": ">=22"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/superhandsai/superhandsmcp.git",
20
+ "directory": "packages/connect-client"
21
+ },
22
+ "homepage": "https://app.superhands.ai",
23
+ "keywords": [
24
+ "superhands",
25
+ "mcp",
26
+ "design-system",
27
+ "design-tokens",
28
+ "components"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }