cyberdyne-mcp 0.4.0 → 0.5.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
@@ -90,17 +90,16 @@ then releases payment on verify. The pattern behind x402-native traders like
90
90
  CYBERDYNE_IDENTITY_TOKEN=cyb_… npm run build && npm run founder-check
91
91
  ```
92
92
 
93
- ## Install — one line, no clone, no build
93
+ ## Install — one line
94
94
 
95
- The repo ships its built `dist/`, so `npx` runs it straight from GitHub. You only
96
- need Node 18+ and your `cyb_…` agent key (mint one in the app's Agent Console).
95
+ Published on [npm](https://www.npmjs.com/package/cyberdyne-mcp), so `npx` runs it
96
+ instantly. You only need Node 18+ and your `cyb_…` agent key (mint one in the app's
97
+ Agent Console).
97
98
 
98
99
  **Claude Code:**
99
100
 
100
101
  ```bash
101
- claude mcp add cyberdyne \
102
- -e CYBERDYNE_IDENTITY_TOKEN=cyb_… \
103
- -- npx -y github:Cyberdyne-OS/cyberdyne-mcp
102
+ claude mcp add cyberdyne -e CYBERDYNE_IDENTITY_TOKEN=cyb_… -- npx -y cyberdyne-mcp
104
103
  ```
105
104
 
106
105
  **Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` and restart:
@@ -110,17 +109,15 @@ claude mcp add cyberdyne \
110
109
  "mcpServers": {
111
110
  "cyberdyne": {
112
111
  "command": "npx",
113
- "args": ["-y", "github:Cyberdyne-OS/cyberdyne-mcp"],
114
- "env": {
115
- "CYBERDYNE_IDENTITY_TOKEN": "cyb_…",
116
- "CYBERDYNE_API_URL": "https://app.cyberdyne-os.xyz"
117
- }
112
+ "args": ["-y", "cyberdyne-mcp"],
113
+ "env": { "CYBERDYNE_IDENTITY_TOKEN": "cyb_…" }
118
114
  }
119
115
  }
120
116
  }
121
117
  ```
122
118
 
123
- *(For local dev from a clone: `npm install && npm run build`, then point the command at `node /abs/path/dist/server.js`.)*
119
+ Once connected, run **`/mcp__cyberdyne__quickstart`** for the full fund post →
120
+ pay walkthrough. *(Local dev from a clone: `npm install && npm run build`, then point at `node /abs/path/dist/server.js`.)*
124
121
 
125
122
  Then ask the agent, e.g.:
126
123
 
package/dist/client.js CHANGED
@@ -9,17 +9,48 @@
9
9
  * body (`identity_token`). Used for `search_humans` (the REST
10
10
  * GET /api/humans is session-only and rejects Bearer keys).
11
11
  *
12
- * Config is read from the environment (stdio MCP servers take creds from env):
13
- * CYBERDYNE_API_URL default "https://app.cyberdyne-os.xyz"
14
- * CYBERDYNE_IDENTITY_TOKEN the agent's `cyb_…` key (required for any network call)
12
+ * The agent key (`cyb_…`) is resolved, in order, from:
13
+ * 1. env CYBERDYNE_IDENTITY_TOKEN (e.g. `claude mcp add … -e CYBERDYNE_IDENTITY_TOKEN=…`)
14
+ * 2. a saved login at ~/.cyberdyne/config.json (written by `cyberdyne-mcp login cyb_…`)
15
+ * so the install line can be the short `claude mcp add cyberdyne -- npx -y cyberdyne-mcp`.
16
+ * CYBERDYNE_API_URL overrides the default "https://app.cyberdyne-os.xyz".
15
17
  *
16
18
  * No secrets are hardcoded; nothing is logged that could leak the key.
17
19
  */
20
+ import { homedir } from "node:os";
21
+ import { join } from "node:path";
22
+ import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
18
23
  export const DEFAULT_API_URL = "https://app.cyberdyne-os.xyz";
19
- /** Read config from the environment. `token` may be undefined (tools then error). */
24
+ /** Path to the persisted login (mode 600). */
25
+ export function configPath() {
26
+ return join(homedir(), ".cyberdyne", "config.json");
27
+ }
28
+ function readConfigFile() {
29
+ try {
30
+ return JSON.parse(readFileSync(configPath(), "utf8"));
31
+ }
32
+ catch {
33
+ return {};
34
+ }
35
+ }
36
+ /** Persist the agent key to ~/.cyberdyne/config.json (0600). Returns the path. */
37
+ export function saveToken(token) {
38
+ mkdirSync(join(homedir(), ".cyberdyne"), { recursive: true });
39
+ const p = configPath();
40
+ writeFileSync(p, JSON.stringify({ ...readConfigFile(), identity_token: token.trim() }, null, 2));
41
+ try {
42
+ chmodSync(p, 0o600);
43
+ }
44
+ catch {
45
+ /* best-effort on platforms without POSIX modes */
46
+ }
47
+ return p;
48
+ }
49
+ /** Resolve config: env first, then the saved login. `token` may be undefined. */
20
50
  export function readConfig(env = process.env) {
21
- const apiUrl = (env.CYBERDYNE_API_URL || DEFAULT_API_URL).replace(/\/+$/, "");
22
- const token = env.CYBERDYNE_IDENTITY_TOKEN?.trim() || undefined;
51
+ const file = readConfigFile();
52
+ const apiUrl = (env.CYBERDYNE_API_URL || file.api_url || DEFAULT_API_URL).replace(/\/+$/, "");
53
+ const token = env.CYBERDYNE_IDENTITY_TOKEN?.trim() || file.identity_token?.trim() || undefined;
23
54
  return { apiUrl, token };
24
55
  }
25
56
  /** An API error surfaced to the caller — carries the HTTP status + the API's error code. */
package/dist/server.js CHANGED
@@ -40,7 +40,21 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
40
40
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
41
41
  import { z } from "zod";
42
42
  import { CATEGORIES, TASK_CATEGORIES } from "./registry.js";
43
- import { ApiError, CyberdyneClient, MissingTokenError, readConfig } from "./client.js";
43
+ import { ApiError, CyberdyneClient, MissingTokenError, readConfig, saveToken } from "./client.js";
44
+ // `cyberdyne-mcp login cyb_…` — persist the key so the MCP add line can omit it
45
+ // (short DFM-style install). Runs before the server boots, then exits.
46
+ if (process.argv[2] === "login") {
47
+ const token = process.argv[3]?.trim();
48
+ if (!token || !token.startsWith("cyb_")) {
49
+ console.error("Usage: npx cyberdyne-mcp login cyb_<your-key>\n" +
50
+ "Get your key at https://app.cyberdyne-os.xyz → Agent Console → Generate API key.");
51
+ process.exit(1);
52
+ }
53
+ const path = saveToken(token);
54
+ console.error(`✓ Saved your CYBERDYNE key to ${path}.\n` +
55
+ "Now run: claude mcp add cyberdyne -- npx -y cyberdyne-mcp");
56
+ process.exit(0);
57
+ }
44
58
  const config = readConfig();
45
59
  const client = new CyberdyneClient(config);
46
60
  // ---- Result helpers -------------------------------------------------------
@@ -185,5 +199,5 @@ server.registerPrompt("quickstart", {
185
199
  const transport = new StdioServerTransport();
186
200
  await server.connect(transport);
187
201
  console.error(`CYBERDYNE MCP server running on stdio → ${config.apiUrl}` +
188
- (config.token ? "" : " (no CYBERDYNE_IDENTITY_TOKEN set; networked tools will error until you set it)") +
202
+ (config.token ? "" : " (no key — run `npx cyberdyne-mcp login cyb_…` or set CYBERDYNE_IDENTITY_TOKEN; networked tools error until then)") +
189
203
  ". Tools: list_categories, search_humans, get_treasury, fund_treasury, get_deposit_address, deposit, post_task, assign_task, authorize_task, get_task, release_payment, close_task.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberdyne-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/client.ts CHANGED
@@ -9,13 +9,19 @@
9
9
  * body (`identity_token`). Used for `search_humans` (the REST
10
10
  * GET /api/humans is session-only and rejects Bearer keys).
11
11
  *
12
- * Config is read from the environment (stdio MCP servers take creds from env):
13
- * CYBERDYNE_API_URL default "https://app.cyberdyne-os.xyz"
14
- * CYBERDYNE_IDENTITY_TOKEN the agent's `cyb_…` key (required for any network call)
12
+ * The agent key (`cyb_…`) is resolved, in order, from:
13
+ * 1. env CYBERDYNE_IDENTITY_TOKEN (e.g. `claude mcp add … -e CYBERDYNE_IDENTITY_TOKEN=…`)
14
+ * 2. a saved login at ~/.cyberdyne/config.json (written by `cyberdyne-mcp login cyb_…`)
15
+ * so the install line can be the short `claude mcp add cyberdyne -- npx -y cyberdyne-mcp`.
16
+ * CYBERDYNE_API_URL overrides the default "https://app.cyberdyne-os.xyz".
15
17
  *
16
18
  * No secrets are hardcoded; nothing is logged that could leak the key.
17
19
  */
18
20
 
21
+ import { homedir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
24
+
19
25
  export const DEFAULT_API_URL = "https://app.cyberdyne-os.xyz";
20
26
 
21
27
  export interface CyberdyneConfig {
@@ -23,10 +29,37 @@ export interface CyberdyneConfig {
23
29
  token: string | undefined;
24
30
  }
25
31
 
26
- /** Read config from the environment. `token` may be undefined (tools then error). */
32
+ /** Path to the persisted login (mode 600). */
33
+ export function configPath(): string {
34
+ return join(homedir(), ".cyberdyne", "config.json");
35
+ }
36
+
37
+ function readConfigFile(): { identity_token?: string; api_url?: string } {
38
+ try {
39
+ return JSON.parse(readFileSync(configPath(), "utf8"));
40
+ } catch {
41
+ return {};
42
+ }
43
+ }
44
+
45
+ /** Persist the agent key to ~/.cyberdyne/config.json (0600). Returns the path. */
46
+ export function saveToken(token: string): string {
47
+ mkdirSync(join(homedir(), ".cyberdyne"), { recursive: true });
48
+ const p = configPath();
49
+ writeFileSync(p, JSON.stringify({ ...readConfigFile(), identity_token: token.trim() }, null, 2));
50
+ try {
51
+ chmodSync(p, 0o600);
52
+ } catch {
53
+ /* best-effort on platforms without POSIX modes */
54
+ }
55
+ return p;
56
+ }
57
+
58
+ /** Resolve config: env first, then the saved login. `token` may be undefined. */
27
59
  export function readConfig(env: NodeJS.ProcessEnv = process.env): CyberdyneConfig {
28
- const apiUrl = (env.CYBERDYNE_API_URL || DEFAULT_API_URL).replace(/\/+$/, "");
29
- const token = env.CYBERDYNE_IDENTITY_TOKEN?.trim() || undefined;
60
+ const file = readConfigFile();
61
+ const apiUrl = (env.CYBERDYNE_API_URL || file.api_url || DEFAULT_API_URL).replace(/\/+$/, "");
62
+ const token = env.CYBERDYNE_IDENTITY_TOKEN?.trim() || file.identity_token?.trim() || undefined;
30
63
  return { apiUrl, token };
31
64
  }
32
65
 
package/src/server.ts CHANGED
@@ -40,7 +40,26 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
40
40
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
41
41
  import { z } from "zod";
42
42
  import { CATEGORIES, TASK_CATEGORIES } from "./registry.js";
43
- import { ApiError, CyberdyneClient, MissingTokenError, readConfig } from "./client.js";
43
+ import { ApiError, CyberdyneClient, MissingTokenError, readConfig, saveToken } from "./client.js";
44
+
45
+ // `cyberdyne-mcp login cyb_…` — persist the key so the MCP add line can omit it
46
+ // (short DFM-style install). Runs before the server boots, then exits.
47
+ if (process.argv[2] === "login") {
48
+ const token = process.argv[3]?.trim();
49
+ if (!token || !token.startsWith("cyb_")) {
50
+ console.error(
51
+ "Usage: npx cyberdyne-mcp login cyb_<your-key>\n" +
52
+ "Get your key at https://app.cyberdyne-os.xyz → Agent Console → Generate API key.",
53
+ );
54
+ process.exit(1);
55
+ }
56
+ const path = saveToken(token);
57
+ console.error(
58
+ `✓ Saved your CYBERDYNE key to ${path}.\n` +
59
+ "Now run: claude mcp add cyberdyne -- npx -y cyberdyne-mcp",
60
+ );
61
+ process.exit(0);
62
+ }
44
63
 
45
64
  const config = readConfig();
46
65
  const client = new CyberdyneClient(config);
@@ -285,6 +304,6 @@ const transport = new StdioServerTransport();
285
304
  await server.connect(transport);
286
305
  console.error(
287
306
  `CYBERDYNE MCP server running on stdio → ${config.apiUrl}` +
288
- (config.token ? "" : " (no CYBERDYNE_IDENTITY_TOKEN set; networked tools will error until you set it)") +
307
+ (config.token ? "" : " (no key — run `npx cyberdyne-mcp login cyb_…` or set CYBERDYNE_IDENTITY_TOKEN; networked tools error until then)") +
289
308
  ". Tools: list_categories, search_humans, get_treasury, fund_treasury, get_deposit_address, deposit, post_task, assign_task, authorize_task, get_task, release_payment, close_task.",
290
309
  );