@hyperstar/mcp 0.1.16 → 0.1.18

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
@@ -104,8 +104,11 @@ when `HYPERSTAR_API_KEY` is used. For non-production login, set matching
104
104
  ## Tools
105
105
 
106
106
  Agents should start by reading `get_hyperstar_workflow_guide` or the MCP
107
- resources/prompts listed below. Tool responses include `agent_guidance` and,
108
- where the next step is deterministic, `next_tool` and `next_arguments`.
107
+ resources/prompts listed below. The MCP server starts without auth so clients
108
+ can discover tools and guides first; Product API workflow calls still require
109
+ local browser login or `HYPERSTAR_API_KEY`. Tool responses include
110
+ `agent_guidance` and, where the next step is deterministic, `next_tool` and
111
+ `next_arguments`.
109
112
 
110
113
  - `hyperstar_whoami`
111
114
  - `list_workspaces`
@@ -353,7 +356,9 @@ npm exec --yes --package @hyperstar/mcp -- hyperstar-mcp --help
353
356
  ```
354
357
 
355
358
  These checks do not authenticate and do not call Hyperstar workflow APIs. They
356
- only prove that npm can resolve the package and both binaries can start.
359
+ prove that npm can resolve the package and both binaries can start. The package
360
+ smoke script also verifies unauthenticated MCP `tools/list`, `resources/list`,
361
+ and `prompts/list` discovery.
357
362
 
358
363
  ## Troubleshooting
359
364
 
@@ -9,7 +9,7 @@ export function createCliAuthClient(options) {
9
9
  code,
10
10
  code_verifier: verifier,
11
11
  client_id: CLI_CLIENT_ID,
12
- redirect_uri: redirectUri,
12
+ loopback_redirect: toLoopbackRedirect(redirectUri),
13
13
  }),
14
14
  refresh: async (refreshToken) => requestRefresh(fetcher, options.apiBaseUrl, now, {
15
15
  refresh_token: refreshToken,
@@ -102,6 +102,23 @@ async function requestToken(fetcher, apiBaseUrl, now, body) {
102
102
  }
103
103
  return parseTokenPair(payload, now);
104
104
  }
105
+ /** Convert the local callback URL to the API-safe descriptor. */
106
+ function toLoopbackRedirect(redirectUri) {
107
+ const url = new URL(redirectUri);
108
+ const port = Number(url.port);
109
+ if (url.protocol !== "http:" ||
110
+ url.pathname !== "/callback" ||
111
+ (url.hostname !== "127.0.0.1" && url.hostname !== "localhost") ||
112
+ !Number.isInteger(port) ||
113
+ port < 1024 ||
114
+ port > 65535) {
115
+ throw new Error("Hyperstar CLI callback URL was invalid");
116
+ }
117
+ return {
118
+ host: url.hostname === "127.0.0.1" ? "ipv4" : "localhost",
119
+ port,
120
+ };
121
+ }
105
122
  /** Rotate a refresh token using the backend refresh route. */
106
123
  async function requestRefresh(fetcher, apiBaseUrl, now, body) {
107
124
  const response = await fetcher(buildApiUrl(apiBaseUrl, "/api/v1/cli-auth/refresh"), {
package/dist/config.d.ts CHANGED
@@ -12,7 +12,11 @@ export type CliConfig = {
12
12
  readonly accessTokenProvider: CliAccessTokenProvider;
13
13
  readonly workspaceSelection: CliWorkspaceSelection;
14
14
  };
15
- export type HyperstarMcpConfig = ServiceAccountConfig | CliConfig;
15
+ export type UnauthenticatedConfig = {
16
+ readonly authMode: "unauthenticated";
17
+ readonly apiBaseUrl: string;
18
+ };
19
+ export type HyperstarMcpConfig = ServiceAccountConfig | CliConfig | UnauthenticatedConfig;
16
20
  export type Env = Readonly<Record<string, string | undefined>>;
17
21
  export type CliWorkspaceSelection = {
18
22
  readonly getSelectedOrganizationId: () => Promise<string | undefined>;
@@ -20,6 +24,7 @@ export type CliWorkspaceSelection = {
20
24
  };
21
25
  export declare const DEFAULT_API_BASE_URL = "https://autopilot.hyper-star.org";
22
26
  export declare const DEFAULT_APP_BASE_URL = "https://app.hyper-star.org";
27
+ export declare const AUTH_CONFIGURATION_MESSAGE = "Run `hyperstar login` or set HYPERSTAR_API_KEY";
23
28
  export type LoadConfigOptions = {
24
29
  readonly env?: Env;
25
30
  readonly fetcher?: Fetcher | undefined;
@@ -27,6 +32,8 @@ export type LoadConfigOptions = {
27
32
  };
28
33
  /** Load MCP configuration from env or persisted CLI auth state. */
29
34
  export declare function loadConfig(options?: Env | LoadConfigOptions): HyperstarMcpConfig;
35
+ /** Load config for MCP server startup, allowing unauthenticated discovery. */
36
+ export declare function loadServerConfig(options?: Env | LoadConfigOptions): HyperstarMcpConfig;
30
37
  /** Create the local CLI workspace-selection port backed by auth state. */
31
38
  export declare function createCliWorkspaceSelection(store: CliAuthStateStore): CliWorkspaceSelection;
32
39
  /** Redact an API key or bearer token for error messages. */
package/dist/config.js CHANGED
@@ -2,6 +2,7 @@ import { createCliAuthClient, createStoredCliAccessTokenProvider, } from "./auth
2
2
  import { createFileCliAuthStateStore, } from "./auth/cli-auth-state.js";
3
3
  export const DEFAULT_API_BASE_URL = "https://autopilot.hyper-star.org";
4
4
  export const DEFAULT_APP_BASE_URL = "https://app.hyper-star.org";
5
+ export const AUTH_CONFIGURATION_MESSAGE = "Run `hyperstar login` or set HYPERSTAR_API_KEY";
5
6
  /** Load MCP configuration from env or persisted CLI auth state. */
6
7
  export function loadConfig(options = process.env) {
7
8
  const loadOptions = isLoadConfigOptions(options) ? options : { env: options };
@@ -24,7 +25,7 @@ export function loadConfig(options = process.env) {
24
25
  const store = createFileCliAuthStateStore({ env });
25
26
  const state = store.readSync();
26
27
  if (state === null) {
27
- throw new Error("Run `hyperstar login` or set HYPERSTAR_API_KEY");
28
+ throw new Error(AUTH_CONFIGURATION_MESSAGE);
28
29
  }
29
30
  const stateApiBaseUrl = normalizeBaseUrl(state.apiBaseUrl, "stored Hyperstar auth API base URL");
30
31
  const rawCliApiBaseUrl = env.HYPERSTAR_API_BASE_URL?.trim();
@@ -52,6 +53,28 @@ export function loadConfig(options = process.env) {
52
53
  workspaceSelection: createCliWorkspaceSelection(store),
53
54
  };
54
55
  }
56
+ /** Load config for MCP server startup, allowing unauthenticated discovery. */
57
+ export function loadServerConfig(options = process.env) {
58
+ try {
59
+ return loadConfig(options);
60
+ }
61
+ catch (error) {
62
+ if (!(error instanceof Error) ||
63
+ error.message !== AUTH_CONFIGURATION_MESSAGE) {
64
+ throw error;
65
+ }
66
+ const loadOptions = isLoadConfigOptions(options)
67
+ ? options
68
+ : { env: options };
69
+ const env = loadOptions.env ?? process.env;
70
+ const rawBaseUrl = env.HYPERSTAR_API_BASE_URL?.trim() ?? DEFAULT_API_BASE_URL;
71
+ const apiBaseUrl = normalizeBaseUrl(rawBaseUrl.length === 0 ? DEFAULT_API_BASE_URL : rawBaseUrl, "HYPERSTAR_API_BASE_URL");
72
+ return {
73
+ authMode: "unauthenticated",
74
+ apiBaseUrl,
75
+ };
76
+ }
77
+ }
55
78
  /** Create the local CLI workspace-selection port backed by auth state. */
56
79
  export function createCliWorkspaceSelection(store) {
57
80
  return {
@@ -60,7 +83,7 @@ export function createCliWorkspaceSelection(store) {
60
83
  await store.withExclusiveLock(async () => {
61
84
  const state = await store.read();
62
85
  if (state === null) {
63
- throw new Error("Run `hyperstar login` or set HYPERSTAR_API_KEY");
86
+ throw new Error(AUTH_CONFIGURATION_MESSAGE);
64
87
  }
65
88
  await store.write({
66
89
  ...state,
package/dist/http.js CHANGED
@@ -1,4 +1,4 @@
1
- import { redactSecret } from "./config.js";
1
+ import { AUTH_CONFIGURATION_MESSAGE, redactSecret, } from "./config.js";
2
2
  export class HyperstarApiError extends Error {
3
3
  status;
4
4
  detail;
@@ -24,6 +24,9 @@ export function createHyperstarClient(options) {
24
24
  };
25
25
  const secretsToRedact = [];
26
26
  let retryCliAccessToken;
27
+ if (options.config.authMode === "unauthenticated") {
28
+ throw new Error(AUTH_CONFIGURATION_MESSAGE);
29
+ }
27
30
  if (options.config.authMode === "service_account") {
28
31
  baseHeaders["x-hyperstar-api-key"] = options.config.apiKey;
29
32
  secretsToRedact.push(options.config.apiKey);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { loadConfig } from "./config.js";
3
+ import { loadServerConfig } from "./config.js";
4
4
  import { createHyperstarMcpServer } from "./server.js";
5
5
  const MCP_SERVER_USAGE = [
6
6
  "Usage: hyperstar-mcp [--help]",
@@ -8,7 +8,8 @@ const MCP_SERVER_USAGE = [
8
8
  "Starts the Hyperstar stdio MCP server.",
9
9
  "",
10
10
  "Authentication:",
11
- " Run `hyperstar login` first, or set HYPERSTAR_API_KEY in the MCP server environment.",
11
+ " Tools and guides are discoverable before auth.",
12
+ " Product API calls require `hyperstar login` or HYPERSTAR_API_KEY.",
12
13
  ].join("\n");
13
14
  /** Start the stdio MCP server process. */
14
15
  async function main() {
@@ -16,7 +17,7 @@ async function main() {
16
17
  process.stdout.write(`${MCP_SERVER_USAGE}\n`);
17
18
  return;
18
19
  }
19
- const config = loadConfig();
20
+ const config = loadServerConfig();
20
21
  const server = createHyperstarMcpServer(config);
21
22
  await server.connect(new StdioServerTransport());
22
23
  }
package/dist/server.js CHANGED
@@ -10,8 +10,12 @@ export function createHyperstarMcpServer(config) {
10
10
  version: packageVersion(),
11
11
  });
12
12
  registerHyperstarDiscovery(server, { apiBaseUrl: config.apiBaseUrl });
13
- registerHyperstarTools(server, createHyperstarClient({ config }), config.authMode === "cli"
14
- ? { workspaceSelection: config.workspaceSelection }
15
- : {});
13
+ const toolOptions = config.authMode === "cli"
14
+ ? {
15
+ authMode: config.authMode,
16
+ workspaceSelection: config.workspaceSelection,
17
+ }
18
+ : { authMode: config.authMode };
19
+ registerHyperstarTools(server, createHyperstarClient({ config }), toolOptions);
16
20
  return server;
17
21
  }
@@ -1,6 +1,7 @@
1
- import type { CliWorkspaceSelection } from "./config.js";
1
+ import type { CliWorkspaceSelection, HyperstarMcpConfig } from "./config.js";
2
2
  import type { HyperstarClient, JsonObject } from "./http.js";
3
3
  export type WorkspaceToolOptions = {
4
+ readonly authMode?: HyperstarMcpConfig["authMode"];
4
5
  readonly workspaceSelection?: CliWorkspaceSelection;
5
6
  };
6
7
  /** List workspaces through the Product API workspace route. */
@@ -1,5 +1,5 @@
1
1
  import { WorkspaceListResponseSchema } from "./schemas.js";
2
- import { workflowGuideJson } from "./workflow-content.js";
2
+ import { CLI_SAFETY_NOTES, CLI_WORKFLOW_SEQUENCE, WORKFLOW_DATA_FLOW_NOTES, workflowGuideJson, workflowGuideResourceDefinitions, } from "./workflow-content.js";
3
3
  /** List workspaces through the Product API workspace route. */
4
4
  export async function listAvailableWorkspaces(client, options = {}) {
5
5
  const payload = WorkspaceListResponseSchema.parse(await client.get("/v1/workspaces"));
@@ -33,6 +33,20 @@ export async function selectAvailableWorkspace(client, organizationId, options =
33
33
  }
34
34
  /** Return the recommended Hyperstar agent workflow and send-safety notes. */
35
35
  export function getHyperstarWorkflowGuide(options = {}) {
36
+ if (options.authMode === "unauthenticated") {
37
+ return {
38
+ sequence: [
39
+ "Configure auth first: run `hyperstar login` and select a workspace, or set HYPERSTAR_API_KEY.",
40
+ ...CLI_WORKFLOW_SEQUENCE,
41
+ ],
42
+ safety_notes: [
43
+ "MCP tools and guides are discoverable before auth, but Product API workflow calls require local browser login or HYPERSTAR_API_KEY.",
44
+ ...CLI_SAFETY_NOTES,
45
+ ],
46
+ data_flow_notes: [...WORKFLOW_DATA_FLOW_NOTES],
47
+ resources: workflowGuideResourceDefinitions.map((resource) => resource.uri),
48
+ };
49
+ }
36
50
  if (options.workspaceSelection === undefined) {
37
51
  return workflowGuideJson("service_account");
38
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperstar/mcp",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "description": "Local stdio MCP server and CLI for Hyperstar headless workflows.",
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from "node:child_process";
4
+ import { spawn } from "node:child_process";
4
5
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
5
6
  import { tmpdir } from "node:os";
6
7
  import { dirname, isAbsolute, resolve } from "node:path";
@@ -39,11 +40,25 @@ function logLocalPackageSpec(value) {
39
40
  );
40
41
  }
41
42
 
43
+ function smokeEnv() {
44
+ const env = {
45
+ ...process.env,
46
+ HOME: scratchDir,
47
+ XDG_CONFIG_HOME: resolve(scratchDir, "config"),
48
+ npm_config_cache: resolve(scratchDir, "npm-cache"),
49
+ npm_config_update_notifier: "false",
50
+ };
51
+ delete env.HYPERSTAR_API_KEY;
52
+ delete env.HYPERSTAR_API_BASE_URL;
53
+ delete env.HYPERSTAR_APP_BASE_URL;
54
+ return env;
55
+ }
56
+
42
57
  function runStep(step, command, args) {
43
58
  const result = spawnSync(command, args, {
44
59
  cwd: scratchDir,
45
60
  encoding: "utf8",
46
- env: process.env,
61
+ env: smokeEnv(),
47
62
  shell: false,
48
63
  });
49
64
  const stdout = result.stdout.trim();
@@ -79,6 +94,115 @@ function runStep(step, command, args) {
79
94
  );
80
95
  }
81
96
 
97
+ async function runMcpDiscoveryStep() {
98
+ const child = spawn(
99
+ "npm",
100
+ ["exec", "--yes", "--package", packageSpec, "--", "hyperstar-mcp"],
101
+ {
102
+ cwd: scratchDir,
103
+ env: smokeEnv(),
104
+ stdio: ["pipe", "pipe", "pipe"],
105
+ },
106
+ );
107
+ const responses = [];
108
+ let stdout = "";
109
+ let stderr = "";
110
+ child.stdout.setEncoding("utf8");
111
+ child.stderr.setEncoding("utf8");
112
+ child.stdout.on("data", (chunk) => {
113
+ stdout += chunk;
114
+ for (;;) {
115
+ const lineEnd = stdout.indexOf("\n");
116
+ if (lineEnd === -1) {
117
+ break;
118
+ }
119
+ const line = stdout.slice(0, lineEnd).trim();
120
+ stdout = stdout.slice(lineEnd + 1);
121
+ if (line.length === 0) {
122
+ continue;
123
+ }
124
+ responses.push(JSON.parse(line));
125
+ }
126
+ });
127
+ child.stderr.on("data", (chunk) => {
128
+ stderr += chunk;
129
+ });
130
+
131
+ const send = (message) => {
132
+ child.stdin.write(`${JSON.stringify(message)}\n`);
133
+ };
134
+ const waitFor = (id) =>
135
+ new Promise((resolveResponse, rejectResponse) => {
136
+ const started = Date.now();
137
+ const interval = setInterval(() => {
138
+ const response = responses.find((item) => item.id === id);
139
+ if (response !== undefined) {
140
+ clearInterval(interval);
141
+ resolveResponse(response);
142
+ return;
143
+ }
144
+ if (Date.now() - started > 15_000) {
145
+ clearInterval(interval);
146
+ rejectResponse(
147
+ new Error(`Timed out waiting for MCP response ${id}: ${stderr}`),
148
+ );
149
+ }
150
+ }, 50);
151
+ });
152
+
153
+ try {
154
+ send({
155
+ jsonrpc: "2.0",
156
+ id: 1,
157
+ method: "initialize",
158
+ params: {
159
+ protocolVersion: "2024-11-05",
160
+ capabilities: {},
161
+ clientInfo: { name: "hyperstar-npm-smoke", version: "0.0.0" },
162
+ },
163
+ });
164
+ await waitFor(1);
165
+ send({ jsonrpc: "2.0", method: "notifications/initialized", params: {} });
166
+ send({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
167
+ send({ jsonrpc: "2.0", id: 3, method: "resources/list", params: {} });
168
+ send({ jsonrpc: "2.0", id: 4, method: "prompts/list", params: {} });
169
+ const [tools, resources, prompts] = await Promise.all([
170
+ waitFor(2),
171
+ waitFor(3),
172
+ waitFor(4),
173
+ ]);
174
+ console.log(
175
+ JSON.stringify(
176
+ {
177
+ step: "hyperstar-mcp-discovery",
178
+ ok: true,
179
+ tools: tools.result.tools.map((tool) => tool.name).slice(0, 8),
180
+ resources: resources.result.resources.map((resource) => resource.uri),
181
+ prompts: prompts.result.prompts.map((prompt) => prompt.name),
182
+ },
183
+ null,
184
+ 2,
185
+ ),
186
+ );
187
+ } catch (error) {
188
+ console.error(
189
+ JSON.stringify(
190
+ {
191
+ step: "hyperstar-mcp-discovery",
192
+ ok: false,
193
+ error: error instanceof Error ? error.message : String(error),
194
+ stderr: stderr.trim(),
195
+ },
196
+ null,
197
+ 2,
198
+ ),
199
+ );
200
+ process.exitCode = 1;
201
+ } finally {
202
+ child.kill("SIGTERM");
203
+ }
204
+ }
205
+
82
206
  try {
83
207
  if (isLocalPackageSpec(packageSpec)) {
84
208
  logLocalPackageSpec(packageSpec);
@@ -108,6 +232,7 @@ try {
108
232
  "hyperstar-mcp",
109
233
  "--help",
110
234
  ]);
235
+ await runMcpDiscoveryStep();
111
236
  } finally {
112
237
  rmSync(scratchDir, { recursive: true, force: true });
113
238
  }