@hue-run/sdk 0.10.0 → 0.11.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/CLI.md +148 -28
- package/ENVIRONMENTS.md +5 -1
- package/EVALUATIONS.md +2 -2
- package/README.md +19 -8
- package/dist/cli/eval.js +43 -17
- package/dist/cli/listen.d.ts +76 -0
- package/dist/cli/listen.js +816 -0
- package/dist/cli/mcp.d.ts +65 -7
- package/dist/cli/mcp.js +271 -73
- package/dist/client.d.ts +7 -4
- package/dist/client.js +36 -10
- package/dist/evals/client.d.ts +5 -1
- package/dist/evals/client.js +17 -11
- package/dist/evals/files.d.ts +10 -2
- package/dist/evals/files.js +68 -15
- package/dist/evals/json.js +116 -9
- package/dist/evals/runner.js +35 -8
- package/dist/evals/scorer-publication.js +58 -3
- package/dist/evals/types.d.ts +44 -0
- package/dist/evals/verdicts.d.ts +11 -0
- package/dist/evals/verdicts.js +33 -4
- package/dist/inline-files.js +11 -3
- package/dist/provider-tools.d.ts +12 -1
- package/dist/provider-tools.js +192 -106
- package/dist/safety.d.ts +7 -1
- package/dist/safety.js +9 -7
- package/dist/setup/cli.js +31 -8
- package/dist/setup/peers.d.ts +13 -0
- package/dist/setup/peers.js +44 -0
- package/dist/tool-definitions.d.ts +18 -0
- package/dist/tool-definitions.js +232 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/cli/mcp.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `hue mcp install`: writes or prints the coding-agent configuration for Hue's MCP server.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* `hue mcp install`: writes or prints the coding-agent configuration for Hue's MCP server. The
|
|
3
|
+
* shapes are those of Hue's published connection guide (https://docs.hue.run/agents/mcp-server),
|
|
4
|
+
* kept as a separate copy here. Key configurations reference the `HUE_MCP_KEY` environment
|
|
5
|
+
* variable (or a VS Code password input); sign-in configurations hold only the URL. A key value is
|
|
6
|
+
* never written.
|
|
5
7
|
*/
|
|
6
8
|
/** Streams, environment and working directory for {@link runMcpCommand}; tests inject these. */
|
|
7
9
|
export interface McpCommandIo {
|
|
@@ -12,10 +14,27 @@ export interface McpCommandIo {
|
|
|
12
14
|
cwd?: string;
|
|
13
15
|
}
|
|
14
16
|
export declare const DEFAULT_MCP_URL = "https://mcp.hue.run/mcp";
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Prompt to paste into the agent after installation. `list_projects` answers for every credential:
|
|
19
|
+
* a project key or earlier connection returns its one project, and an organization connection
|
|
20
|
+
* lists them, after which each call takes `project_id`. It reads what needs attention and falls
|
|
21
|
+
* back to recent traces, so a project without errors still proves the read path.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MCP_VERIFY_PROMPT = "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 as project_id on each call after list_projects.";
|
|
24
|
+
/** Toolset names Hue's MCP server accepts: `all`, its catalog groups and the `observe` profile. */
|
|
25
|
+
export declare const MCP_TOOLSETS: readonly ["all", "observe", "project", "traces", "evals", "environments", "intents", "docs"];
|
|
26
|
+
/** The header that selects toolsets for a sign-in connection, whose URL must stay bare. */
|
|
27
|
+
export declare const TOOLSETS_HEADER = "X-Hue-MCP-Toolsets";
|
|
28
|
+
export declare const MCP_USAGE = "Usage: hue mcp install --client <claude-code|codex|conductor|cursor|vscode|windsurf|gemini>\n [--auth key|oauth] [--read-only] [--toolsets NAMES] [--url URL]\n [--scope project|user] [--dry-run] [--print]\n\nConfigure a coding agent to use the Hue MCP server. A key configuration references the\nHUE_MCP_KEY environment variable; a key value is never written. A sign-in configuration holds\nonly the URL: the client opens Hue in a browser, where you approve access to the projects of\none organization.\n\nOptions:\n --client NAME Coding agent to configure (required)\n --auth MODE key: reference HUE_MCP_KEY (the default, except for conductor)\n oauth: URL only, sign in with Hue in the client (claude-code, codex and\n conductor; the default for conductor)\n --read-only key only: add ?read_only=true to the URL so write tools are hidden. A\n sign-in connection has Read and write access; use a Read key for read-only\n --toolsets NAMES\n List only these tools, comma-separated: observe (production reads),\n all, project, traces, evals, environments, intents or docs. A key\n configuration adds ?toolsets= to the URL; a sign-in one sends the\n X-Hue-MCP-Toolsets header. cursor defaults to observe; others to all\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.";
|
|
29
|
+
/**
|
|
30
|
+
* A URL as a POSIX shell word for printed commands: `?` is a zsh glob and `&` ends a command, so a
|
|
31
|
+
* URL with a query (such as `?read_only=true`) is single-quoted. Executed commands pass argv.
|
|
32
|
+
*/
|
|
33
|
+
export declare function shellWord(value: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Canonical Hue client snippets for a key configuration; the JSON values are also the merge
|
|
36
|
+
* entries for config files.
|
|
37
|
+
*/
|
|
19
38
|
export declare function renderMcpSnippets(url: string): {
|
|
20
39
|
claudeCodeServer: {
|
|
21
40
|
type: string;
|
|
@@ -67,6 +86,45 @@ export declare function renderMcpSnippets(url: string): {
|
|
|
67
86
|
display: string;
|
|
68
87
|
};
|
|
69
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* Sign-in snippets: the bare server URL, which the client uses to discover Hue's OAuth metadata,
|
|
91
|
+
* and, when toolsets are selected, the header that carries them.
|
|
92
|
+
*/
|
|
93
|
+
export declare function renderMcpSignInSnippets(url: string, toolsets?: string): {
|
|
94
|
+
claudeCodeServer: {
|
|
95
|
+
type: string;
|
|
96
|
+
url: string;
|
|
97
|
+
headers?: {
|
|
98
|
+
"X-Hue-MCP-Toolsets": string;
|
|
99
|
+
} | undefined;
|
|
100
|
+
};
|
|
101
|
+
claudeCodeProjectJson: string;
|
|
102
|
+
claudeCodeCli: {
|
|
103
|
+
args: string[];
|
|
104
|
+
display: string;
|
|
105
|
+
};
|
|
106
|
+
codexCli: {
|
|
107
|
+
args: string[];
|
|
108
|
+
display: string;
|
|
109
|
+
};
|
|
110
|
+
codexToml: string;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Parses a comma-separated `--toolsets` value into Hue's canonical form, or returns an error.
|
|
114
|
+
* Unknown names are refused: Hue ignores them and would list the whole catalog, hiding a typo.
|
|
115
|
+
*/
|
|
116
|
+
export declare function parseToolsets(value: string): {
|
|
117
|
+
toolsets: string;
|
|
118
|
+
} | {
|
|
119
|
+
error: string;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* `?toolsets=` lists only the selected tools for a key configuration; commas stay readable.
|
|
123
|
+
* Without a selection the parameter is removed, so the URL lists every tool.
|
|
124
|
+
*/
|
|
125
|
+
export declare function toolsetsMcpUrl(url: string, toolsets: string | undefined): string;
|
|
126
|
+
/** `?read_only=true` hides and rejects Hue's write tools for any key. */
|
|
127
|
+
export declare function readOnlyMcpUrl(url: string): string;
|
|
70
128
|
/** Validates the MCP endpoint: HTTPS, or HTTP for loopback test servers; no credentials or hash. */
|
|
71
129
|
export declare function parseMcpUrl(value: string): string | null;
|
|
72
130
|
/**
|
package/dist/cli/mcp.js
CHANGED
|
@@ -10,37 +10,92 @@ const SERVER_NAME = "hue";
|
|
|
10
10
|
const ENV_VAR = "HUE_MCP_KEY";
|
|
11
11
|
const INPUT_ID = `${SERVER_NAME}-mcp-key`;
|
|
12
12
|
const MAX_CONFIG_BYTES = 1024 * 1024;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Prompt to paste into the agent after installation. `list_projects` answers for every credential:
|
|
15
|
+
* a project key or earlier connection returns its one project, and an organization connection
|
|
16
|
+
* lists them, after which each call takes `project_id`. It reads what needs attention and falls
|
|
17
|
+
* back to recent traces, so a project without errors still proves the read path.
|
|
18
|
+
*/
|
|
19
|
+
export const MCP_VERIFY_PROMPT = "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 as project_id on each call after list_projects.";
|
|
20
|
+
const CLIENT_IDS = [
|
|
21
|
+
"claude-code",
|
|
22
|
+
"codex",
|
|
23
|
+
"conductor",
|
|
24
|
+
"cursor",
|
|
25
|
+
"vscode",
|
|
26
|
+
"windsurf",
|
|
27
|
+
"gemini",
|
|
28
|
+
];
|
|
16
29
|
const CLIENT_LABELS = {
|
|
17
30
|
"claude-code": "Claude Code",
|
|
18
|
-
cursor: "Cursor",
|
|
19
31
|
codex: "Codex",
|
|
32
|
+
conductor: "Conductor",
|
|
33
|
+
cursor: "Cursor",
|
|
20
34
|
vscode: "VS Code",
|
|
21
35
|
windsurf: "Windsurf",
|
|
22
36
|
gemini: "Gemini CLI",
|
|
23
37
|
};
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
/** Clients whose browser sign-in with Hue works against the deployed server. */
|
|
39
|
+
const OAUTH_CLIENTS = ["claude-code", "codex", "conductor"];
|
|
40
|
+
/** Toolset names Hue's MCP server accepts: `all`, its catalog groups and the `observe` profile. */
|
|
41
|
+
export const MCP_TOOLSETS = [
|
|
42
|
+
"all",
|
|
43
|
+
"observe",
|
|
44
|
+
"project",
|
|
45
|
+
"traces",
|
|
46
|
+
"evals",
|
|
47
|
+
"environments",
|
|
48
|
+
"intents",
|
|
49
|
+
"docs",
|
|
50
|
+
];
|
|
51
|
+
/** The header that selects toolsets for a sign-in connection, whose URL must stay bare. */
|
|
52
|
+
export const TOOLSETS_HEADER = "X-Hue-MCP-Toolsets";
|
|
53
|
+
/** Clients whose configuration selects a toolset unless `--toolsets` says otherwise. */
|
|
54
|
+
const DEFAULT_TOOLSETS = { cursor: "observe" };
|
|
55
|
+
export const MCP_USAGE = `Usage: hue mcp install --client <claude-code|codex|conductor|cursor|vscode|windsurf|gemini>
|
|
56
|
+
[--auth key|oauth] [--read-only] [--toolsets NAMES] [--url URL]
|
|
57
|
+
[--scope project|user] [--dry-run] [--print]
|
|
26
58
|
|
|
27
|
-
Configure a coding agent to use the Hue MCP server.
|
|
28
|
-
${ENV_VAR} environment variable; a key value is never written.
|
|
59
|
+
Configure a coding agent to use the Hue MCP server. A key configuration references the
|
|
60
|
+
${ENV_VAR} environment variable; a key value is never written. A sign-in configuration holds
|
|
61
|
+
only the URL: the client opens Hue in a browser, where you approve access to the projects of
|
|
62
|
+
one organization.
|
|
29
63
|
|
|
30
64
|
Options:
|
|
31
65
|
--client NAME Coding agent to configure (required)
|
|
66
|
+
--auth MODE key: reference ${ENV_VAR} (the default, except for conductor)
|
|
67
|
+
oauth: URL only, sign in with Hue in the client (claude-code, codex and
|
|
68
|
+
conductor; the default for conductor)
|
|
69
|
+
--read-only key only: add ?read_only=true to the URL so write tools are hidden. A
|
|
70
|
+
sign-in connection has Read and write access; use a Read key for read-only
|
|
71
|
+
--toolsets NAMES
|
|
72
|
+
List only these tools, comma-separated: observe (production reads),
|
|
73
|
+
all, project, traces, evals, environments, intents or docs. A key
|
|
74
|
+
configuration adds ?toolsets= to the URL; a sign-in one sends the
|
|
75
|
+
${TOOLSETS_HEADER} header. cursor defaults to observe; others to all
|
|
32
76
|
--url URL Hue MCP endpoint (default ${DEFAULT_MCP_URL})
|
|
33
77
|
--scope SCOPE claude-code only: project writes .mcp.json (default); user runs
|
|
34
78
|
\`claude mcp add --scope user\`
|
|
35
|
-
--dry-run Print the resulting file content or
|
|
79
|
+
--dry-run Print the resulting file content or commands without writing or running
|
|
36
80
|
--print Print the configuration snippet only
|
|
37
81
|
-h, --help Show this help
|
|
38
82
|
|
|
39
83
|
Files: claude-code .mcp.json, cursor .cursor/mcp.json, vscode .vscode/mcp.json (relative to the
|
|
40
84
|
current directory). codex and gemini use their own CLI when it is on PATH; windsurf prints the
|
|
41
|
-
snippet for its user configuration file
|
|
85
|
+
snippet for its user configuration file. conductor registers the server for its Claude Code
|
|
86
|
+
(user scope) and Codex agents with their CLIs, printing the command for a CLI not on PATH.`;
|
|
42
87
|
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
43
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* A URL as a POSIX shell word for printed commands: `?` is a zsh glob and `&` ends a command, so a
|
|
90
|
+
* URL with a query (such as `?read_only=true`) is single-quoted. Executed commands pass argv.
|
|
91
|
+
*/
|
|
92
|
+
export function shellWord(value) {
|
|
93
|
+
return /^[\w@%+=:,./-]+$/u.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Canonical Hue client snippets for a key configuration; the JSON values are also the merge
|
|
97
|
+
* entries for config files.
|
|
98
|
+
*/
|
|
44
99
|
export function renderMcpSnippets(url) {
|
|
45
100
|
const bearer = (reference) => `Bearer ${reference}`;
|
|
46
101
|
const claudeCodeServer = {
|
|
@@ -57,7 +112,7 @@ export function renderMcpSnippets(url) {
|
|
|
57
112
|
const vscodeInput = {
|
|
58
113
|
type: "promptString",
|
|
59
114
|
id: INPUT_ID,
|
|
60
|
-
description: "Hue
|
|
115
|
+
description: "Hue Read or Read and write API key",
|
|
61
116
|
password: true,
|
|
62
117
|
};
|
|
63
118
|
const windsurfServer = {
|
|
@@ -80,13 +135,13 @@ export function renderMcpSnippets(url) {
|
|
|
80
135
|
"--header",
|
|
81
136
|
`Authorization: Bearer \${${ENV_VAR}}`,
|
|
82
137
|
],
|
|
83
|
-
display: `claude mcp add --transport http --scope user ${SERVER_NAME} ${url} --header 'Authorization: Bearer \${${ENV_VAR}}'`,
|
|
138
|
+
display: `claude mcp add --transport http --scope user ${SERVER_NAME} ${shellWord(url)} --header 'Authorization: Bearer \${${ENV_VAR}}'`,
|
|
84
139
|
},
|
|
85
140
|
cursorServer,
|
|
86
141
|
cursorJson: json({ mcpServers: { [SERVER_NAME]: cursorServer } }),
|
|
87
142
|
codexCli: {
|
|
88
143
|
args: ["mcp", "add", SERVER_NAME, "--url", url, "--bearer-token-env-var", ENV_VAR],
|
|
89
|
-
display: `codex mcp add ${SERVER_NAME} --url ${url} --bearer-token-env-var ${ENV_VAR}`,
|
|
144
|
+
display: `codex mcp add ${SERVER_NAME} --url ${shellWord(url)} --bearer-token-env-var ${ENV_VAR}`,
|
|
90
145
|
},
|
|
91
146
|
codexToml: `[mcp_servers.${SERVER_NAME}]\nurl = "${url}"\nbearer_token_env_var = "${ENV_VAR}"\n`,
|
|
92
147
|
vscodeServer,
|
|
@@ -98,19 +153,88 @@ export function renderMcpSnippets(url) {
|
|
|
98
153
|
args: [
|
|
99
154
|
"mcp",
|
|
100
155
|
"add",
|
|
156
|
+
"--scope",
|
|
157
|
+
"user",
|
|
101
158
|
"--transport",
|
|
102
159
|
"http",
|
|
103
160
|
SERVER_NAME,
|
|
104
161
|
url,
|
|
105
|
-
"
|
|
106
|
-
`Authorization: Bearer
|
|
162
|
+
"--header",
|
|
163
|
+
`Authorization: Bearer \${${ENV_VAR}}`,
|
|
107
164
|
],
|
|
108
165
|
// Single quotes keep the reference literal when a person runs this in a shell where the
|
|
109
|
-
// key is exported; Gemini CLI
|
|
110
|
-
display: `gemini mcp add --transport http ${SERVER_NAME} ${url}
|
|
166
|
+
// key is exported; Gemini CLI expands ${HUE_MCP_KEY} from its settings at connection time.
|
|
167
|
+
display: `gemini mcp add --scope user --transport http ${SERVER_NAME} ${shellWord(url)} --header 'Authorization: Bearer \${${ENV_VAR}}'`,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Sign-in snippets: the bare server URL, which the client uses to discover Hue's OAuth metadata,
|
|
173
|
+
* and, when toolsets are selected, the header that carries them.
|
|
174
|
+
*/
|
|
175
|
+
export function renderMcpSignInSnippets(url, toolsets) {
|
|
176
|
+
const headers = toolsets ? { [TOOLSETS_HEADER]: toolsets } : undefined;
|
|
177
|
+
const claudeCodeServer = { type: "http", url, ...(headers ? { headers } : {}) };
|
|
178
|
+
const headerArgs = toolsets ? ["--header", `${TOOLSETS_HEADER}: ${toolsets}`] : [];
|
|
179
|
+
return {
|
|
180
|
+
claudeCodeServer,
|
|
181
|
+
claudeCodeProjectJson: json({ mcpServers: { [SERVER_NAME]: claudeCodeServer } }),
|
|
182
|
+
claudeCodeCli: {
|
|
183
|
+
args: [
|
|
184
|
+
"mcp",
|
|
185
|
+
"add",
|
|
186
|
+
"--transport",
|
|
187
|
+
"http",
|
|
188
|
+
"--scope",
|
|
189
|
+
"user",
|
|
190
|
+
SERVER_NAME,
|
|
191
|
+
url,
|
|
192
|
+
...headerArgs,
|
|
193
|
+
],
|
|
194
|
+
display: `claude mcp add --transport http --scope user ${SERVER_NAME} ${shellWord(url)}${toolsets ? ` --header '${TOOLSETS_HEADER}: ${toolsets}'` : ""}`,
|
|
195
|
+
},
|
|
196
|
+
// `codex mcp add` cannot store a header; the TOML carries it and the next steps say so.
|
|
197
|
+
codexCli: {
|
|
198
|
+
args: ["mcp", "add", SERVER_NAME, "--url", url],
|
|
199
|
+
display: `codex mcp add ${SERVER_NAME} --url ${shellWord(url)}`,
|
|
111
200
|
},
|
|
201
|
+
codexToml: `[mcp_servers.${SERVER_NAME}]\nurl = "${url}"\n${toolsets ? `http_headers = { "${TOOLSETS_HEADER}" = "${toolsets}" }\n` : ""}`,
|
|
112
202
|
};
|
|
113
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Parses a comma-separated `--toolsets` value into Hue's canonical form, or returns an error.
|
|
206
|
+
* Unknown names are refused: Hue ignores them and would list the whole catalog, hiding a typo.
|
|
207
|
+
*/
|
|
208
|
+
export function parseToolsets(value) {
|
|
209
|
+
const names = [...new Set(value.split(",").map((name) => name.trim()))];
|
|
210
|
+
if (names.some((name) => !name))
|
|
211
|
+
return { error: "--toolsets takes comma-separated names, such as observe or traces,docs." };
|
|
212
|
+
const unknown = names.filter((name) => !MCP_TOOLSETS.includes(name));
|
|
213
|
+
if (unknown.length)
|
|
214
|
+
return {
|
|
215
|
+
error: `Unknown toolset${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}. Choose from ${MCP_TOOLSETS.join(", ")}.`,
|
|
216
|
+
};
|
|
217
|
+
return { toolsets: names.join(",") };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* `?toolsets=` lists only the selected tools for a key configuration; commas stay readable.
|
|
221
|
+
* Without a selection the parameter is removed, so the URL lists every tool.
|
|
222
|
+
*/
|
|
223
|
+
export function toolsetsMcpUrl(url, toolsets) {
|
|
224
|
+
const parsed = new URL(url);
|
|
225
|
+
parsed.searchParams.delete("toolsets");
|
|
226
|
+
if (!toolsets)
|
|
227
|
+
return parsed.href;
|
|
228
|
+
const query = parsed.search.slice(1);
|
|
229
|
+
parsed.search = `${query ? `${query}&` : ""}toolsets=${toolsets}`;
|
|
230
|
+
return parsed.href;
|
|
231
|
+
}
|
|
232
|
+
/** `?read_only=true` hides and rejects Hue's write tools for any key. */
|
|
233
|
+
export function readOnlyMcpUrl(url) {
|
|
234
|
+
const parsed = new URL(url);
|
|
235
|
+
parsed.searchParams.set("read_only", "true");
|
|
236
|
+
return parsed.href;
|
|
237
|
+
}
|
|
114
238
|
class ConfigError extends Error {
|
|
115
239
|
constructor(message) {
|
|
116
240
|
super(message);
|
|
@@ -264,6 +388,9 @@ function parseMcpArguments(argv) {
|
|
|
264
388
|
strict: true,
|
|
265
389
|
options: {
|
|
266
390
|
client: { type: "string" },
|
|
391
|
+
auth: { type: "string" },
|
|
392
|
+
"read-only": { type: "boolean", default: false },
|
|
393
|
+
toolsets: { type: "string" },
|
|
267
394
|
url: { type: "string" },
|
|
268
395
|
scope: { type: "string" },
|
|
269
396
|
"dry-run": { type: "boolean", default: false },
|
|
@@ -276,26 +403,48 @@ function displayPath(cwd, path) {
|
|
|
276
403
|
const shown = relative(cwd, path);
|
|
277
404
|
return shown && !shown.startsWith("..") && !isAbsolute(shown) ? shown : path;
|
|
278
405
|
}
|
|
279
|
-
|
|
406
|
+
const notOnPath = (executable, label) => `${executable} is not on PATH. Run this where ${label} is installed:`;
|
|
407
|
+
/** `auth: "oauth"` is only planned for {@link OAUTH_CLIENTS}; the caller refuses the others. */
|
|
408
|
+
function planFor(client, auth, scope, url, toolsets) {
|
|
280
409
|
const snippets = renderMcpSnippets(url);
|
|
410
|
+
// Claude Code and Codex take the same plan shape with or without a key.
|
|
411
|
+
const shared = auth === "oauth" ? renderMcpSignInSnippets(url, toolsets) : snippets;
|
|
412
|
+
const claudeUser = {
|
|
413
|
+
executable: "claude",
|
|
414
|
+
label: "Claude Code",
|
|
415
|
+
...shared.claudeCodeCli,
|
|
416
|
+
fallback: [notOnPath("claude", "Claude Code")],
|
|
417
|
+
};
|
|
418
|
+
const codex = {
|
|
419
|
+
executable: "codex",
|
|
420
|
+
label: "Codex",
|
|
421
|
+
...shared.codexCli,
|
|
422
|
+
fallback: [
|
|
423
|
+
"codex is not on PATH. Add this to ~/.codex/config.toml (or run the command where Codex is installed):",
|
|
424
|
+
shared.codexToml.trimEnd(),
|
|
425
|
+
],
|
|
426
|
+
};
|
|
281
427
|
switch (client) {
|
|
282
428
|
case "claude-code":
|
|
283
429
|
return scope === "user"
|
|
284
|
-
? {
|
|
285
|
-
kind: "cli",
|
|
286
|
-
executable: "claude",
|
|
287
|
-
args: snippets.claudeCodeCli.args,
|
|
288
|
-
display: snippets.claudeCodeCli.display,
|
|
289
|
-
snippet: `${snippets.claudeCodeCli.display}\n`,
|
|
290
|
-
fallback: ["claude is not on PATH. Run this where Claude Code is installed:"],
|
|
291
|
-
}
|
|
430
|
+
? { kind: "cli", commands: [claudeUser], snippet: `${claudeUser.display}\n` }
|
|
292
431
|
: {
|
|
293
432
|
kind: "file",
|
|
294
433
|
file: ".mcp.json",
|
|
295
434
|
key: "mcpServers",
|
|
296
|
-
entry:
|
|
297
|
-
snippet:
|
|
435
|
+
entry: shared.claudeCodeServer,
|
|
436
|
+
snippet: shared.claudeCodeProjectJson,
|
|
298
437
|
};
|
|
438
|
+
case "codex":
|
|
439
|
+
return { kind: "cli", commands: [codex], snippet: shared.codexToml };
|
|
440
|
+
case "conductor":
|
|
441
|
+
// Conductor has no MCP configuration of its own: its Claude Code and Codex agents read their
|
|
442
|
+
// user configuration in every workspace.
|
|
443
|
+
return {
|
|
444
|
+
kind: "cli",
|
|
445
|
+
commands: [claudeUser, codex],
|
|
446
|
+
snippet: `${claudeUser.display}\n${codex.display}\n`,
|
|
447
|
+
};
|
|
299
448
|
case "cursor":
|
|
300
449
|
return {
|
|
301
450
|
kind: "file",
|
|
@@ -304,18 +453,6 @@ function planFor(client, scope, url) {
|
|
|
304
453
|
entry: snippets.cursorServer,
|
|
305
454
|
snippet: snippets.cursorJson,
|
|
306
455
|
};
|
|
307
|
-
case "codex":
|
|
308
|
-
return {
|
|
309
|
-
kind: "cli",
|
|
310
|
-
executable: "codex",
|
|
311
|
-
args: snippets.codexCli.args,
|
|
312
|
-
display: snippets.codexCli.display,
|
|
313
|
-
snippet: snippets.codexToml,
|
|
314
|
-
fallback: [
|
|
315
|
-
"codex is not on PATH. Add this to ~/.codex/config.toml (or run the command where Codex is installed):",
|
|
316
|
-
snippets.codexToml.trimEnd(),
|
|
317
|
-
],
|
|
318
|
-
};
|
|
319
456
|
case "vscode":
|
|
320
457
|
return {
|
|
321
458
|
kind: "file",
|
|
@@ -331,27 +468,51 @@ function planFor(client, scope, url) {
|
|
|
331
468
|
snippet: snippets.windsurfJson,
|
|
332
469
|
hint: "Merge this into ~/.codeium/windsurf/mcp_config.json (Windsurf > Settings > MCP); the command does not write to your home directory.",
|
|
333
470
|
};
|
|
334
|
-
case "gemini":
|
|
335
|
-
|
|
336
|
-
kind: "cli",
|
|
471
|
+
case "gemini": {
|
|
472
|
+
const gemini = {
|
|
337
473
|
executable: "gemini",
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
fallback: ["gemini is not on PATH. Run this where Gemini CLI is installed:"],
|
|
474
|
+
label: "Gemini CLI",
|
|
475
|
+
...snippets.geminiCli,
|
|
476
|
+
fallback: [notOnPath("gemini", "Gemini CLI")],
|
|
342
477
|
};
|
|
478
|
+
return { kind: "cli", commands: [gemini], snippet: `${gemini.display}\n` };
|
|
479
|
+
}
|
|
343
480
|
}
|
|
344
481
|
}
|
|
345
|
-
function nextSteps(client) {
|
|
482
|
+
function nextSteps(client, auth, toolsets) {
|
|
346
483
|
const label = CLIENT_LABELS[client];
|
|
347
|
-
const
|
|
348
|
-
|
|
484
|
+
const approve = "Sign in to Hue and approve the connection. It can read and write every active project in the organization you choose, within your role; for read-only access, use a Read project key with --auth key.";
|
|
485
|
+
let lines;
|
|
486
|
+
if (auth === "oauth")
|
|
487
|
+
lines =
|
|
488
|
+
client === "claude-code"
|
|
489
|
+
? [`In Claude Code, run /mcp, select ${SERVER_NAME} and choose Authenticate. ${approve}`]
|
|
490
|
+
: client === "codex"
|
|
491
|
+
? [
|
|
492
|
+
`If Codex did not open Hue in your browser, run: codex mcp login ${SERVER_NAME}`,
|
|
493
|
+
approve,
|
|
494
|
+
]
|
|
495
|
+
: [
|
|
496
|
+
`In Conductor, open MCP status from the plug icon or /mcp-status, refresh, and use ${SERVER_NAME}'s authentication action. ${approve}`,
|
|
497
|
+
"Start a new agent session if the Hue tools do not appear. Hue has not yet verified signed-in Conductor end to end; if the tools still do not load, rerun with --auth key.",
|
|
498
|
+
];
|
|
499
|
+
else if (client === "vscode")
|
|
500
|
+
lines = [
|
|
349
501
|
`${label} prompts for the key (input ${INPUT_ID}) when the server starts; paste the ${ENV_VAR} value that hue login stored in .env.hue.`,
|
|
350
|
-
]
|
|
351
|
-
|
|
502
|
+
];
|
|
503
|
+
else if (client === "conductor")
|
|
504
|
+
lines = [
|
|
505
|
+
`Conductor agents read ${ENV_VAR} from the login-shell environment Conductor captures, so export it there, not only in a terminal; hue login stores it in .env.hue. Sign-in (--auth oauth) needs no variable.`,
|
|
506
|
+
];
|
|
507
|
+
else
|
|
508
|
+
lines = [
|
|
352
509
|
`Export ${ENV_VAR} in the shell that starts ${label}; hue login stores it in .env.hue:`,
|
|
353
510
|
" set -a; . ./.env.hue; set +a",
|
|
511
|
+
`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" : ""}.`,
|
|
354
512
|
];
|
|
513
|
+
// `codex mcp add` stores no headers, so a sign-in toolset selection is added by hand.
|
|
514
|
+
if (auth === "oauth" && toolsets && (client === "codex" || client === "conductor"))
|
|
515
|
+
lines.push(`To list only the ${toolsets} tools in Codex, add this line under [mcp_servers.${SERVER_NAME}] in ~/.codex/config.toml:`, ` http_headers = { "${TOOLSETS_HEADER}" = "${toolsets}" }`);
|
|
355
516
|
return [...lines, "Then ask your agent:", ` ${MCP_VERIFY_PROMPT}`];
|
|
356
517
|
}
|
|
357
518
|
/**
|
|
@@ -387,20 +548,52 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
387
548
|
const client = parsed.values.client;
|
|
388
549
|
if (!client || !CLIENT_IDS.includes(client))
|
|
389
550
|
return fail(`${client ? `Unknown client: ${client}.` : "--client is required."} Choose one of ${CLIENT_IDS.join(", ")}.\n\n${MCP_USAGE}`, 2);
|
|
551
|
+
const clientId = client;
|
|
552
|
+
const auth = parsed.values.auth ?? (clientId === "conductor" ? "oauth" : "key");
|
|
553
|
+
if (auth !== "key" && auth !== "oauth")
|
|
554
|
+
return fail(`--auth must be key or oauth.\n\n${MCP_USAGE}`, 2);
|
|
555
|
+
if (auth === "oauth" && !OAUTH_CLIENTS.includes(clientId))
|
|
556
|
+
return fail(clientId === "cursor"
|
|
557
|
+
? "--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."
|
|
558
|
+
: `--auth oauth is available for ${OAUTH_CLIENTS.join(", ")}. Use a key (the default) for ${clientId}.`, 2);
|
|
390
559
|
const scope = parsed.values.scope ?? "project";
|
|
391
560
|
if (scope !== "project" && scope !== "user")
|
|
392
561
|
return fail(`--scope must be project or user.\n\n${MCP_USAGE}`, 2);
|
|
393
|
-
if (scope === "user" &&
|
|
562
|
+
if (scope === "user" && clientId !== "claude-code")
|
|
394
563
|
return fail("--scope user is only available with --client claude-code.", 2);
|
|
395
|
-
|
|
564
|
+
let url = parseMcpUrl(parsed.values.url ?? DEFAULT_MCP_URL);
|
|
396
565
|
if (!url)
|
|
397
566
|
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);
|
|
398
|
-
|
|
567
|
+
// A sign-in connection's access is chosen when it is approved; read_only is not part of it.
|
|
568
|
+
if (auth === "oauth" &&
|
|
569
|
+
(parsed.values["read-only"] || new URL(url).searchParams.has("read_only")))
|
|
570
|
+
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);
|
|
571
|
+
if (parsed.values["read-only"])
|
|
572
|
+
url = readOnlyMcpUrl(url);
|
|
573
|
+
// Toolsets are not part of the protected resource either: a sign-in URL stays bare.
|
|
574
|
+
if (auth === "oauth" && new URL(url).searchParams.has("toolsets"))
|
|
575
|
+
return fail("Leave toolsets off a sign-in URL; --toolsets sends them in a header instead.", 2);
|
|
576
|
+
// --toolsets wins over a selection already in --url, which wins over the client's default.
|
|
577
|
+
// Both sources are validated: Hue ignores an unknown name and would list every tool.
|
|
578
|
+
const requested = parsed.values.toolsets ?? new URL(url).searchParams.get("toolsets");
|
|
579
|
+
let toolsets = DEFAULT_TOOLSETS[clientId];
|
|
580
|
+
if (requested !== null) {
|
|
581
|
+
const selected = parseToolsets(requested);
|
|
582
|
+
if ("error" in selected)
|
|
583
|
+
return fail(`${parsed.values.toolsets === undefined ? "In --url: " : ""}${selected.error}\n\n${MCP_USAGE}`, 2);
|
|
584
|
+
toolsets = selected.toolsets;
|
|
585
|
+
}
|
|
586
|
+
// `all` is Hue's default, so it needs no selection.
|
|
587
|
+
if (toolsets === "all")
|
|
588
|
+
toolsets = undefined;
|
|
589
|
+
if (auth === "key")
|
|
590
|
+
url = toolsetsMcpUrl(url, toolsets);
|
|
591
|
+
const plan = planFor(clientId, auth, scope, url, auth === "oauth" ? toolsets : undefined);
|
|
399
592
|
if (parsed.values.print) {
|
|
400
593
|
stdout.write(plan.snippet);
|
|
401
594
|
return 0;
|
|
402
595
|
}
|
|
403
|
-
const steps = nextSteps(
|
|
596
|
+
const steps = nextSteps(clientId, auth, toolsets);
|
|
404
597
|
if (plan.kind === "manual") {
|
|
405
598
|
out(plan.hint);
|
|
406
599
|
stdout.write(plan.snippet);
|
|
@@ -410,25 +603,30 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
410
603
|
}
|
|
411
604
|
if (plan.kind === "cli") {
|
|
412
605
|
if (parsed.values["dry-run"]) {
|
|
413
|
-
|
|
606
|
+
for (const command of plan.commands)
|
|
607
|
+
out(`Would run: ${command.display}`);
|
|
414
608
|
return 0;
|
|
415
609
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
out(
|
|
423
|
-
|
|
610
|
+
let failed = false;
|
|
611
|
+
for (const command of plan.commands) {
|
|
612
|
+
const executable = await findExecutable(command.executable, env);
|
|
613
|
+
if (!executable) {
|
|
614
|
+
for (const line of command.fallback)
|
|
615
|
+
out(line);
|
|
616
|
+
out(command.display);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
out(`Running: ${command.display}`);
|
|
620
|
+
const code = await runClientCli(executable, command.args, { cwd, env, stdout, stderr });
|
|
621
|
+
if (code === 0) {
|
|
622
|
+
out(`Registered the "${SERVER_NAME}" MCP server (${url}) with ${command.label}.`);
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
failed = true;
|
|
626
|
+
stderr.write(`${command.executable} ${code === null ? "could not be started" : `exited with code ${code}`}. Run this command yourself:\n${command.display}\n`);
|
|
424
627
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (code === null)
|
|
428
|
-
return fail(`${plan.executable} could not be started. Run this command yourself:\n${plan.display}`);
|
|
429
|
-
if (code !== 0)
|
|
430
|
-
return fail(`${plan.executable} exited with code ${code}. Run this command yourself:\n${plan.display}`);
|
|
431
|
-
out(`Registered the "${SERVER_NAME}" MCP server (${url}) with ${CLIENT_LABELS[client]}.`);
|
|
628
|
+
if (failed)
|
|
629
|
+
return 1;
|
|
432
630
|
for (const line of steps)
|
|
433
631
|
out(line);
|
|
434
632
|
return 0;
|
package/dist/client.d.ts
CHANGED
|
@@ -129,11 +129,14 @@ export declare class HueClient {
|
|
|
129
129
|
* `hue.tool()` call saw: OpenAI Responses `mcp_call`, `web_search_call`, `file_search_call` and
|
|
130
130
|
* `code_interpreter_call` items, and Anthropic Messages `mcp_tool_use` / `server_tool_use`
|
|
131
131
|
* blocks with their result blocks. Each becomes an `execute_tool {name}` child span of the active
|
|
132
|
-
* (or given) context with `gen_ai.tool.type` `extension
|
|
132
|
+
* (or given) context with `gen_ai.tool.type` `extension`, `gen_ai.tool.call.id` and
|
|
133
|
+
* `hue.tool.call.position` (the item's 0-based position in the response); MCP calls add
|
|
133
134
|
* `mcp.server.name` (the provider's label, or the `servers` entry for it). Arguments and results
|
|
134
|
-
* follow `captureContent`; a failed call carries `error.type` and ERROR status
|
|
135
|
-
*
|
|
136
|
-
*
|
|
135
|
+
* follow `captureContent`; a failed call carries `error.type` and ERROR status, and with content
|
|
136
|
+
* capture a failed OpenAI MCP call's status description is the provider's scrubbed, bounded error
|
|
137
|
+
* text. An OpenAI `mcp_list_tools` item becomes a `tools/list` child span carrying that server's
|
|
138
|
+
* tools as `gen_ai.tool.definitions`, or without content capture only `hue.tool.names` and
|
|
139
|
+
* `hue.tool.definitions.sha256`. Call it inside `hue.model()` so the spans nest under the model call
|
|
137
140
|
* and `provider` defaults to its provider; pass `request` to record each server's host as
|
|
138
141
|
* `server.address`. The spans have no duration of their own: the provider ran the tools inside
|
|
139
142
|
* the model request. Unreadable items are skipped and counted; nothing is thrown.
|