@heliyos/heliyos-mcp 1.0.3 → 1.0.4

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "heliyos",
3
3
  "displayName": "Heliyos",
4
- "version": "1.0.3",
4
+ "version": "1.0.4",
5
5
  "description": "Heliyos, the AI operating system for revenue, for your agent: customer records, notes, tasks, Scouts, reviews, knowledge and AI workers, through the Heliyos API with an API key.",
6
6
  "author": {
7
7
  "name": "Heliyos",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heliyos",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Heliyos, the AI operating system for revenue, for your agent: customer records, notes, tasks, Scouts, reviews, knowledge and AI workers.",
5
5
  "author": {
6
6
  "name": "Heliyos"
package/README.md CHANGED
@@ -83,13 +83,15 @@ Every client runs the same command, `npx -y @heliyos/heliyos-mcp`, with your key
83
83
 
84
84
  ### Claude Code
85
85
 
86
- Install the plugin. It asks for your key and keeps it in your system's secure credential store, and it adds a skill that teaches Claude when to use which tool.
86
+ Install the plugin and set your key in one go. The key goes into your system's secure credential store, never into a file, and the plugin adds a skill that teaches Claude when to use which tool.
87
87
 
88
88
  ```sh
89
89
  claude plugin marketplace add heliyos-ai/heliyos-mcp
90
- claude plugin install heliyos@heliyos
90
+ claude plugin install heliyos@heliyos --config api_key=YOUR_HELIYOS_API_KEY
91
91
  ```
92
92
 
93
+ If you installed without `--config`, the server stays off until the key is set: run `/plugin configure heliyos@heliyos` inside Claude Code and enter it there. Then `/mcp` lists `heliyos`, or restart Claude Code if it does not.
94
+
93
95
  Or add only the MCP server:
94
96
 
95
97
  ```sh
@@ -431,7 +433,8 @@ Report a security issue privately through [GitHub security advisories](https://g
431
433
  | `FORBIDDEN_ACTION` | The workspace plan or billing status does not allow the action, or the action is not available to API keys. |
432
434
  | `RATE_LIMITED` | You are above 60 requests per minute. Wait a minute, and prefer list calls over many single gets. |
433
435
  | `heliyos_ask` times out | Raise the client's tool timeout (see [Tool timeout](#tool-timeout)). The run keeps going; follow it with `heliyos_runs_get`. |
434
- | The server does not start or shows no tools | Check `node -v` is 20 or later. The first start downloads the package, which can take longer than a 10 second startup timeout; raise it (Codex `startup_timeout_sec`, Claude Code `MCP_TIMEOUT`). Apps opened from the dock may not find `npx`; use its full path from `which npx`. |
436
+ | The server shows "connecting" on its first start | The first `npx -y @heliyos/heliyos-mcp` downloads the package, which can take 20 to 30 seconds on a slow connection and outlast a client's startup timeout. Warm the cache once from a terminal with `npx -y @heliyos/heliyos-mcp --version`, then reconnect; later starts take about a second. Or raise the timeout (Codex `startup_timeout_sec`, Claude Code `MCP_TIMEOUT=60000`). |
437
+ | `Node 16.x is too old` or tools fail with `fetch is not defined` | The client started `npx` with an old Node, usually a version manager's default. The server needs Node 20 or newer and restarts itself on the newest suitable Node it finds (nvm, fnm, n, Volta, Homebrew, PATH); the message names which one. If it finds none, install Node 20+ or run `nvm install 20 && nvm alias default 20`, then restart the client. Apps opened from the dock may not find `npx` at all; use its full path from `which npx`. |
435
438
  | Experimental tools are missing | Set `HELIYOS_MCP_EXPERIMENTAL=1` and restart the client. |
436
439
 
437
440
  Still stuck? Open an issue at [github.com/heliyos-ai/heliyos-mcp/issues](https://github.com/heliyos-ai/heliyos-mcp/issues). Never paste your key.
@@ -0,0 +1,18 @@
1
+ /** The lowest Node.js major the server supports (global fetch, Web Streams). */
2
+ export declare const MIN_NODE_MAJOR = 20;
3
+ /** The major version of a `vX.Y.Z` or `X.Y.Z` string, or 0 when unparsable. */
4
+ export declare function majorOf(version: string): number;
5
+ /** Compares two `vX.Y.Z` strings numerically; positive when `a` is newer. */
6
+ export declare function compareVersions(a: string, b: string): number;
7
+ /** Every `node` executable this machine is likely to have, deduplicated, unverified. */
8
+ export declare function candidateNodes(env?: NodeJS.ProcessEnv, home?: string): string[];
9
+ /** `node -v` of an executable, or undefined when it cannot be run. Bounded to a second. */
10
+ export declare function versionOf(executable: string): string | undefined;
11
+ /**
12
+ * The newest Node at or above `minMajor` among `candidates`, as `{path, version}`,
13
+ * or undefined when none qualifies. `probe` is injectable for tests.
14
+ */
15
+ export declare function findNewerNode(candidates: string[], minMajor?: number, probe?: (executable: string) => string | undefined): {
16
+ path: string;
17
+ version: string;
18
+ } | undefined;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Picks a Node.js runtime the server can run on. Clients start the server with
3
+ * `npx`, which uses whatever `node` is first on their PATH; version managers
4
+ * such as nvm often leave an old default there (Node 16 has no global fetch),
5
+ * and the server would die before the MCP handshake with no useful message.
6
+ *
7
+ * `findNewerNode` looks in the places version managers and package managers
8
+ * install Node, verifies each candidate's version, and returns the newest one
9
+ * that meets the minimum. Everything here must run on old Node too, so it uses
10
+ * only long-standing APIs.
11
+ */
12
+ import { execFileSync } from "node:child_process";
13
+ import { existsSync, readdirSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { delimiter, join } from "node:path";
16
+ /** The lowest Node.js major the server supports (global fetch, Web Streams). */
17
+ export const MIN_NODE_MAJOR = 20;
18
+ /** The major version of a `vX.Y.Z` or `X.Y.Z` string, or 0 when unparsable. */
19
+ export function majorOf(version) {
20
+ const match = /^v?(\d+)\./.exec(version.trim());
21
+ return match ? Number(match[1]) : 0;
22
+ }
23
+ /** Compares two `vX.Y.Z` strings numerically; positive when `a` is newer. */
24
+ export function compareVersions(a, b) {
25
+ const pa = a.replace(/^v/, "").split(".").map(Number);
26
+ const pb = b.replace(/^v/, "").split(".").map(Number);
27
+ for (let i = 0; i < 3; i += 1) {
28
+ const diff = (pa[i] || 0) - (pb[i] || 0);
29
+ if (diff !== 0)
30
+ return diff;
31
+ }
32
+ return 0;
33
+ }
34
+ const EXE = process.platform === "win32" ? "node.exe" : "node";
35
+ /** Directories that may hold one Node install each (`<dir>/<version>/bin/node`). */
36
+ function versionManagerRoots(env, home) {
37
+ const fnmRoots = [
38
+ env.FNM_DIR,
39
+ join(home, ".fnm"),
40
+ join(home, ".local", "share", "fnm"),
41
+ join(home, "Library", "Application Support", "fnm"),
42
+ env.APPDATA ? join(env.APPDATA, "fnm") : undefined,
43
+ ].filter((p) => !!p);
44
+ return [
45
+ // nvm: ~/.nvm/versions/node/v20.15.0/bin/node
46
+ { root: join(env.NVM_DIR || join(home, ".nvm"), "versions", "node"), bin: process.platform === "win32" ? "" : "bin" },
47
+ // nvm-windows: %NVM_HOME%\v20.15.0\node.exe
48
+ ...(env.NVM_HOME ? [{ root: env.NVM_HOME, bin: "" }] : []),
49
+ // fnm: <root>/node-versions/v20.15.0/installation/bin/node
50
+ ...fnmRoots.map((r) => ({ root: join(r, "node-versions"), bin: join("installation", process.platform === "win32" ? "" : "bin") })),
51
+ // n: /usr/local/n/versions/node/20.15.0/bin/node
52
+ { root: join(env.N_PREFIX || "/usr/local", "n", "versions", "node"), bin: "bin" },
53
+ // Volta's shared tool images
54
+ { root: join(env.VOLTA_HOME || join(home, ".volta"), "tools", "image", "node"), bin: process.platform === "win32" ? "" : "bin" },
55
+ ];
56
+ }
57
+ /** Every `node` executable this machine is likely to have, deduplicated, unverified. */
58
+ export function candidateNodes(env = process.env, home = homedir()) {
59
+ const found = [];
60
+ const add = (p) => {
61
+ if (p && !found.includes(p) && existsSync(p))
62
+ found.push(p);
63
+ };
64
+ for (const { root, bin } of versionManagerRoots(env, home)) {
65
+ let entries = [];
66
+ try {
67
+ entries = readdirSync(root);
68
+ }
69
+ catch {
70
+ continue;
71
+ }
72
+ for (const entry of entries)
73
+ add(join(root, entry, bin, EXE));
74
+ }
75
+ for (const fixed of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/opt/local/bin", join(home, ".volta", "bin")])
76
+ add(join(fixed, EXE));
77
+ for (const dir of (env.PATH || "").split(delimiter))
78
+ if (dir)
79
+ add(join(dir, EXE));
80
+ return found;
81
+ }
82
+ /** `node -v` of an executable, or undefined when it cannot be run. Bounded to a second. */
83
+ export function versionOf(executable) {
84
+ try {
85
+ const out = execFileSync(executable, ["-v"], { encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"] });
86
+ return /^v\d+\.\d+\.\d+/.test(out.trim()) ? out.trim() : undefined;
87
+ }
88
+ catch {
89
+ return undefined;
90
+ }
91
+ }
92
+ /**
93
+ * The newest Node at or above `minMajor` among `candidates`, as `{path, version}`,
94
+ * or undefined when none qualifies. `probe` is injectable for tests.
95
+ */
96
+ export function findNewerNode(candidates, minMajor = MIN_NODE_MAJOR, probe = versionOf) {
97
+ let best;
98
+ for (const path of candidates) {
99
+ const version = probe(path);
100
+ if (!version || majorOf(version) < minMajor)
101
+ continue;
102
+ if (!best || compareVersions(version, best.version) > 0)
103
+ best = { path, version };
104
+ }
105
+ return best;
106
+ }
@@ -0,0 +1,2 @@
1
+ /** Reads the configuration from the environment and serves until the client disconnects. */
2
+ export declare function main(): void;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Starts the MCP server over stdio. Loaded by bin/stdio.ts only after the Node
3
+ * version check, because the imports below need Node 20 or newer.
4
+ */
5
+ import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
6
+ import { createClient } from "../client/http.js";
7
+ import { ConfigError, loadConfig } from "../config.js";
8
+ import { createServer } from "../index.js";
9
+ /** Reads the configuration from the environment and serves until the client disconnects. */
10
+ export function main() {
11
+ let config;
12
+ try {
13
+ config = loadConfig();
14
+ }
15
+ catch (err) {
16
+ if (err instanceof ConfigError) {
17
+ process.stderr.write(`heliyos-mcp: ${err.message}\n`);
18
+ process.exit(1);
19
+ }
20
+ throw err;
21
+ }
22
+ const client = createClient({ baseUrl: config.baseUrl, apiKey: config.apiKey });
23
+ const options = { experimental: config.experimental };
24
+ // serveStdio picks the protocol era from the client's opening message and
25
+ // pins one server instance from the factory for the connection.
26
+ serveStdio(() => createServer(client, options), {
27
+ transport: new StdioServerTransport(),
28
+ onerror: (error) => process.stderr.write(`heliyos-mcp: ${error.message}\n`),
29
+ });
30
+ }
@@ -2,30 +2,63 @@
2
2
  /**
3
3
  * stdio entry point: `npx -y @heliyos/heliyos-mcp`. stdout carries MCP protocol
4
4
  * messages only; diagnostics go to stderr and never include the API key.
5
+ *
6
+ * The server needs Node 20 or newer, but `npx` runs whatever `node` is first
7
+ * on the client's PATH, which with version managers is often an old default.
8
+ * So this file does nothing version-specific itself: it checks the running
9
+ * Node first and, when it is too old, re-executes the server on the newest
10
+ * suitable Node it can find on the machine (nvm, fnm, n, Volta, Homebrew,
11
+ * PATH), passing stdio straight through. Only then does it load the server.
5
12
  */
6
- import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
7
- import { createClient } from "../client/http.js";
8
- import { ConfigError, loadConfig } from "../config.js";
9
- import { createServer } from "../index.js";
10
- function main() {
11
- let config;
12
- try {
13
- config = loadConfig();
13
+ import { spawn } from "node:child_process";
14
+ import { fileURLToPath } from "node:url";
15
+ import { PACKAGE_NAME, PACKAGE_VERSION } from "../version.js";
16
+ import { candidateNodes, findNewerNode, majorOf, MIN_NODE_MAJOR } from "./nodeRuntime.js";
17
+ /** Set on the re-executed child so a still-too-old Node can never loop. */
18
+ const REEXEC_FLAG = "HELIYOS_MCP_REEXEC";
19
+ function log(message) {
20
+ process.stderr.write(`heliyos-mcp: ${message}\n`);
21
+ }
22
+ /**
23
+ * Runs the server on another Node executable with this process's stdio and
24
+ * environment, and exits with the child's exit code.
25
+ */
26
+ function reexec(nodePath, version) {
27
+ log(`Node ${process.versions.node} is too old (needs ${MIN_NODE_MAJOR}+); restarting on Node ${version} at ${nodePath}`);
28
+ const child = spawn(nodePath, [fileURLToPath(import.meta.url)], {
29
+ stdio: "inherit",
30
+ env: { ...process.env, [REEXEC_FLAG]: "1" },
31
+ });
32
+ child.on("error", (err) => {
33
+ log(`could not start ${nodePath}: ${err.message}`);
34
+ process.exit(1);
35
+ });
36
+ child.on("exit", (code, signal) => {
37
+ if (signal)
38
+ process.kill(process.pid, signal);
39
+ process.exit(code ?? 1);
40
+ });
41
+ }
42
+ async function start() {
43
+ // `--version` answers without starting a server, so a client setup can warm
44
+ // the npx cache (`npx -y @heliyos/heliyos-mcp --version`) ahead of the first run.
45
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
46
+ process.stdout.write(`${PACKAGE_NAME} ${PACKAGE_VERSION} (node ${process.versions.node})\n`);
47
+ return;
14
48
  }
15
- catch (err) {
16
- if (err instanceof ConfigError) {
17
- process.stderr.write(`heliyos-mcp: ${err.message}\n`);
18
- process.exit(1);
49
+ if (majorOf(process.versions.node) < MIN_NODE_MAJOR) {
50
+ const newer = process.env[REEXEC_FLAG] ? undefined : findNewerNode(candidateNodes());
51
+ if (newer) {
52
+ reexec(newer.path, newer.version);
53
+ return;
19
54
  }
20
- throw err;
55
+ log(`Node ${process.versions.node} is too old: this server needs Node ${MIN_NODE_MAJOR} or newer and found no newer Node on this machine. ` +
56
+ "Install Node 20+ (https://nodejs.org) or, with nvm, run `nvm install 20 && nvm alias default 20`, then restart your client.");
57
+ process.exit(1);
21
58
  }
22
- const client = createClient({ baseUrl: config.baseUrl, apiKey: config.apiKey });
23
- const options = { experimental: config.experimental };
24
- // serveStdio picks the protocol era from the client's opening message and
25
- // pins one server instance from the factory for the connection.
26
- serveStdio(() => createServer(client, options), {
27
- transport: new StdioServerTransport(),
28
- onerror: (error) => process.stderr.write(`heliyos-mcp: ${error.message}\n`),
29
- });
59
+ // Loaded only once the runtime is known to be new enough: these modules use
60
+ // global fetch and other APIs an old Node would fail on at import time.
61
+ const { main } = await import("./serve.js");
62
+ main();
30
63
  }
31
- main();
64
+ void start();
@@ -1,4 +1,4 @@
1
1
  /** The npm package name, for the User-Agent header. */
2
2
  export declare const PACKAGE_NAME = "@heliyos/heliyos-mcp";
3
3
  /** The package version, for the User-Agent header and the MCP server info. */
4
- export declare const PACKAGE_VERSION = "1.0.3";
4
+ export declare const PACKAGE_VERSION = "1.0.4";
@@ -4,4 +4,4 @@
4
4
  /** The npm package name, for the User-Agent header. */
5
5
  export const PACKAGE_NAME = "@heliyos/heliyos-mcp";
6
6
  /** The package version, for the User-Agent header and the MCP server info. */
7
- export const PACKAGE_VERSION = "1.0.3";
7
+ export const PACKAGE_VERSION = "1.0.4";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heliyos/heliyos-mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Model Context Protocol server for Heliyos, the AI operating system for revenue. Gives AI agents tools for customer records, notes, tasks, email and meeting history, Scouts and signals, reviews, workers and the knowledge base, over stdio with a workspace API key.",
5
5
  "type": "module",
6
6
  "mcpName": "ai.heliyos/heliyos-mcp",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "heliyos",
4
- "version": "1.0.3",
4
+ "version": "1.0.4",
5
5
  "description": "Heliyos, the AI operating system for revenue, for your agent: customer records, notes, tasks, Scouts, reviews, knowledge and AI workers. Set HELIYOS_API_KEY in the environment the client starts servers with.",
6
6
  "author": {
7
7
  "name": "Heliyos",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "ai.heliyos/heliyos-mcp",
4
4
  "title": "Heliyos",
5
5
  "description": "Heliyos, the AI operating system for revenue: records, notes, tasks, Scouts and AI workers.",
6
- "version": "1.0.3",
6
+ "version": "1.0.4",
7
7
  "websiteUrl": "https://docs.heliyos.ai/mcp/overview",
8
8
  "repository": {
9
9
  "url": "https://github.com/heliyos-ai/heliyos-mcp",
@@ -14,7 +14,7 @@
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "@heliyos/heliyos-mcp",
17
- "version": "1.0.3",
17
+ "version": "1.0.4",
18
18
  "runtimeHint": "npx",
19
19
  "transport": {
20
20
  "type": "stdio"