@hue-run/sdk 0.11.3 → 0.11.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.
- package/CLI.md +33 -18
- package/README.md +6 -5
- package/dist/cli/eval.js +6 -4
- package/dist/cli/login.js +2 -1
- package/dist/cli/mcp.d.ts +5 -4
- package/dist/cli/mcp.js +245 -31
- package/dist/environment/world.d.ts +4 -2
- package/dist/environment/world.js +13 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CLI.md
CHANGED
|
@@ -42,7 +42,7 @@ project manifest are refused because managers can update ancestor locks; Python
|
|
|
42
42
|
The generated `hue.setup.mjs` or `hue_setup.py` always selects `captureContent: false` /
|
|
43
43
|
`capture_content=False`. For a supported application, setup installs the dependency and adds the
|
|
44
44
|
managed import and middleware registration to the existing entrypoint; an unreferenced helper is
|
|
45
|
-
not a completed integration. TypeScript uses `@hue-run/sdk@0.11.
|
|
45
|
+
not a completed integration. TypeScript uses `@hue-run/sdk@0.11.4`, `@opentelemetry/api@1.9.1` and
|
|
46
46
|
`@opentelemetry/context-async-hooks@2.11.0`; Python setup uses its separately tested package pin.
|
|
47
47
|
Content capture requires an ordinary account-managed key and a later explicit application decision.
|
|
48
48
|
|
|
@@ -141,7 +141,7 @@ uses the observed predecessor for compare-and-swap. A conflict refreshes status
|
|
|
141
141
|
At most 32 distinct handoff IDs exist per installation; `SETUP_HANDOFF_LIMIT` is terminal. Replacing a
|
|
142
142
|
handoff does not create a trial, reset quota or rerun business work.
|
|
143
143
|
|
|
144
|
-
##
|
|
144
|
+
## Store project keys
|
|
145
145
|
|
|
146
146
|
`hue login` stores keys that a person creates in Hue; it never mints one, because setup
|
|
147
147
|
credentials are deliberately isolated from ordinary project keys. It prints the key settings page
|
|
@@ -185,14 +185,27 @@ for staging). Hue's Settings page does not show these snippets; this command kee
|
|
|
185
185
|
|
|
186
186
|
`--auth` chooses how the client authenticates:
|
|
187
187
|
|
|
188
|
-
- `key` (the default, except for `conductor`) references the `HUE_MCP_KEY` environment variable,
|
|
189
|
-
which `hue login` stores in `.env.hue`. A key value is never written.
|
|
190
188
|
- `oauth` configures the URL only. The client opens Hue in a browser, where you sign in and
|
|
191
189
|
approve the connection; no key is involved. A new sign-in connection has **Read and
|
|
192
|
-
write** access to every active project in the organization
|
|
193
|
-
agent first calls `list_projects` and passes the chosen project's id as `project_id`.
|
|
194
|
-
|
|
195
|
-
`
|
|
190
|
+
write** access to every active project in the organization the consent page names, within your
|
|
191
|
+
role, so an agent first calls `list_projects` and passes the chosen project's id as `project_id`.
|
|
192
|
+
The approval lasts 30 days; then sign in again. Sign-in covers the MCP server only: tracing and
|
|
193
|
+
`hue eval` still use a project key as `HUE_API_KEY`. It is available for `claude-code`, `codex`
|
|
194
|
+
and `conductor`. Hue does not yet accept Cursor's sign-in callback, so `cursor` and the other
|
|
195
|
+
clients use a key.
|
|
196
|
+
- `key` references the `HUE_MCP_KEY` environment variable, which `hue login` stores in
|
|
197
|
+
`.env.hue`. A key value is never written. Use a key for read-only access (a **Read** key, which
|
|
198
|
+
only `hue login --keys coding-agent` accepts), for a run that cannot complete a browser sign-in
|
|
199
|
+
(CI, `claude -p`), or for access that must outlast 30 days.
|
|
200
|
+
|
|
201
|
+
Without `--auth`, `claude-code`, `codex` and `conductor` sign in and the other clients use a key.
|
|
202
|
+
`--read-only` (or `read_only=true` in `--url`) selects a key. Otherwise `claude-code` and `codex`
|
|
203
|
+
keep the choice of the server's existing entry, in `./.mcp.json` or as `codex mcp get` reports it,
|
|
204
|
+
so a rerun that changes `--toolsets` keeps a key and its `read_only`; for a new entry they use a
|
|
205
|
+
key when `HUE_MCP_KEY` is set (only its presence is checked). When `codex` is on `PATH` but `codex mcp get` fails
|
|
206
|
+
for another reason, the command asks for `--auth` rather than guess which to keep. `conductor` keeps signing in, since
|
|
207
|
+
its agents read the login-shell environment Conductor captures rather than your terminal's. The
|
|
208
|
+
command names its choice on stderr; pass `--auth` to replace an entry as given.
|
|
196
209
|
|
|
197
210
|
`--project <id-or-slug>` pins either kind of connection to one project. The command adds or
|
|
198
211
|
replaces `?project=<value>` while keeping `toolsets`, and names the server `hue-<value>` so it can
|
|
@@ -203,10 +216,11 @@ The printed sign-in Codex TOML keeps its URL bare and sends `X-Hue-MCP-Project`
|
|
|
203
216
|
credential cannot reach it, Hue returns HTTP `404`; change or remove `--project`, because signing
|
|
204
217
|
in again does not change the credential's reach.
|
|
205
218
|
|
|
206
|
-
`--read-only` adds `?read_only=true` to
|
|
219
|
+
`--read-only` selects a key and adds `?read_only=true` to its URL, so Hue hides and rejects write
|
|
207
220
|
tools whatever the key allows. A project key, with or without it, keeps reaching its one project.
|
|
208
|
-
With `--auth oauth` it is refused
|
|
209
|
-
**Read**
|
|
221
|
+
With `--auth oauth` it is refused, as is any `read_only` in a sign-in `--url` (Hue reads only
|
|
222
|
+
`true` or `1`): a sign-in connection has **Read and write** access, so use a **Read** project key
|
|
223
|
+
for read-only access.
|
|
210
224
|
|
|
211
225
|
`--toolsets <names>` chooses the tools the connection lists: `all`; the profiles `observe`
|
|
212
226
|
(production reads), `author` (author evaluations) and `evaluate` (run an agent against a case);
|
|
@@ -233,9 +247,9 @@ reviewed cases `case_id`, environment versions `environment_version_id`, and tra
|
|
|
233
247
|
|
|
234
248
|
| Client | Key (`--auth key`) | Sign-in (`--auth oauth`) |
|
|
235
249
|
| --- | --- | --- |
|
|
236
|
-
| `claude-code` | Merges `mcpServers.hue` into `./.mcp.json`. `--scope user` runs `claude mcp add --transport http --scope user hue URL --header 'Authorization: Bearer ${HUE_MCP_KEY}'` when `claude` is on `PATH`, otherwise prints it. | Merges `mcpServers.hue` with only `type` and `url` into `./.mcp.json`; `--scope user` runs `claude mcp add --transport http --scope user hue URL`. Then
|
|
237
|
-
| `codex` | Runs `codex mcp add hue --url URL --bearer-token-env-var HUE_MCP_KEY`, or prints the `[mcp_servers.hue]` TOML block for `~/.codex/config.toml`. | Runs `codex mcp add hue --url URL`, which
|
|
238
|
-
| `conductor` | Runs the Claude Code user-scope and Codex key commands above. |
|
|
250
|
+
| `claude-code` | Merges `mcpServers.hue` into `./.mcp.json`. `--scope user` runs `claude mcp add --transport http --scope user hue URL --header 'Authorization: Bearer ${HUE_MCP_KEY}'` when `claude` is on `PATH`, otherwise prints it. | Merges `mcpServers.hue` with only `type` and `url` into `./.mcp.json`; `--scope user` runs `claude mcp add --transport http --scope user hue URL`. Then restart Claude Code, run `/mcp`, select `hue` and choose **Authenticate**. |
|
|
251
|
+
| `codex` | Runs `codex mcp add hue --url URL --bearer-token-env-var HUE_MCP_KEY`, or prints the `[mcp_servers.hue]` TOML block for `~/.codex/config.toml`. | Runs `codex mcp add hue --url URL`, which signs in at once and waits up to 5 minutes for the browser; without `--auth` and outside a terminal (CI, an agent's shell tool) the command prints it instead. A sign-in that does not finish leaves the server registered, and `codex mcp login hue` signs in. Then start a new Codex session. The printed TOML block keeps its URL bare and sends the selection as the `X-Hue-MCP-Toolsets` header. |
|
|
252
|
+
| `conductor` | Runs the Claude Code user-scope and Codex key commands above. | Runs `claude mcp add --transport http --scope user hue URL` and `codex mcp add hue --url URL` (printed outside a terminal, as for `codex`). Then use `hue`'s authentication action in Conductor's MCP status (the plug icon or `/mcp-status`). |
|
|
239
253
|
| `cursor` | Merges `mcpServers.hue` into `./.cursor/mcp.json` with `${env:HUE_MCP_KEY}`. | Refused. |
|
|
240
254
|
| `vscode` | Merges `servers.hue` and the `hue-mcp-key` password input into `./.vscode/mcp.json`. | Refused. |
|
|
241
255
|
| `windsurf` | Prints the `serverUrl` snippet for `~/.codeium/windsurf/mcp_config.json`; nothing is written to the home directory. | Refused. |
|
|
@@ -285,8 +299,9 @@ the same reason, and quote a URL with a query such as `?read_only=true`, whose `
|
|
|
285
299
|
After a key installation the command reminds you to export `HUE_MCP_KEY` in the shell that starts
|
|
286
300
|
the client (VS Code prompts for the key instead). A desktop app started from the Dock or a launcher
|
|
287
301
|
does not see that shell's variables, and Conductor's agents read the login-shell environment that
|
|
288
|
-
Conductor captures, so sign-in is the simpler choice there. After a sign-in installation it
|
|
289
|
-
the client
|
|
302
|
+
Conductor captures, so sign-in is the simpler choice there. After a sign-in installation it says
|
|
303
|
+
how the client loads the server (restart Claude Code, or a new Codex session) and names its
|
|
304
|
+
authentication action. Both end with the prompt that verifies the connection:
|
|
290
305
|
`Use the Hue MCP: call list_projects and confirm which project to inspect. Then call get_project_context and show that project's traces from the last 24 hours that need attention or have errors, with links; if there are none, show its 5 most recent traces. For an organization connection, pass the project's id or slug as project_id on each call after list_projects.` A pinned installation instead verifies `connection.pinned: true` and omits `project_id`. `list_projects` returns a project key's one project too. Exit codes:
|
|
291
306
|
`0` done or printed, `1` failed, `2` usage error.
|
|
292
307
|
|
|
@@ -454,7 +469,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
|
|
|
454
469
|
# Set project to an existing supported fixture; use the same directory on resume.
|
|
455
470
|
project=/absolute/path/to/supported-fixture
|
|
456
471
|
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
457
|
-
--archive .artifacts/typescript/hue-run-sdk-0.11.
|
|
472
|
+
--archive .artifacts/typescript/hue-run-sdk-0.11.4.tgz \
|
|
458
473
|
--origin https://STAGING_ORIGIN \
|
|
459
474
|
--project "$project" --command setup \
|
|
460
475
|
--evidence .context/setup-staging-before-claim.json
|
|
@@ -465,7 +480,7 @@ the private local handoff and finish the real browser claim, then reconcile the
|
|
|
465
480
|
|
|
466
481
|
```sh
|
|
467
482
|
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
468
|
-
--archive .artifacts/typescript/hue-run-sdk-0.11.
|
|
483
|
+
--archive .artifacts/typescript/hue-run-sdk-0.11.4.tgz \
|
|
469
484
|
--origin https://STAGING_ORIGIN \
|
|
470
485
|
--project "$project" --command claim \
|
|
471
486
|
--evidence .context/setup-staging-after-claim.json
|
package/README.md
CHANGED
|
@@ -82,11 +82,12 @@ account linkage; the original request evidence is retained and business work is
|
|
|
82
82
|
claim. Setup never enables content capture or creates a simulation, Hue Run, evaluation, source capture
|
|
83
83
|
or remote execution. See the [setup CLI contract](./CLI.md) for the supported shapes and release gates.
|
|
84
84
|
|
|
85
|
-
For an existing account, `hue
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
For an existing account, `hue mcp install --client claude-code` (or `codex`, `conductor`,
|
|
86
|
+
`cursor`, `vscode`, `windsurf`, `gemini`) writes the Hue MCP configuration. For Claude Code, Codex
|
|
87
|
+
and Conductor it holds only the URL, and you sign in with Hue in the client; for the other clients,
|
|
88
|
+
`--read-only` and `--auth key` it references `HUE_MCP_KEY` (VS Code prompts for the key).
|
|
89
|
+
`hue login` validates keys created in Hue Settings and stores them in `.env.hue` without printing
|
|
90
|
+
them. See [Store project keys](./CLI.md#store-project-keys) and
|
|
90
91
|
[Install the MCP for your coding agent](./CLI.md#install-the-mcp-for-your-coding-agent).
|
|
91
92
|
|
|
92
93
|
`checkConnection()` rejects with `HueConnectionError`: its fixed message is safe to log, `status`
|
package/dist/cli/eval.js
CHANGED
|
@@ -8,7 +8,7 @@ import { parseArgs } from "node:util";
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { createHue } from "../client.js";
|
|
10
10
|
import { createEnvironmentClient } from "../environment/client.js";
|
|
11
|
-
import { agentEnvironment, isHueControlPlaneCredential, stripHueControlPlaneCredentials, writeMcpConfig, } from "../environment/world.js";
|
|
11
|
+
import { agentEnvironment, isHueControlPlaneCredential, stripHueControlPlaneCredentials, withoutWorldVariables, writeMcpConfig, } from "../environment/world.js";
|
|
12
12
|
import { EvaluationClient, HueApiError } from "../evals/client.js";
|
|
13
13
|
import { TargetResult } from "../evals/types.js";
|
|
14
14
|
import { CheckpointIdentityError, CheckpointStore, checkpointPath } from "../evals/checkpoint.js";
|
|
@@ -583,11 +583,13 @@ function redacting(adapter) {
|
|
|
583
583
|
};
|
|
584
584
|
}
|
|
585
585
|
/** The parent environment an agent child starts from: without Hue control-plane credentials
|
|
586
|
-
* unless `--allow-hue-credentials` was passed. */
|
|
586
|
+
* unless `--allow-hue-credentials` was passed, and without any world variable. */
|
|
587
587
|
function parentEnvironment(allowHueCredentials) {
|
|
588
|
-
|
|
588
|
+
// A world variable left in this process's environment belongs to another world: only the
|
|
589
|
+
// current case's world sets them, whether or not the case has one.
|
|
590
|
+
return withoutWorldVariables(allowHueCredentials
|
|
589
591
|
? Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined))
|
|
590
|
-
: stripHueControlPlaneCredentials(process.env);
|
|
592
|
+
: stripHueControlPlaneCredentials(process.env));
|
|
591
593
|
}
|
|
592
594
|
function commandAdapter(command, timeoutSeconds, options) {
|
|
593
595
|
return async (inputs, context) => {
|
package/dist/cli/login.js
CHANGED
|
@@ -742,7 +742,8 @@ export async function runLoginCommand(argv, io = {}) {
|
|
|
742
742
|
out("Next steps:");
|
|
743
743
|
if (stored.includes("coding-agent"))
|
|
744
744
|
// `hue mcp install` defaults to production; a non-default origin needs its own endpoint.
|
|
745
|
-
|
|
745
|
+
// Without --auth it configures sign-in unless HUE_MCP_KEY is exported, so name the key stored.
|
|
746
|
+
out(` hue mcp install --client claude-code --auth key${mcpUrl === mcpUrlForOrigin(DEFAULT_ORIGIN) ? "" : ` --url ${mcpUrl}`}`);
|
|
746
747
|
if (stored.includes("evaluations"))
|
|
747
748
|
out(` hue eval --case "<name>" ./hue-agent.ts --env-file ${envDisplay}`);
|
|
748
749
|
return 0;
|
package/dist/cli/mcp.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `hue mcp install`: writes or prints the coding-agent configuration for Hue's MCP server. The
|
|
3
3
|
* shapes are those of Hue's published connection guide (https://docs.hue.run/agents/mcp-server),
|
|
4
|
-
* kept as a separate copy here.
|
|
5
|
-
* variable (or a VS Code password input)
|
|
6
|
-
*
|
|
4
|
+
* kept as a separate copy here. Sign-in configurations hold no credential; key configurations
|
|
5
|
+
* reference the `HUE_MCP_KEY` environment variable (or a VS Code password input). A key value is
|
|
6
|
+
* never written. Without `--auth`, a client that can sign in does unless a key is asked for or
|
|
7
|
+
* already set up (`defaultAuth`).
|
|
7
8
|
*/
|
|
8
9
|
/** Streams, environment and working directory for {@link runMcpCommand}; tests inject these. */
|
|
9
10
|
export interface McpCommandIo {
|
|
@@ -30,7 +31,7 @@ export declare const MCP_TOOLSETS: readonly ["all", "observe", "author", "evalua
|
|
|
30
31
|
export declare const TOOLSETS_HEADER = "X-Hue-MCP-Toolsets";
|
|
31
32
|
/** The header form of `?project=`, used by the sign-in Codex TOML to keep its URL bare. */
|
|
32
33
|
export declare const PROJECT_HEADER = "X-Hue-MCP-Project";
|
|
33
|
-
export declare const MCP_USAGE = "Usage: hue mcp install --client <claude-code|codex|conductor|cursor|vscode|windsurf|gemini>\n [--auth key|oauth] [--read-only] [--project ID-OR-SLUG]\n [--toolsets NAMES] [--url URL]\n [--scope project|user] [--dry-run] [--print]\n\nConfigure a coding agent to use the Hue MCP server. A
|
|
34
|
+
export declare const MCP_USAGE = "Usage: hue mcp install --client <claude-code|codex|conductor|cursor|vscode|windsurf|gemini>\n [--auth key|oauth] [--read-only] [--project ID-OR-SLUG]\n [--toolsets NAMES] [--url URL]\n [--scope project|user] [--dry-run] [--print]\n\nConfigure a coding agent to use the Hue MCP server. A sign-in configuration holds only the\nURL: the client opens Hue in a browser, where you approve access to the projects of one\norganization. A key configuration references the HUE_MCP_KEY environment variable; a key value\nis never written.\n\nOptions:\n --client NAME Coding agent to configure (required)\n --auth MODE oauth: URL only, sign in with Hue in the client (claude-code, codex and\n conductor; their default)\n key: reference HUE_MCP_KEY (the default for the other clients and with\n --read-only). Without --auth, claude-code and codex keep the choice of\n an existing entry, and use a key for a new one when HUE_MCP_KEY is set\n --read-only Add ?read_only=true to a key configuration's URL so write tools are\n hidden; selects a key without --auth. A sign-in connection has Read and\n write access; use a Read key for read-only\n --project VALUE Pin the connection to one project by id or slug. Its tools omit project_id,\n and the server is named hue-<value> so it can coexist with an unpinned hue\n --toolsets NAMES\n Tools to list, comma-separated: all; the observe, author or evaluate\n profiles; or project, traces, eval_sets, runs, judges, cases, runners,\n environments, intents or docs. Added to the URL as ?toolsets=.\n Defaults: all for claude-code, codex and conductor,\n which search their own tools; observe for cursor. Without a selection\n Hue lists the production reads and search_hue_tools, which reaches\n every other tool\n --url URL Hue MCP endpoint (default https://mcp.hue.run/mcp)\n --scope SCOPE claude-code only: project writes .mcp.json (default); user runs\n `claude mcp add --scope user`\n --dry-run Print the resulting file content or commands without writing or running\n --print Print the configuration snippet only\n -h, --help Show this help\n\nFiles: claude-code .mcp.json, cursor .cursor/mcp.json, vscode .vscode/mcp.json (relative to the\ncurrent directory). codex and gemini use their own CLI when it is on PATH; windsurf prints the\nsnippet for its user configuration file. conductor registers the server for its Claude Code\n(user scope) and Codex agents with their CLIs, printing the command for a CLI not on PATH.\ncodex mcp add signs in at once and waits up to 5 minutes for the browser, so without --auth\nand outside a terminal the command prints it instead of running it.";
|
|
34
35
|
/**
|
|
35
36
|
* A URL as a POSIX shell word for printed commands: `?` is a zsh glob and `&` ends a command, so a
|
|
36
37
|
* URL with a query (such as `?read_only=true`) is single-quoted. Executed commands pass argv.
|
package/dist/cli/mcp.js
CHANGED
|
@@ -82,18 +82,21 @@ export const MCP_USAGE = `Usage: hue mcp install --client <claude-code|codex|con
|
|
|
82
82
|
[--toolsets NAMES] [--url URL]
|
|
83
83
|
[--scope project|user] [--dry-run] [--print]
|
|
84
84
|
|
|
85
|
-
Configure a coding agent to use the Hue MCP server. A
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
Configure a coding agent to use the Hue MCP server. A sign-in configuration holds only the
|
|
86
|
+
URL: the client opens Hue in a browser, where you approve access to the projects of one
|
|
87
|
+
organization. A key configuration references the ${ENV_VAR} environment variable; a key value
|
|
88
|
+
is never written.
|
|
89
89
|
|
|
90
90
|
Options:
|
|
91
91
|
--client NAME Coding agent to configure (required)
|
|
92
|
-
--auth MODE
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
--auth MODE oauth: URL only, sign in with Hue in the client (claude-code, codex and
|
|
93
|
+
conductor; their default)
|
|
94
|
+
key: reference ${ENV_VAR} (the default for the other clients and with
|
|
95
|
+
--read-only). Without --auth, claude-code and codex keep the choice of
|
|
96
|
+
an existing entry, and use a key for a new one when ${ENV_VAR} is set
|
|
97
|
+
--read-only Add ?read_only=true to a key configuration's URL so write tools are
|
|
98
|
+
hidden; selects a key without --auth. A sign-in connection has Read and
|
|
99
|
+
write access; use a Read key for read-only
|
|
97
100
|
--project VALUE Pin the connection to one project by id or slug. Its tools omit project_id,
|
|
98
101
|
and the server is named hue-<value> so it can coexist with an unpinned hue
|
|
99
102
|
--toolsets NAMES
|
|
@@ -114,7 +117,9 @@ Options:
|
|
|
114
117
|
Files: claude-code .mcp.json, cursor .cursor/mcp.json, vscode .vscode/mcp.json (relative to the
|
|
115
118
|
current directory). codex and gemini use their own CLI when it is on PATH; windsurf prints the
|
|
116
119
|
snippet for its user configuration file. conductor registers the server for its Claude Code
|
|
117
|
-
(user scope) and Codex agents with their CLIs, printing the command for a CLI not on PATH
|
|
120
|
+
(user scope) and Codex agents with their CLIs, printing the command for a CLI not on PATH.
|
|
121
|
+
codex mcp add signs in at once and waits up to 5 minutes for the browser, so without --auth
|
|
122
|
+
and outside a terminal the command prints it instead of running it.`;
|
|
118
123
|
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
119
124
|
/**
|
|
120
125
|
* A URL as a POSIX shell word for printed commands: `?` is a zsh glob and `&` ends a command, so a
|
|
@@ -281,6 +286,10 @@ export function readOnlyMcpUrl(url) {
|
|
|
281
286
|
parsed.searchParams.set("read_only", "true");
|
|
282
287
|
return parsed.href;
|
|
283
288
|
}
|
|
289
|
+
/** Whether Hue treats the URL as read-only: it honors only `read_only=true` or `read_only=1`. */
|
|
290
|
+
function isReadOnlyUrl(url) {
|
|
291
|
+
return ["true", "1"].includes(new URL(url).searchParams.get("read_only") ?? "");
|
|
292
|
+
}
|
|
284
293
|
class ConfigError extends Error {
|
|
285
294
|
constructor(message) {
|
|
286
295
|
super(message);
|
|
@@ -501,9 +510,9 @@ function redactUrlPath(value) {
|
|
|
501
510
|
const PARAMETER_NAME = /^[A-Za-z_][\w.-]{0,31}$/u;
|
|
502
511
|
/**
|
|
503
512
|
* The URL this command writes, as its output shows it: the selections it validated (`toolsets`,
|
|
504
|
-
* `project`, and `read_only` when true or
|
|
505
|
-
* path segment, becomes `[redacted]`. `parseMcpUrl` has already refused userinfo and a
|
|
506
|
-
* Commands printed for a person to run keep the URL as given.
|
|
513
|
+
* `project`, and `read_only` when true, false, 1 or 0) are kept; any other query value, and a
|
|
514
|
+
* token-like path segment, becomes `[redacted]`. `parseMcpUrl` has already refused userinfo and a
|
|
515
|
+
* fragment. Commands printed for a person to run keep the URL as given.
|
|
507
516
|
*/
|
|
508
517
|
function displayMcpUrl(url) {
|
|
509
518
|
const parsed = new URL(url);
|
|
@@ -515,7 +524,7 @@ function displayMcpUrl(url) {
|
|
|
515
524
|
const [name = "", value = ""] = pair.split("=", 2);
|
|
516
525
|
const selection = name === "toolsets" ||
|
|
517
526
|
name === "project" ||
|
|
518
|
-
(name === "read_only" && /^(?:true|false)$/u.test(value));
|
|
527
|
+
(name === "read_only" && /^(?:true|false|1|0)$/u.test(value));
|
|
519
528
|
if (selection)
|
|
520
529
|
return pair;
|
|
521
530
|
return PARAMETER_NAME.test(name) &&
|
|
@@ -573,6 +582,8 @@ function redactConfig(root, serversKey, url, shownUrl) {
|
|
|
573
582
|
};
|
|
574
583
|
return redact(root, false, 0, false);
|
|
575
584
|
}
|
|
585
|
+
/** Whether a person is likely watching: login.ts opens a browser on the same test. */
|
|
586
|
+
const isTerminal = (stream) => stream.isTTY === true;
|
|
576
587
|
async function findExecutable(name, env) {
|
|
577
588
|
const searchPath = env.PATH ?? env.Path ?? "";
|
|
578
589
|
const extensions = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
|
|
@@ -618,6 +629,102 @@ function runClientCli(executable, args, options) {
|
|
|
618
629
|
}
|
|
619
630
|
});
|
|
620
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* A client CLI's exit code and output within ten seconds, none of it shown; null when it could not
|
|
634
|
+
* start or ran out of time.
|
|
635
|
+
*/
|
|
636
|
+
function readClientCli(executable, args, options) {
|
|
637
|
+
return new Promise((resolveRun) => {
|
|
638
|
+
try {
|
|
639
|
+
const child = spawn(executable, args, { ...options, stdio: ["ignore", "pipe", "pipe"] });
|
|
640
|
+
const collect = () => {
|
|
641
|
+
const chunks = [];
|
|
642
|
+
let size = 0;
|
|
643
|
+
return {
|
|
644
|
+
add: (chunk) => {
|
|
645
|
+
size += chunk.length;
|
|
646
|
+
if (size <= MAX_CONFIG_BYTES)
|
|
647
|
+
chunks.push(chunk);
|
|
648
|
+
},
|
|
649
|
+
text: () => (size <= MAX_CONFIG_BYTES ? Buffer.concat(chunks).toString() : null),
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
const out = collect();
|
|
653
|
+
const err = collect();
|
|
654
|
+
let timedOut = false;
|
|
655
|
+
const timer = setTimeout(() => {
|
|
656
|
+
timedOut = true;
|
|
657
|
+
child.kill();
|
|
658
|
+
}, 10_000);
|
|
659
|
+
child.stdout.on("data", out.add);
|
|
660
|
+
child.stderr.on("data", err.add);
|
|
661
|
+
child.once("error", () => {
|
|
662
|
+
clearTimeout(timer);
|
|
663
|
+
resolveRun(null);
|
|
664
|
+
});
|
|
665
|
+
child.once("close", (code) => {
|
|
666
|
+
clearTimeout(timer);
|
|
667
|
+
const stdout = out.text();
|
|
668
|
+
const stderr = err.text();
|
|
669
|
+
resolveRun(timedOut || code === null || stdout === null || stderr === null
|
|
670
|
+
? null
|
|
671
|
+
: { code, stdout, stderr });
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
resolveRun(null);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
const hasAuthorization = (headers) => isRecord(headers) && Object.keys(headers).some((name) => name.toLowerCase() === "authorization");
|
|
680
|
+
/** The server's HTTP entry in `./.mcp.json`; an unreadable file is reported by the write. */
|
|
681
|
+
async function readClaudeProjectEntry(cwd, serverName) {
|
|
682
|
+
let config;
|
|
683
|
+
try {
|
|
684
|
+
({ config } = await readJsonConfig(resolve(cwd, ".mcp.json"), ".mcp.json"));
|
|
685
|
+
}
|
|
686
|
+
catch {
|
|
687
|
+
return undefined;
|
|
688
|
+
}
|
|
689
|
+
const servers = isRecord(config) ? config.mcpServers : undefined;
|
|
690
|
+
const entry = isRecord(servers) ? servers[serverName] : undefined;
|
|
691
|
+
if (!isRecord(entry) || typeof entry.url !== "string" || !parseMcpUrl(entry.url))
|
|
692
|
+
return undefined;
|
|
693
|
+
const auth = hasAuthorization(entry.headers) ? "key" : "oauth";
|
|
694
|
+
return { url: entry.url, auth, source: ".mcp.json" };
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* The server's entry in Codex's configuration, as `codex mcp get --json` reports it: `found` with
|
|
698
|
+
* its exact output, `none` when Codex says it has no such server (or holds one that is not an HTTP
|
|
699
|
+
* Hue entry), or `unknown` when Codex could not be asked or answered otherwise, which is never
|
|
700
|
+
* taken for `none`.
|
|
701
|
+
*/
|
|
702
|
+
async function readCodexEntry(executable, serverName, options) {
|
|
703
|
+
const result = await readClientCli(executable, ["mcp", "get", serverName, "--json"], options);
|
|
704
|
+
if (!result)
|
|
705
|
+
return { state: "unknown" };
|
|
706
|
+
if (result.code !== 0)
|
|
707
|
+
return result.stderr.includes(`No MCP server named '${serverName}' found`)
|
|
708
|
+
? { state: "none" }
|
|
709
|
+
: { state: "unknown" };
|
|
710
|
+
let transport;
|
|
711
|
+
try {
|
|
712
|
+
transport = JSON.parse(result.stdout).transport;
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
return { state: "unknown" };
|
|
716
|
+
}
|
|
717
|
+
if (!isRecord(transport) || typeof transport.url !== "string" || !parseMcpUrl(transport.url))
|
|
718
|
+
return { state: "none" };
|
|
719
|
+
const key = Boolean(transport.bearer_token_env_var) ||
|
|
720
|
+
hasAuthorization(transport.http_headers) ||
|
|
721
|
+
hasAuthorization(transport.env_http_headers);
|
|
722
|
+
return {
|
|
723
|
+
state: "found",
|
|
724
|
+
entry: { url: transport.url, auth: key ? "key" : "oauth", source: "Codex's configuration" },
|
|
725
|
+
raw: result.stdout,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
621
728
|
/** Validates the MCP endpoint: HTTPS, or HTTP for loopback test servers; no credentials or hash. */
|
|
622
729
|
export function parseMcpUrl(value) {
|
|
623
730
|
let url;
|
|
@@ -732,20 +839,69 @@ function planFor(client, auth, scope, url, serverName) {
|
|
|
732
839
|
}
|
|
733
840
|
}
|
|
734
841
|
}
|
|
735
|
-
|
|
842
|
+
/**
|
|
843
|
+
* The authentication used without `--auth`, and the line saying why. A client that can sign in
|
|
844
|
+
* does, unless a key is asked for: read-only access (`--read-only`, or `read_only=true` in
|
|
845
|
+
* `--url`), which a sign-in connection does not have. For Claude Code and Codex, an existing entry
|
|
846
|
+
* keeps its choice, so rerunning the command, to change `--toolsets` for instance, never swaps a
|
|
847
|
+
* working setup; a key keeps its `read_only`. A new entry uses a key when `HUE_MCP_KEY` is set in
|
|
848
|
+
* `env`; only the variable's presence is checked. Conductor signed in by default before and keeps
|
|
849
|
+
* doing so: its agents read the login-shell environment Conductor captures, not this shell's. The
|
|
850
|
+
* other clients only take a key, so they get no line.
|
|
851
|
+
*/
|
|
852
|
+
function defaultAuth(client, readOnly, existing, env) {
|
|
853
|
+
if (!OAUTH_CLIENTS.includes(client))
|
|
854
|
+
return { auth: "key" };
|
|
855
|
+
if (readOnly)
|
|
856
|
+
return {
|
|
857
|
+
auth: "key",
|
|
858
|
+
note: `Using a key: ${readOnly} needs one, since a sign-in connection has Read and write access.`,
|
|
859
|
+
};
|
|
860
|
+
if (existing?.auth === "key") {
|
|
861
|
+
const keepReadOnly = isReadOnlyUrl(existing.url);
|
|
862
|
+
return {
|
|
863
|
+
auth: "key",
|
|
864
|
+
keepReadOnly,
|
|
865
|
+
note: keepReadOnly
|
|
866
|
+
? `Keeping a read-only key, as the existing entry in ${existing.source} has; pass --auth key to drop read_only, or --auth oauth to sign in with Hue instead.`
|
|
867
|
+
: `Keeping a key, as the existing entry in ${existing.source} has; pass --auth oauth to sign in with Hue instead.`,
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
if (existing)
|
|
871
|
+
return {
|
|
872
|
+
auth: "oauth",
|
|
873
|
+
note: `Keeping sign-in with Hue, as the existing entry in ${existing.source} has; pass --auth key to use a key instead.`,
|
|
874
|
+
};
|
|
875
|
+
if (client !== "conductor" && env[ENV_VAR])
|
|
876
|
+
return {
|
|
877
|
+
auth: "key",
|
|
878
|
+
note: `Using a key (${ENV_VAR} is set); pass --auth oauth to sign in with Hue instead.`,
|
|
879
|
+
};
|
|
880
|
+
return {
|
|
881
|
+
auth: "oauth",
|
|
882
|
+
note: `Using sign-in with Hue (${client === "conductor" ? "Conductor's default" : `${ENV_VAR} is not set`}); pass --auth key to use a key instead.`,
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function nextSteps(client, auth, serverName, project, scope, readOnly) {
|
|
736
886
|
const label = CLIENT_LABELS[client];
|
|
737
887
|
const approve = project
|
|
738
888
|
? `Sign in to Hue and approve the connection. It acts only on the pinned project ${project}, within your role; for read-only access, use a Read project key with --auth key.`
|
|
739
|
-
: "Sign in to Hue and approve the connection. It can read and write every active project in the organization
|
|
889
|
+
: "Sign in to Hue and approve the connection. It can read and write every active project in the organization the consent page names, within your role; for read-only access, use a Read project key with --auth key.";
|
|
890
|
+
// A read-only configuration needs a key: plain hue login refuses a Read key.
|
|
891
|
+
const stored = readOnly
|
|
892
|
+
? "hue login --keys coding-agent stores it in .env.hue and accepts a Read key"
|
|
893
|
+
: "hue login stores it in .env.hue";
|
|
740
894
|
let lines;
|
|
741
895
|
if (auth === "oauth")
|
|
742
896
|
lines =
|
|
743
897
|
client === "claude-code"
|
|
744
|
-
? [
|
|
898
|
+
? [
|
|
899
|
+
`Restart Claude Code (for example claude --continue)${scope === "project" ? ` and approve ${serverName} from .mcp.json` : ""}, then run /mcp, select ${serverName} and choose Authenticate. ${approve}`,
|
|
900
|
+
]
|
|
745
901
|
: client === "codex"
|
|
746
902
|
? [
|
|
747
903
|
`If Codex did not open Hue in your browser, run: codex mcp login ${serverName}`,
|
|
748
|
-
approve,
|
|
904
|
+
`${approve} Then start a new Codex session, which loads ${serverName}.`,
|
|
749
905
|
]
|
|
750
906
|
: [
|
|
751
907
|
`In Conductor, open MCP status from the plug icon or /mcp-status, refresh, and use ${serverName}'s authentication action. ${approve}`,
|
|
@@ -757,13 +913,13 @@ function nextSteps(client, auth, serverName, project) {
|
|
|
757
913
|
];
|
|
758
914
|
else if (client === "conductor")
|
|
759
915
|
lines = [
|
|
760
|
-
`Conductor agents read ${ENV_VAR} from the login-shell environment Conductor captures, so export it there, not only in a terminal;
|
|
916
|
+
`Conductor agents read ${ENV_VAR} from the login-shell environment Conductor captures, so export it there, not only in a terminal; ${stored}.${readOnly ? "" : " Sign-in (--auth oauth) needs no variable."}`,
|
|
761
917
|
];
|
|
762
918
|
else
|
|
763
919
|
lines = [
|
|
764
|
-
`Export ${ENV_VAR} in the shell that starts ${label};
|
|
920
|
+
`Export ${ENV_VAR} in the shell that starts ${label}; ${stored}:`,
|
|
765
921
|
" set -a; . ./.env.hue; set +a",
|
|
766
|
-
`An app started from the Dock or a launcher does not see that shell's variables; start ${label} from the shell${OAUTH_CLIENTS.includes(client) ? ", or sign in instead with --auth oauth" : ""}.`,
|
|
922
|
+
`An app started from the Dock or a launcher does not see that shell's variables; start ${label} from the shell${OAUTH_CLIENTS.includes(client) && !readOnly ? ", or sign in instead with --auth oauth" : ""}.`,
|
|
767
923
|
];
|
|
768
924
|
// `codex mcp add` has no option for it, so a registered server gets the line by hand.
|
|
769
925
|
if (client === "codex" || client === "conductor")
|
|
@@ -809,10 +965,10 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
809
965
|
if (!client || !CLIENT_IDS.includes(client))
|
|
810
966
|
return fail(`${client ? `Unknown client: ${client}.` : "--client is required."} Choose one of ${CLIENT_IDS.join(", ")}.\n\n${MCP_USAGE}`, 2);
|
|
811
967
|
const clientId = client;
|
|
812
|
-
const
|
|
813
|
-
if (
|
|
968
|
+
const explicitAuth = parsed.values.auth;
|
|
969
|
+
if (explicitAuth !== undefined && explicitAuth !== "key" && explicitAuth !== "oauth")
|
|
814
970
|
return fail(`--auth must be key or oauth.\n\n${MCP_USAGE}`, 2);
|
|
815
|
-
if (
|
|
971
|
+
if (explicitAuth === "oauth" && !OAUTH_CLIENTS.includes(clientId))
|
|
816
972
|
return fail(clientId === "cursor"
|
|
817
973
|
? "--auth oauth is not available for cursor: Hue does not yet accept Cursor's sign-in callback. Use a key (the default), or sign in from claude-code or codex."
|
|
818
974
|
: `--auth oauth is available for ${OAUTH_CLIENTS.join(", ")}. Use a key (the default) for ${clientId}.`, 2);
|
|
@@ -824,12 +980,6 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
824
980
|
let url = parseMcpUrl(parsed.values.url ?? DEFAULT_MCP_URL);
|
|
825
981
|
if (!url)
|
|
826
982
|
return fail("--url must be an HTTPS URL such as https://mcp.hue.run/mcp (plain HTTP is accepted for loopback test servers only).", 2);
|
|
827
|
-
// A sign-in connection's access is chosen when it is approved; read_only is not part of it.
|
|
828
|
-
if (auth === "oauth" &&
|
|
829
|
-
(parsed.values["read-only"] || new URL(url).searchParams.has("read_only")))
|
|
830
|
-
return fail("A sign-in connection has Read and write access and cannot be made read-only by its URL. For read-only access, use a Read project key (--auth key), or add --read-only to a key configuration.", 2);
|
|
831
|
-
if (parsed.values["read-only"])
|
|
832
|
-
url = readOnlyMcpUrl(url);
|
|
833
983
|
// --project replaces a pin already in --url. Without it, validate and keep the URL's pin.
|
|
834
984
|
const urlProjects = new URL(url).searchParams.getAll("project");
|
|
835
985
|
let projectValue = parsed.values.project;
|
|
@@ -848,6 +998,40 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
848
998
|
project = selected.project;
|
|
849
999
|
serverName = selected.serverName;
|
|
850
1000
|
}
|
|
1001
|
+
const urlReadOnly = new URL(url).searchParams.has("read_only");
|
|
1002
|
+
const readOnly = parsed.values["read-only"]
|
|
1003
|
+
? "--read-only"
|
|
1004
|
+
: isReadOnlyUrl(url)
|
|
1005
|
+
? "read_only in --url"
|
|
1006
|
+
: undefined;
|
|
1007
|
+
// Without --auth or read_only, an existing entry keeps its choice. A user-scope `claude mcp add`
|
|
1008
|
+
// refuses a name it already has, and Conductor keeps its default, so only these two are read.
|
|
1009
|
+
let existing;
|
|
1010
|
+
if (explicitAuth === undefined && !parsed.values["read-only"] && !urlReadOnly) {
|
|
1011
|
+
if (clientId === "claude-code" && scope === "project")
|
|
1012
|
+
existing = await readClaudeProjectEntry(cwd, serverName);
|
|
1013
|
+
else if (clientId === "codex") {
|
|
1014
|
+
// Without codex on PATH nothing is run, so nothing Codex holds is replaced. With it, an
|
|
1015
|
+
// entry that cannot be read may hold either a key or a sign-in, so the command does not
|
|
1016
|
+
// guess which to keep.
|
|
1017
|
+
const codex = await findExecutable("codex", env);
|
|
1018
|
+
const lookup = codex ? await readCodexEntry(codex, serverName, { cwd, env }) : undefined;
|
|
1019
|
+
if (lookup?.state === "unknown")
|
|
1020
|
+
return fail(`Could not read Codex's "${serverName}" entry (codex mcp get failed), so the command will not guess whether to keep a key or sign-in. Pass --auth oauth or --auth key.`, 2);
|
|
1021
|
+
if (lookup?.state === "found")
|
|
1022
|
+
existing = lookup.entry;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
const { auth, keepReadOnly, note } = explicitAuth === undefined
|
|
1026
|
+
? defaultAuth(clientId, readOnly, existing, env)
|
|
1027
|
+
: { auth: explicitAuth };
|
|
1028
|
+
// A sign-in connection's access is chosen when it is approved; read_only is not part of it.
|
|
1029
|
+
if (auth === "oauth" && readOnly)
|
|
1030
|
+
return fail("A sign-in connection has Read and write access and cannot be made read-only by its URL. For read-only access, use a Read project key (--auth key), or add --read-only to a key configuration.", 2);
|
|
1031
|
+
if (auth === "oauth" && urlReadOnly)
|
|
1032
|
+
return fail("Remove read_only from --url: a sign-in URL does not carry it, since a sign-in connection has Read and write access, and Hue reads only read_only=true or 1. For read-only access, use a Read project key (--auth key).", 2);
|
|
1033
|
+
if (parsed.values["read-only"] || keepReadOnly)
|
|
1034
|
+
url = readOnlyMcpUrl(url);
|
|
851
1035
|
url = projectMcpUrl(url, project);
|
|
852
1036
|
// --toolsets wins over a selection already in --url, which wins over the client's default.
|
|
853
1037
|
// Both sources are validated: Hue ignores an unknown name and would list its default.
|
|
@@ -860,6 +1044,9 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
860
1044
|
toolsets = selected.toolsets;
|
|
861
1045
|
}
|
|
862
1046
|
url = toolsetsMcpUrl(url, toolsets);
|
|
1047
|
+
// On stderr, so --print and --dry-run output stays as it was.
|
|
1048
|
+
if (note)
|
|
1049
|
+
stderr.write(`${note}\n`);
|
|
863
1050
|
const shownUrl = displayMcpUrl(url);
|
|
864
1051
|
// What the command reports doing; a command a person is to run is printed as given.
|
|
865
1052
|
const shownCommand = (command) => command.display.replaceAll(shellWord(url), () => shellWord(shownUrl));
|
|
@@ -868,7 +1055,7 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
868
1055
|
stdout.write(plan.snippet);
|
|
869
1056
|
return 0;
|
|
870
1057
|
}
|
|
871
|
-
const steps = nextSteps(clientId, auth, serverName, project);
|
|
1058
|
+
const steps = nextSteps(clientId, auth, serverName, project, scope, isReadOnlyUrl(url));
|
|
872
1059
|
if (plan.kind === "manual") {
|
|
873
1060
|
out(plan.hint);
|
|
874
1061
|
stdout.write(plan.snippet);
|
|
@@ -883,6 +1070,9 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
883
1070
|
return 0;
|
|
884
1071
|
}
|
|
885
1072
|
let failed = false;
|
|
1073
|
+
// `codex mcp add` signs in at once and waits up to five minutes for the browser. Outside a
|
|
1074
|
+
// terminal (CI, an agent's shell tool) nobody may finish that, so a default sign-in prints it.
|
|
1075
|
+
const deferCodex = auth === "oauth" && explicitAuth === undefined && !isTerminal(stdout);
|
|
886
1076
|
for (const command of plan.commands) {
|
|
887
1077
|
const executable = await findExecutable(command.executable, env);
|
|
888
1078
|
if (!executable) {
|
|
@@ -891,12 +1081,36 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
891
1081
|
out(command.display);
|
|
892
1082
|
continue;
|
|
893
1083
|
}
|
|
1084
|
+
if (deferCodex && command.executable === "codex") {
|
|
1085
|
+
out("Not running codex mcp add: it signs in at once and waits up to 5 minutes for the browser, and no terminal is attached. Run it where you can sign in, or pass --auth oauth to run it here and wait:");
|
|
1086
|
+
out(command.display);
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
// What Codex held before, so a failed add is not mistaken for this run's registration.
|
|
1090
|
+
const codexSignIn = auth === "oauth" && command.executable === "codex";
|
|
1091
|
+
const before = codexSignIn
|
|
1092
|
+
? await readCodexEntry(executable, serverName, { cwd, env })
|
|
1093
|
+
: undefined;
|
|
894
1094
|
out(`Running: ${shownCommand(command)}`);
|
|
895
1095
|
const code = await runClientCli(executable, command.args, { cwd, env, stdout, stderr });
|
|
896
1096
|
if (code === 0) {
|
|
897
1097
|
out(`Registered the "${serverName}" MCP server (${shownUrl}) with ${command.label}.`);
|
|
898
1098
|
continue;
|
|
899
1099
|
}
|
|
1100
|
+
// Codex writes its entry before signing in, so a sign-in that timed out or was denied
|
|
1101
|
+
// leaves the server registered; `codex mcp login` signs in again. Only an entry this run
|
|
1102
|
+
// wrote counts: one Codex already held unchanged proves nothing about this add.
|
|
1103
|
+
if (code !== null && codexSignIn) {
|
|
1104
|
+
const after = await readCodexEntry(executable, serverName, { cwd, env });
|
|
1105
|
+
const wrote = after.state === "found" &&
|
|
1106
|
+
after.entry.auth === "oauth" &&
|
|
1107
|
+
after.entry.url === url &&
|
|
1108
|
+
(before?.state === "none" || (before?.state === "found" && before.raw !== after.raw));
|
|
1109
|
+
if (wrote) {
|
|
1110
|
+
out(`Registered the "${serverName}" MCP server (${shownUrl}) with Codex, but its sign-in did not finish. Run codex mcp login ${serverName} to sign in.`);
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
900
1114
|
failed = true;
|
|
901
1115
|
stderr.write(`${command.executable} ${code === null ? "could not be started" : `exited with code ${code}`}. Run this command yourself:\n${command.display}\n`);
|
|
902
1116
|
}
|
|
@@ -23,10 +23,12 @@ export interface AgentEnvironmentOptions {
|
|
|
23
23
|
* default; the canonical carriers are the world's own `env`. */
|
|
24
24
|
legacyMcpVariables?: boolean;
|
|
25
25
|
}
|
|
26
|
+
/** `environment` without any variable a world or its server owns. */
|
|
27
|
+
export declare function withoutWorldVariables(environment: Record<string, string>): Record<string, string>;
|
|
26
28
|
/**
|
|
27
29
|
* The environment for an agent child process running one case (the coordination briefs'
|
|
28
|
-
* recommended policy): the parent's variables minus Hue control-plane credentials
|
|
29
|
-
* world
|
|
30
|
+
* recommended policy): the parent's variables minus Hue control-plane credentials and any
|
|
31
|
+
* world-scoped variable, then the world's carriers. Nothing here is logged.
|
|
30
32
|
*/
|
|
31
33
|
export declare function agentEnvironment(world: WorldHandoff, options?: AgentEnvironmentOptions): Record<string, string>;
|
|
32
34
|
/** The `{ url, token, expiresAt }` shape the `hue_sim_` capability had, projected from the
|
|
@@ -53,16 +53,25 @@ export function stripHueControlPlaneCredentials(parent) {
|
|
|
53
53
|
}
|
|
54
54
|
return child;
|
|
55
55
|
}
|
|
56
|
+
/** Variables a world or its server owns: a parent's value belongs to another world (a runner
|
|
57
|
+
* started inside a case, a shell left from an earlier run) or to the server (a signing key), and
|
|
58
|
+
* would send this world's token to a mirror this world does not have. The whole `HUE_WORLD_`
|
|
59
|
+
* prefix is the world's. */
|
|
60
|
+
const WORLD_SCOPED = /^(?:HUE_WORLD_[A-Z0-9_]+|HUE_MCP_(?:CONFIG|URL|TOKEN|EXPIRES_AT)|HUE_SIM_[A-Z0-9_]+_(?:URL|ALIAS))$/;
|
|
61
|
+
/** `environment` without any variable a world or its server owns. */
|
|
62
|
+
export function withoutWorldVariables(environment) {
|
|
63
|
+
return Object.fromEntries(Object.entries(environment).filter(([name]) => !WORLD_SCOPED.test(name)));
|
|
64
|
+
}
|
|
56
65
|
/**
|
|
57
66
|
* The environment for an agent child process running one case (the coordination briefs'
|
|
58
|
-
* recommended policy): the parent's variables minus Hue control-plane credentials
|
|
59
|
-
* world
|
|
67
|
+
* recommended policy): the parent's variables minus Hue control-plane credentials and any
|
|
68
|
+
* world-scoped variable, then the world's carriers. Nothing here is logged.
|
|
60
69
|
*/
|
|
61
70
|
export function agentEnvironment(world, options = {}) {
|
|
62
71
|
const parent = options.parent ?? process.env;
|
|
63
|
-
const child = options.includeHueCredentials
|
|
72
|
+
const child = withoutWorldVariables(options.includeHueCredentials
|
|
64
73
|
? Object.fromEntries(Object.entries(parent).filter((entry) => entry[1] !== undefined))
|
|
65
|
-
: stripHueControlPlaneCredentials(parent);
|
|
74
|
+
: stripHueControlPlaneCredentials(parent));
|
|
66
75
|
Object.assign(child, world.env);
|
|
67
76
|
if (options.legacyMcpVariables ?? true) {
|
|
68
77
|
const legacy = legacyMcpCapability(world);
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Package version shared by the instrumentation scope and the export User-Agent. */
|
|
2
|
-
export declare const sdkVersion = "0.11.
|
|
2
|
+
export declare const sdkVersion = "0.11.4";
|
package/dist/version.js
CHANGED