@hue-run/sdk 0.10.0 → 0.11.1
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 +154 -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 +66 -7
- package/dist/cli/mcp.js +273 -74
- 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 no credential. A key value
|
|
6
|
+
* is 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,30 @@ 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
|
+
/**
|
|
27
|
+
* The header form of `?toolsets=`. Only the sign-in Codex TOML uses it, as Hue's guide does, to
|
|
28
|
+
* keep that URL bare; every other configuration carries the selection in its URL.
|
|
29
|
+
*/
|
|
30
|
+
export declare const TOOLSETS_HEADER = "X-Hue-MCP-Toolsets";
|
|
31
|
+
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 Tools to list, comma-separated: all, observe (production reads),\n project, traces, evals, environments, intents or docs, added to the\n URL as ?toolsets=. 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.";
|
|
32
|
+
/**
|
|
33
|
+
* A URL as a POSIX shell word for printed commands: `?` is a zsh glob and `&` ends a command, so a
|
|
34
|
+
* URL with a query (such as `?read_only=true`) is single-quoted. Executed commands pass argv.
|
|
35
|
+
*/
|
|
36
|
+
export declare function shellWord(value: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Canonical Hue client snippets for a key configuration; the JSON values are also the merge
|
|
39
|
+
* entries for config files.
|
|
40
|
+
*/
|
|
19
41
|
export declare function renderMcpSnippets(url: string): {
|
|
20
42
|
claudeCodeServer: {
|
|
21
43
|
type: string;
|
|
@@ -67,6 +89,43 @@ export declare function renderMcpSnippets(url: string): {
|
|
|
67
89
|
display: string;
|
|
68
90
|
};
|
|
69
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* Sign-in snippets: the server URL and nothing secret. The client discovers Hue's OAuth metadata
|
|
94
|
+
* from it and sends it, query included, on every request; the token's resource ignores the query,
|
|
95
|
+
* so `?toolsets=` selects tools here as it does for a key.
|
|
96
|
+
*/
|
|
97
|
+
export declare function renderMcpSignInSnippets(url: string): {
|
|
98
|
+
claudeCodeServer: {
|
|
99
|
+
type: string;
|
|
100
|
+
url: string;
|
|
101
|
+
};
|
|
102
|
+
claudeCodeProjectJson: string;
|
|
103
|
+
claudeCodeCli: {
|
|
104
|
+
args: string[];
|
|
105
|
+
display: string;
|
|
106
|
+
};
|
|
107
|
+
codexCli: {
|
|
108
|
+
args: string[];
|
|
109
|
+
display: string;
|
|
110
|
+
};
|
|
111
|
+
codexToml: string;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Parses a comma-separated `--toolsets` value into Hue's canonical form, or returns an error.
|
|
115
|
+
* Unknown names are refused: Hue ignores them and would list its default, hiding a typo.
|
|
116
|
+
*/
|
|
117
|
+
export declare function parseToolsets(value: string): {
|
|
118
|
+
toolsets: string;
|
|
119
|
+
} | {
|
|
120
|
+
error: string;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* `?toolsets=` lists only the selected tools; commas stay readable. Without a selection the
|
|
124
|
+
* parameter is removed, so the connection lists Hue's default.
|
|
125
|
+
*/
|
|
126
|
+
export declare function toolsetsMcpUrl(url: string, toolsets: string | undefined): string;
|
|
127
|
+
/** `?read_only=true` hides and rejects Hue's write tools for any key. */
|
|
128
|
+
export declare function readOnlyMcpUrl(url: string): string;
|
|
70
129
|
/** Validates the MCP endpoint: HTTPS, or HTTP for loopback test servers; no credentials or hash. */
|
|
71
130
|
export declare function parseMcpUrl(value: string): string | null;
|
|
72
131
|
/**
|
package/dist/cli/mcp.js
CHANGED
|
@@ -10,37 +10,110 @@ 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
|
+
/**
|
|
52
|
+
* The header form of `?toolsets=`. Only the sign-in Codex TOML uses it, as Hue's guide does, to
|
|
53
|
+
* keep that URL bare; every other configuration carries the selection in its URL.
|
|
54
|
+
*/
|
|
55
|
+
export const TOOLSETS_HEADER = "X-Hue-MCP-Toolsets";
|
|
56
|
+
/**
|
|
57
|
+
* Toolsets a client's configuration selects unless `--toolsets` says otherwise. Claude Code,
|
|
58
|
+
* Codex and Conductor's agents defer MCP tools behind their own tool search, so listing every tool
|
|
59
|
+
* costs them little. Cursor caps the tools it loads, so it names the production reads. The other
|
|
60
|
+
* clients list Hue's default: the production reads, and `search_hue_tools` with its executors,
|
|
61
|
+
* which reach every other tool.
|
|
62
|
+
*/
|
|
63
|
+
const DEFAULT_TOOLSETS = {
|
|
64
|
+
"claude-code": "all",
|
|
65
|
+
codex: "all",
|
|
66
|
+
conductor: "all",
|
|
67
|
+
cursor: "observe",
|
|
68
|
+
};
|
|
69
|
+
/** Codex runs a server's tool calls one at a time without it; each Hue call is independent. */
|
|
70
|
+
const CODEX_PARALLEL = "supports_parallel_tool_calls = true";
|
|
71
|
+
export const MCP_USAGE = `Usage: hue mcp install --client <claude-code|codex|conductor|cursor|vscode|windsurf|gemini>
|
|
72
|
+
[--auth key|oauth] [--read-only] [--toolsets NAMES] [--url URL]
|
|
73
|
+
[--scope project|user] [--dry-run] [--print]
|
|
26
74
|
|
|
27
|
-
Configure a coding agent to use the Hue MCP server.
|
|
28
|
-
${ENV_VAR} environment variable; a key value is never written.
|
|
75
|
+
Configure a coding agent to use the Hue MCP server. A key configuration references the
|
|
76
|
+
${ENV_VAR} environment variable; a key value is never written. A sign-in configuration holds
|
|
77
|
+
only the URL: the client opens Hue in a browser, where you approve access to the projects of
|
|
78
|
+
one organization.
|
|
29
79
|
|
|
30
80
|
Options:
|
|
31
81
|
--client NAME Coding agent to configure (required)
|
|
82
|
+
--auth MODE key: reference ${ENV_VAR} (the default, except for conductor)
|
|
83
|
+
oauth: URL only, sign in with Hue in the client (claude-code, codex and
|
|
84
|
+
conductor; the default for conductor)
|
|
85
|
+
--read-only key only: add ?read_only=true to the URL so write tools are hidden. A
|
|
86
|
+
sign-in connection has Read and write access; use a Read key for read-only
|
|
87
|
+
--toolsets NAMES
|
|
88
|
+
Tools to list, comma-separated: all, observe (production reads),
|
|
89
|
+
project, traces, evals, environments, intents or docs, added to the
|
|
90
|
+
URL as ?toolsets=. Defaults: all for claude-code, codex and conductor,
|
|
91
|
+
which search their own tools; observe for cursor. Without a selection
|
|
92
|
+
Hue lists the production reads and search_hue_tools, which reaches
|
|
93
|
+
every other tool
|
|
32
94
|
--url URL Hue MCP endpoint (default ${DEFAULT_MCP_URL})
|
|
33
95
|
--scope SCOPE claude-code only: project writes .mcp.json (default); user runs
|
|
34
96
|
\`claude mcp add --scope user\`
|
|
35
|
-
--dry-run Print the resulting file content or
|
|
97
|
+
--dry-run Print the resulting file content or commands without writing or running
|
|
36
98
|
--print Print the configuration snippet only
|
|
37
99
|
-h, --help Show this help
|
|
38
100
|
|
|
39
101
|
Files: claude-code .mcp.json, cursor .cursor/mcp.json, vscode .vscode/mcp.json (relative to the
|
|
40
102
|
current directory). codex and gemini use their own CLI when it is on PATH; windsurf prints the
|
|
41
|
-
snippet for its user configuration file
|
|
103
|
+
snippet for its user configuration file. conductor registers the server for its Claude Code
|
|
104
|
+
(user scope) and Codex agents with their CLIs, printing the command for a CLI not on PATH.`;
|
|
42
105
|
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
43
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* A URL as a POSIX shell word for printed commands: `?` is a zsh glob and `&` ends a command, so a
|
|
108
|
+
* URL with a query (such as `?read_only=true`) is single-quoted. Executed commands pass argv.
|
|
109
|
+
*/
|
|
110
|
+
export function shellWord(value) {
|
|
111
|
+
return /^[\w@%+=:,./-]+$/u.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Canonical Hue client snippets for a key configuration; the JSON values are also the merge
|
|
115
|
+
* entries for config files.
|
|
116
|
+
*/
|
|
44
117
|
export function renderMcpSnippets(url) {
|
|
45
118
|
const bearer = (reference) => `Bearer ${reference}`;
|
|
46
119
|
const claudeCodeServer = {
|
|
@@ -57,7 +130,7 @@ export function renderMcpSnippets(url) {
|
|
|
57
130
|
const vscodeInput = {
|
|
58
131
|
type: "promptString",
|
|
59
132
|
id: INPUT_ID,
|
|
60
|
-
description: "Hue
|
|
133
|
+
description: "Hue Read or Read and write API key",
|
|
61
134
|
password: true,
|
|
62
135
|
};
|
|
63
136
|
const windsurfServer = {
|
|
@@ -80,15 +153,15 @@ export function renderMcpSnippets(url) {
|
|
|
80
153
|
"--header",
|
|
81
154
|
`Authorization: Bearer \${${ENV_VAR}}`,
|
|
82
155
|
],
|
|
83
|
-
display: `claude mcp add --transport http --scope user ${SERVER_NAME} ${url} --header 'Authorization: Bearer \${${ENV_VAR}}'`,
|
|
156
|
+
display: `claude mcp add --transport http --scope user ${SERVER_NAME} ${shellWord(url)} --header 'Authorization: Bearer \${${ENV_VAR}}'`,
|
|
84
157
|
},
|
|
85
158
|
cursorServer,
|
|
86
159
|
cursorJson: json({ mcpServers: { [SERVER_NAME]: cursorServer } }),
|
|
87
160
|
codexCli: {
|
|
88
161
|
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}`,
|
|
162
|
+
display: `codex mcp add ${SERVER_NAME} --url ${shellWord(url)} --bearer-token-env-var ${ENV_VAR}`,
|
|
90
163
|
},
|
|
91
|
-
codexToml: `[mcp_servers.${SERVER_NAME}]\nurl = "${url}"\nbearer_token_env_var = "${ENV_VAR}"\n`,
|
|
164
|
+
codexToml: `[mcp_servers.${SERVER_NAME}]\nurl = "${url}"\nbearer_token_env_var = "${ENV_VAR}"\n${CODEX_PARALLEL}\n`,
|
|
92
165
|
vscodeServer,
|
|
93
166
|
vscodeInput,
|
|
94
167
|
vscodeJson: json({ servers: { [SERVER_NAME]: vscodeServer }, inputs: [vscodeInput] }),
|
|
@@ -98,19 +171,78 @@ export function renderMcpSnippets(url) {
|
|
|
98
171
|
args: [
|
|
99
172
|
"mcp",
|
|
100
173
|
"add",
|
|
174
|
+
"--scope",
|
|
175
|
+
"user",
|
|
101
176
|
"--transport",
|
|
102
177
|
"http",
|
|
103
178
|
SERVER_NAME,
|
|
104
179
|
url,
|
|
105
|
-
"
|
|
106
|
-
`Authorization: Bearer
|
|
180
|
+
"--header",
|
|
181
|
+
`Authorization: Bearer \${${ENV_VAR}}`,
|
|
107
182
|
],
|
|
108
183
|
// 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}
|
|
184
|
+
// key is exported; Gemini CLI expands ${HUE_MCP_KEY} from its settings at connection time.
|
|
185
|
+
display: `gemini mcp add --scope user --transport http ${SERVER_NAME} ${shellWord(url)} --header 'Authorization: Bearer \${${ENV_VAR}}'`,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Sign-in snippets: the server URL and nothing secret. The client discovers Hue's OAuth metadata
|
|
191
|
+
* from it and sends it, query included, on every request; the token's resource ignores the query,
|
|
192
|
+
* so `?toolsets=` selects tools here as it does for a key.
|
|
193
|
+
*/
|
|
194
|
+
export function renderMcpSignInSnippets(url) {
|
|
195
|
+
const toolsets = new URL(url).searchParams.get("toolsets");
|
|
196
|
+
const claudeCodeServer = { type: "http", url };
|
|
197
|
+
return {
|
|
198
|
+
claudeCodeServer,
|
|
199
|
+
claudeCodeProjectJson: json({ mcpServers: { [SERVER_NAME]: claudeCodeServer } }),
|
|
200
|
+
claudeCodeCli: {
|
|
201
|
+
args: ["mcp", "add", "--transport", "http", "--scope", "user", SERVER_NAME, url],
|
|
202
|
+
display: `claude mcp add --transport http --scope user ${SERVER_NAME} ${shellWord(url)}`,
|
|
203
|
+
},
|
|
204
|
+
codexCli: {
|
|
205
|
+
args: ["mcp", "add", SERVER_NAME, "--url", url],
|
|
206
|
+
display: `codex mcp add ${SERVER_NAME} --url ${shellWord(url)}`,
|
|
111
207
|
},
|
|
208
|
+
// The header form of the same selection, for a TOML entry that keeps its URL bare.
|
|
209
|
+
codexToml: `[mcp_servers.${SERVER_NAME}]\nurl = "${toolsetsMcpUrl(url, undefined)}"\n${toolsets ? `http_headers = { "${TOOLSETS_HEADER}" = "${toolsets}" }\n` : ""}${CODEX_PARALLEL}\n`,
|
|
112
210
|
};
|
|
113
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Parses a comma-separated `--toolsets` value into Hue's canonical form, or returns an error.
|
|
214
|
+
* Unknown names are refused: Hue ignores them and would list its default, hiding a typo.
|
|
215
|
+
*/
|
|
216
|
+
export function parseToolsets(value) {
|
|
217
|
+
const names = [...new Set(value.split(",").map((name) => name.trim()))];
|
|
218
|
+
if (names.some((name) => !name))
|
|
219
|
+
return { error: "--toolsets takes comma-separated names, such as observe or traces,docs." };
|
|
220
|
+
const unknown = names.filter((name) => !MCP_TOOLSETS.includes(name));
|
|
221
|
+
if (unknown.length)
|
|
222
|
+
return {
|
|
223
|
+
error: `Unknown toolset${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}. Choose from ${MCP_TOOLSETS.join(", ")}.`,
|
|
224
|
+
};
|
|
225
|
+
return { toolsets: names.join(",") };
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* `?toolsets=` lists only the selected tools; commas stay readable. Without a selection the
|
|
229
|
+
* parameter is removed, so the connection lists Hue's default.
|
|
230
|
+
*/
|
|
231
|
+
export function toolsetsMcpUrl(url, toolsets) {
|
|
232
|
+
const parsed = new URL(url);
|
|
233
|
+
parsed.searchParams.delete("toolsets");
|
|
234
|
+
if (!toolsets)
|
|
235
|
+
return parsed.href;
|
|
236
|
+
const query = parsed.search.slice(1);
|
|
237
|
+
parsed.search = `${query ? `${query}&` : ""}toolsets=${toolsets}`;
|
|
238
|
+
return parsed.href;
|
|
239
|
+
}
|
|
240
|
+
/** `?read_only=true` hides and rejects Hue's write tools for any key. */
|
|
241
|
+
export function readOnlyMcpUrl(url) {
|
|
242
|
+
const parsed = new URL(url);
|
|
243
|
+
parsed.searchParams.set("read_only", "true");
|
|
244
|
+
return parsed.href;
|
|
245
|
+
}
|
|
114
246
|
class ConfigError extends Error {
|
|
115
247
|
constructor(message) {
|
|
116
248
|
super(message);
|
|
@@ -264,6 +396,9 @@ function parseMcpArguments(argv) {
|
|
|
264
396
|
strict: true,
|
|
265
397
|
options: {
|
|
266
398
|
client: { type: "string" },
|
|
399
|
+
auth: { type: "string" },
|
|
400
|
+
"read-only": { type: "boolean", default: false },
|
|
401
|
+
toolsets: { type: "string" },
|
|
267
402
|
url: { type: "string" },
|
|
268
403
|
scope: { type: "string" },
|
|
269
404
|
"dry-run": { type: "boolean", default: false },
|
|
@@ -276,26 +411,48 @@ function displayPath(cwd, path) {
|
|
|
276
411
|
const shown = relative(cwd, path);
|
|
277
412
|
return shown && !shown.startsWith("..") && !isAbsolute(shown) ? shown : path;
|
|
278
413
|
}
|
|
279
|
-
|
|
414
|
+
const notOnPath = (executable, label) => `${executable} is not on PATH. Run this where ${label} is installed:`;
|
|
415
|
+
/** `auth: "oauth"` is only planned for {@link OAUTH_CLIENTS}; the caller refuses the others. */
|
|
416
|
+
function planFor(client, auth, scope, url) {
|
|
280
417
|
const snippets = renderMcpSnippets(url);
|
|
418
|
+
// Claude Code and Codex take the same plan shape with or without a key.
|
|
419
|
+
const shared = auth === "oauth" ? renderMcpSignInSnippets(url) : snippets;
|
|
420
|
+
const claudeUser = {
|
|
421
|
+
executable: "claude",
|
|
422
|
+
label: "Claude Code",
|
|
423
|
+
...shared.claudeCodeCli,
|
|
424
|
+
fallback: [notOnPath("claude", "Claude Code")],
|
|
425
|
+
};
|
|
426
|
+
const codex = {
|
|
427
|
+
executable: "codex",
|
|
428
|
+
label: "Codex",
|
|
429
|
+
...shared.codexCli,
|
|
430
|
+
fallback: [
|
|
431
|
+
"codex is not on PATH. Add this to ~/.codex/config.toml (or run the command where Codex is installed):",
|
|
432
|
+
shared.codexToml.trimEnd(),
|
|
433
|
+
],
|
|
434
|
+
};
|
|
281
435
|
switch (client) {
|
|
282
436
|
case "claude-code":
|
|
283
437
|
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
|
-
}
|
|
438
|
+
? { kind: "cli", commands: [claudeUser], snippet: `${claudeUser.display}\n` }
|
|
292
439
|
: {
|
|
293
440
|
kind: "file",
|
|
294
441
|
file: ".mcp.json",
|
|
295
442
|
key: "mcpServers",
|
|
296
|
-
entry:
|
|
297
|
-
snippet:
|
|
443
|
+
entry: shared.claudeCodeServer,
|
|
444
|
+
snippet: shared.claudeCodeProjectJson,
|
|
298
445
|
};
|
|
446
|
+
case "codex":
|
|
447
|
+
return { kind: "cli", commands: [codex], snippet: shared.codexToml };
|
|
448
|
+
case "conductor":
|
|
449
|
+
// Conductor has no MCP configuration of its own: its Claude Code and Codex agents read their
|
|
450
|
+
// user configuration in every workspace.
|
|
451
|
+
return {
|
|
452
|
+
kind: "cli",
|
|
453
|
+
commands: [claudeUser, codex],
|
|
454
|
+
snippet: `${claudeUser.display}\n${codex.display}\n`,
|
|
455
|
+
};
|
|
299
456
|
case "cursor":
|
|
300
457
|
return {
|
|
301
458
|
kind: "file",
|
|
@@ -304,18 +461,6 @@ function planFor(client, scope, url) {
|
|
|
304
461
|
entry: snippets.cursorServer,
|
|
305
462
|
snippet: snippets.cursorJson,
|
|
306
463
|
};
|
|
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
464
|
case "vscode":
|
|
320
465
|
return {
|
|
321
466
|
kind: "file",
|
|
@@ -331,27 +476,51 @@ function planFor(client, scope, url) {
|
|
|
331
476
|
snippet: snippets.windsurfJson,
|
|
332
477
|
hint: "Merge this into ~/.codeium/windsurf/mcp_config.json (Windsurf > Settings > MCP); the command does not write to your home directory.",
|
|
333
478
|
};
|
|
334
|
-
case "gemini":
|
|
335
|
-
|
|
336
|
-
kind: "cli",
|
|
479
|
+
case "gemini": {
|
|
480
|
+
const gemini = {
|
|
337
481
|
executable: "gemini",
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
fallback: ["gemini is not on PATH. Run this where Gemini CLI is installed:"],
|
|
482
|
+
label: "Gemini CLI",
|
|
483
|
+
...snippets.geminiCli,
|
|
484
|
+
fallback: [notOnPath("gemini", "Gemini CLI")],
|
|
342
485
|
};
|
|
486
|
+
return { kind: "cli", commands: [gemini], snippet: `${gemini.display}\n` };
|
|
487
|
+
}
|
|
343
488
|
}
|
|
344
489
|
}
|
|
345
|
-
function nextSteps(client) {
|
|
490
|
+
function nextSteps(client, auth) {
|
|
346
491
|
const label = CLIENT_LABELS[client];
|
|
347
|
-
const
|
|
348
|
-
|
|
492
|
+
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.";
|
|
493
|
+
let lines;
|
|
494
|
+
if (auth === "oauth")
|
|
495
|
+
lines =
|
|
496
|
+
client === "claude-code"
|
|
497
|
+
? [`In Claude Code, run /mcp, select ${SERVER_NAME} and choose Authenticate. ${approve}`]
|
|
498
|
+
: client === "codex"
|
|
499
|
+
? [
|
|
500
|
+
`If Codex did not open Hue in your browser, run: codex mcp login ${SERVER_NAME}`,
|
|
501
|
+
approve,
|
|
502
|
+
]
|
|
503
|
+
: [
|
|
504
|
+
`In Conductor, open MCP status from the plug icon or /mcp-status, refresh, and use ${SERVER_NAME}'s authentication action. ${approve}`,
|
|
505
|
+
"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.",
|
|
506
|
+
];
|
|
507
|
+
else if (client === "vscode")
|
|
508
|
+
lines = [
|
|
349
509
|
`${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
|
-
|
|
510
|
+
];
|
|
511
|
+
else if (client === "conductor")
|
|
512
|
+
lines = [
|
|
513
|
+
`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.`,
|
|
514
|
+
];
|
|
515
|
+
else
|
|
516
|
+
lines = [
|
|
352
517
|
`Export ${ENV_VAR} in the shell that starts ${label}; hue login stores it in .env.hue:`,
|
|
353
518
|
" set -a; . ./.env.hue; set +a",
|
|
519
|
+
`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
520
|
];
|
|
521
|
+
// `codex mcp add` has no option for it, so a registered server gets the line by hand.
|
|
522
|
+
if (client === "codex" || client === "conductor")
|
|
523
|
+
lines.push(`If codex mcp add registered ${SERVER_NAME}, add this line under [mcp_servers.${SERVER_NAME}] in ~/.codex/config.toml so Codex runs Hue's tool calls in parallel:`, ` ${CODEX_PARALLEL}`);
|
|
355
524
|
return [...lines, "Then ask your agent:", ` ${MCP_VERIFY_PROMPT}`];
|
|
356
525
|
}
|
|
357
526
|
/**
|
|
@@ -387,20 +556,45 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
387
556
|
const client = parsed.values.client;
|
|
388
557
|
if (!client || !CLIENT_IDS.includes(client))
|
|
389
558
|
return fail(`${client ? `Unknown client: ${client}.` : "--client is required."} Choose one of ${CLIENT_IDS.join(", ")}.\n\n${MCP_USAGE}`, 2);
|
|
559
|
+
const clientId = client;
|
|
560
|
+
const auth = parsed.values.auth ?? (clientId === "conductor" ? "oauth" : "key");
|
|
561
|
+
if (auth !== "key" && auth !== "oauth")
|
|
562
|
+
return fail(`--auth must be key or oauth.\n\n${MCP_USAGE}`, 2);
|
|
563
|
+
if (auth === "oauth" && !OAUTH_CLIENTS.includes(clientId))
|
|
564
|
+
return fail(clientId === "cursor"
|
|
565
|
+
? "--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."
|
|
566
|
+
: `--auth oauth is available for ${OAUTH_CLIENTS.join(", ")}. Use a key (the default) for ${clientId}.`, 2);
|
|
390
567
|
const scope = parsed.values.scope ?? "project";
|
|
391
568
|
if (scope !== "project" && scope !== "user")
|
|
392
569
|
return fail(`--scope must be project or user.\n\n${MCP_USAGE}`, 2);
|
|
393
|
-
if (scope === "user" &&
|
|
570
|
+
if (scope === "user" && clientId !== "claude-code")
|
|
394
571
|
return fail("--scope user is only available with --client claude-code.", 2);
|
|
395
|
-
|
|
572
|
+
let url = parseMcpUrl(parsed.values.url ?? DEFAULT_MCP_URL);
|
|
396
573
|
if (!url)
|
|
397
574
|
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
|
-
|
|
575
|
+
// A sign-in connection's access is chosen when it is approved; read_only is not part of it.
|
|
576
|
+
if (auth === "oauth" &&
|
|
577
|
+
(parsed.values["read-only"] || new URL(url).searchParams.has("read_only")))
|
|
578
|
+
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);
|
|
579
|
+
if (parsed.values["read-only"])
|
|
580
|
+
url = readOnlyMcpUrl(url);
|
|
581
|
+
// --toolsets wins over a selection already in --url, which wins over the client's default.
|
|
582
|
+
// Both sources are validated: Hue ignores an unknown name and would list its default.
|
|
583
|
+
const requested = parsed.values.toolsets ?? new URL(url).searchParams.get("toolsets");
|
|
584
|
+
let toolsets = DEFAULT_TOOLSETS[clientId];
|
|
585
|
+
if (requested !== null) {
|
|
586
|
+
const selected = parseToolsets(requested);
|
|
587
|
+
if ("error" in selected)
|
|
588
|
+
return fail(`${parsed.values.toolsets === undefined ? "In --url: " : ""}${selected.error}\n\n${MCP_USAGE}`, 2);
|
|
589
|
+
toolsets = selected.toolsets;
|
|
590
|
+
}
|
|
591
|
+
url = toolsetsMcpUrl(url, toolsets);
|
|
592
|
+
const plan = planFor(clientId, auth, scope, url);
|
|
399
593
|
if (parsed.values.print) {
|
|
400
594
|
stdout.write(plan.snippet);
|
|
401
595
|
return 0;
|
|
402
596
|
}
|
|
403
|
-
const steps = nextSteps(
|
|
597
|
+
const steps = nextSteps(clientId, auth);
|
|
404
598
|
if (plan.kind === "manual") {
|
|
405
599
|
out(plan.hint);
|
|
406
600
|
stdout.write(plan.snippet);
|
|
@@ -410,25 +604,30 @@ export async function runMcpCommand(argv, io = {}) {
|
|
|
410
604
|
}
|
|
411
605
|
if (plan.kind === "cli") {
|
|
412
606
|
if (parsed.values["dry-run"]) {
|
|
413
|
-
|
|
607
|
+
for (const command of plan.commands)
|
|
608
|
+
out(`Would run: ${command.display}`);
|
|
414
609
|
return 0;
|
|
415
610
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
out(
|
|
423
|
-
|
|
611
|
+
let failed = false;
|
|
612
|
+
for (const command of plan.commands) {
|
|
613
|
+
const executable = await findExecutable(command.executable, env);
|
|
614
|
+
if (!executable) {
|
|
615
|
+
for (const line of command.fallback)
|
|
616
|
+
out(line);
|
|
617
|
+
out(command.display);
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
out(`Running: ${command.display}`);
|
|
621
|
+
const code = await runClientCli(executable, command.args, { cwd, env, stdout, stderr });
|
|
622
|
+
if (code === 0) {
|
|
623
|
+
out(`Registered the "${SERVER_NAME}" MCP server (${url}) with ${command.label}.`);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
failed = true;
|
|
627
|
+
stderr.write(`${command.executable} ${code === null ? "could not be started" : `exited with code ${code}`}. Run this command yourself:\n${command.display}\n`);
|
|
424
628
|
}
|
|
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]}.`);
|
|
629
|
+
if (failed)
|
|
630
|
+
return 1;
|
|
432
631
|
for (const line of steps)
|
|
433
632
|
out(line);
|
|
434
633
|
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.
|