@yagni-app/code-staging 1.0.9-staging.1284.1 → 1.0.9-staging.1288.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/dist/extension/mcp/cliConfig.d.ts +1 -1
- package/dist/extension/mcp/cliConfig.js +1 -1
- package/dist/extension/mcp/config.d.ts +2 -0
- package/dist/extension/mcp/config.js +2 -0
- package/dist/extension/mcp/tools.js +69 -6
- package/dist/mcpCommand.d.ts +7 -0
- package/dist/mcpCommand.js +161 -18
- package/package.json +2 -2
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
export { McpServerConfig, McpScope, PLUGIN_MCP_ENV, PROJECT_CONFIG_FILENAME, ScopedMcpServerConfig, expandEnvVarsInString, expandServerEnv, loadMcpServers, mcpConfigPath, readPluginMcpServers, readProjectMcpConfig, readUserMcpConfig, resolveProjectRoot, validateServerConfig, writeUserMcpConfig, } from "./config.js";
|
|
8
8
|
export { ProjectApprovalState, decisionFor, readProjectApproval, recordProjectDecision, resetProjectChoices, undecidedProjectServers, } from "./approval.js";
|
|
9
9
|
export { McpAuthFile, StoredOAuthEntry, deleteStoredOAuthEntry, getServerKey, getStoredOAuthEntry, mcpAuthPath, readMcpAuth, updateStoredOAuthEntry, writeMcpAuth, } from "./authStore.js";
|
|
10
|
-
export { revokeTokensOnRemove } from "./auth.js";
|
|
10
|
+
export { authenticate, revokeTokensOnRemove } from "./auth.js";
|
|
11
11
|
export { probeServer, McpHealthResult, McpHealthStatus } from "./manager.js";
|
|
12
12
|
//# sourceMappingURL=cliConfig.d.ts.map
|
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
export { PLUGIN_MCP_ENV, PROJECT_CONFIG_FILENAME, expandEnvVarsInString, expandServerEnv, loadMcpServers, mcpConfigPath, readPluginMcpServers, readProjectMcpConfig, readUserMcpConfig, resolveProjectRoot, validateServerConfig, writeUserMcpConfig, } from "./config.js";
|
|
8
8
|
export { decisionFor, readProjectApproval, recordProjectDecision, resetProjectChoices, undecidedProjectServers, } from "./approval.js";
|
|
9
9
|
export { deleteStoredOAuthEntry, getServerKey, getStoredOAuthEntry, mcpAuthPath, readMcpAuth, updateStoredOAuthEntry, writeMcpAuth, } from "./authStore.js";
|
|
10
|
-
export { revokeTokensOnRemove } from "./auth.js";
|
|
10
|
+
export { authenticate, revokeTokensOnRemove } from "./auth.js";
|
|
11
11
|
export { probeServer } from "./manager.js";
|
|
12
12
|
//# sourceMappingURL=cliConfig.js.map
|
|
@@ -36,6 +36,8 @@ export interface McpHttpServerConfig {
|
|
|
36
36
|
url: string;
|
|
37
37
|
headers?: Record<string, string>;
|
|
38
38
|
oauth?: McpOAuthConfig;
|
|
39
|
+
/** Optional explicit tool names, used by the Worker connection to avoid duplicating existing context tools. */
|
|
40
|
+
tools?: string[];
|
|
39
41
|
}
|
|
40
42
|
export type McpServerConfig = McpStdioServerConfig | McpHttpServerConfig;
|
|
41
43
|
export interface ScopedMcpServerConfig {
|
|
@@ -267,6 +267,8 @@ export function validateServerConfig(value) {
|
|
|
267
267
|
return { ok: true };
|
|
268
268
|
}
|
|
269
269
|
if (type === "http" || type === "sse") {
|
|
270
|
+
if (v["tools"] !== undefined && (!Array.isArray(v["tools"]) || v["tools"].length > 100 || v["tools"].some(name => typeof name !== "string" || name.length === 0 || name.length > 128)))
|
|
271
|
+
return { ok: false, message: "tools must be an array of up to 100 tool names" };
|
|
270
272
|
if (typeof v["url"] !== "string" || v["url"].length === 0) {
|
|
271
273
|
return { ok: false, message: `${type} server requires a non-empty "url"` };
|
|
272
274
|
}
|
|
@@ -14,7 +14,11 @@ import { buildMcpToolName } from "./names.js";
|
|
|
14
14
|
export const MAX_MCP_DESCRIPTION_LENGTH = 2048;
|
|
15
15
|
export async function registerServerTools(pi, manager, serverName, env = process.env) {
|
|
16
16
|
const server = manager.get(serverName);
|
|
17
|
-
const result = {
|
|
17
|
+
const result = {
|
|
18
|
+
tools: [],
|
|
19
|
+
mutatingToolNames: [],
|
|
20
|
+
warnings: [],
|
|
21
|
+
};
|
|
18
22
|
if (!server?.client)
|
|
19
23
|
return result;
|
|
20
24
|
let toolList;
|
|
@@ -26,7 +30,12 @@ export async function registerServerTools(pi, manager, serverName, env = process
|
|
|
26
30
|
return result;
|
|
27
31
|
}
|
|
28
32
|
const toolTimeout = toolTimeoutFromEnv(env);
|
|
33
|
+
const selectedTools = server.config.type === "http" || server.config.type === "sse"
|
|
34
|
+
? server.config.tools
|
|
35
|
+
: undefined;
|
|
29
36
|
for (const tool of toolList.tools ?? []) {
|
|
37
|
+
if (selectedTools && !selectedTools.includes(tool.name))
|
|
38
|
+
continue;
|
|
30
39
|
const fullToolName = buildMcpToolName(serverName, tool.name);
|
|
31
40
|
if (!fullToolName) {
|
|
32
41
|
result.warnings.push(`${serverName}: tool "${tool.name}" produced an empty wire name; skipped`);
|
|
@@ -45,7 +54,10 @@ export async function registerServerTools(pi, manager, serverName, env = process
|
|
|
45
54
|
throw new Error(`MCP server "${serverName}" is not connected (try /mcp reconnect).`);
|
|
46
55
|
}
|
|
47
56
|
const args = (params && typeof params === "object" ? params : {});
|
|
48
|
-
const call = active.client.callTool({
|
|
57
|
+
const call = active.client.callTool({
|
|
58
|
+
name: tool.name,
|
|
59
|
+
arguments: args,
|
|
60
|
+
});
|
|
49
61
|
const settled = toolTimeout
|
|
50
62
|
? await withTimeout(call, toolTimeout, `MCP tool call timed out after ${toolTimeout}ms`)
|
|
51
63
|
: await call;
|
|
@@ -53,7 +65,12 @@ export async function registerServerTools(pi, manager, serverName, env = process
|
|
|
53
65
|
},
|
|
54
66
|
};
|
|
55
67
|
pi.registerTool(definition);
|
|
56
|
-
result.tools.push({
|
|
68
|
+
result.tools.push({
|
|
69
|
+
toolName: fullToolName,
|
|
70
|
+
serverName,
|
|
71
|
+
originalName: tool.name,
|
|
72
|
+
description,
|
|
73
|
+
});
|
|
57
74
|
if (mutating)
|
|
58
75
|
result.mutatingToolNames.push(fullToolName);
|
|
59
76
|
}
|
|
@@ -67,6 +84,19 @@ export function capDescription(description) {
|
|
|
67
84
|
/** Cheap heuristic in the spirit of Claude Code's input-hint check; per-tool annotations arrive via listTools only in newer servers. */
|
|
68
85
|
export function looksMutating(toolName, description) {
|
|
69
86
|
const name = toolName.toLowerCase();
|
|
87
|
+
if ([
|
|
88
|
+
"critique_plan",
|
|
89
|
+
"review_pr",
|
|
90
|
+
"test_pr",
|
|
91
|
+
"validate_qa_replay",
|
|
92
|
+
"accept_qa_replay",
|
|
93
|
+
"authorize_qa_fork",
|
|
94
|
+
"engage_worker",
|
|
95
|
+
"propose_instruction_change",
|
|
96
|
+
"prepare_review_publication",
|
|
97
|
+
"resolve_decision",
|
|
98
|
+
].includes(name))
|
|
99
|
+
return true;
|
|
70
100
|
const writeHints = /^(create|add|update|edit|delete|remove|set|write|send|post|put|patch|deploy|publish|close|merge|assign|move|archive|trash|restore)/;
|
|
71
101
|
if (writeHints.test(name))
|
|
72
102
|
return true;
|
|
@@ -91,16 +121,49 @@ function schemaFor(inputSchema) {
|
|
|
91
121
|
}
|
|
92
122
|
function renderCallResult(settled, serverName, toolName) {
|
|
93
123
|
const parts = [];
|
|
124
|
+
const images = [];
|
|
125
|
+
let imageBytes = 0;
|
|
94
126
|
for (const item of settled.content ?? []) {
|
|
95
|
-
if (item &&
|
|
127
|
+
if (item &&
|
|
128
|
+
typeof item === "object" &&
|
|
129
|
+
item.type === "text") {
|
|
96
130
|
parts.push(String(item.text ?? ""));
|
|
97
131
|
}
|
|
132
|
+
else if (item &&
|
|
133
|
+
typeof item === "object" &&
|
|
134
|
+
"type" in item &&
|
|
135
|
+
item.type === "image") {
|
|
136
|
+
const value = item;
|
|
137
|
+
if (typeof value.data !== "string" ||
|
|
138
|
+
typeof value.mimeType !== "string" ||
|
|
139
|
+
!["image/png", "image/jpeg", "image/webp"].includes(value.mimeType) ||
|
|
140
|
+
value.data.length > 12 * 1024 * 1024 ||
|
|
141
|
+
images.length >= 8 ||
|
|
142
|
+
value.data.length % 4 !== 0 ||
|
|
143
|
+
!/^[A-Za-z0-9+/]*={0,2}$/.test(value.data))
|
|
144
|
+
throw new Error("MCP image is unsupported or exceeds its limit.");
|
|
145
|
+
const bytes = Buffer.byteLength(value.data, "base64");
|
|
146
|
+
imageBytes += bytes;
|
|
147
|
+
if (!bytes || imageBytes > 8 * 1024 * 1024)
|
|
148
|
+
throw new Error("MCP image is unsupported or exceeds its limit.");
|
|
149
|
+
images.push({
|
|
150
|
+
type: "image",
|
|
151
|
+
data: value.data,
|
|
152
|
+
mimeType: value.mimeType,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
98
155
|
}
|
|
99
|
-
const text = parts.join("\n") ||
|
|
156
|
+
const text = parts.join("\n") ||
|
|
157
|
+
(images.length
|
|
158
|
+
? `Image returned by ${serverName}.${toolName} (untrusted tool content).`
|
|
159
|
+
: "(no text content)");
|
|
100
160
|
if (settled.isError === true) {
|
|
101
161
|
throw new Error(`MCP tool error from "${serverName}.${toolName}": ${text}`);
|
|
102
162
|
}
|
|
103
|
-
return {
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: "text", text }, ...images],
|
|
165
|
+
details: { server: serverName, tool: toolName },
|
|
166
|
+
};
|
|
104
167
|
}
|
|
105
168
|
function withTimeout(promise, ms, message) {
|
|
106
169
|
return new Promise((resolve, reject) => {
|
package/dist/mcpCommand.d.ts
CHANGED
|
@@ -38,6 +38,13 @@ export interface McpDeps {
|
|
|
38
38
|
}
|
|
39
39
|
/** The structural slice of the extension's mcp config surface this file needs. */
|
|
40
40
|
export interface McpCliModule {
|
|
41
|
+
authenticate?(serverName: string, config: {
|
|
42
|
+
type: "http";
|
|
43
|
+
url: string;
|
|
44
|
+
tools: string[];
|
|
45
|
+
}): Promise<{
|
|
46
|
+
result: "AUTHORIZED";
|
|
47
|
+
}>;
|
|
41
48
|
loadMcpServers(cwd: string, env: NodeJS.ProcessEnv): McpLoadResult;
|
|
42
49
|
mcpConfigPath(): string;
|
|
43
50
|
PROJECT_CONFIG_FILENAME: string;
|
package/dist/mcpCommand.js
CHANGED
|
@@ -21,7 +21,7 @@ import { fileURLToPath } from "node:url";
|
|
|
21
21
|
import { CLAUDE_PLUGIN_MCP_ENV, claudeCompatArgs } from "./claudeCompat.js";
|
|
22
22
|
import { agentDir } from "./credentials.js";
|
|
23
23
|
import { DISTRIBUTION } from "./distribution.js";
|
|
24
|
-
import { getActiveProfileName } from "./profiles.js";
|
|
24
|
+
import { getActiveProfileName, readActiveProfile } from "./profiles.js";
|
|
25
25
|
export function resolveMcpConfigPath() {
|
|
26
26
|
const bundled = fileURLToPath(new URL("./extension/mcp/cliConfig.js", import.meta.url));
|
|
27
27
|
if (existsSync(bundled))
|
|
@@ -55,6 +55,9 @@ Scopes:
|
|
|
55
55
|
user available in all your projects (~/.yagni-code/mcp.json)
|
|
56
56
|
project shared via .mcp.json at the repo root (approval-gated)
|
|
57
57
|
|
|
58
|
+
Connect YAGNI Workers:
|
|
59
|
+
${DISTRIBUTION.commandName} mcp connect-workers Browser consent for the active environment
|
|
60
|
+
|
|
58
61
|
Examples:
|
|
59
62
|
${DISTRIBUTION.commandName} mcp add --transport http sentry https://mcp.sentry.dev/mcp
|
|
60
63
|
${DISTRIBUTION.commandName} mcp add -e API_KEY=xxx my-server -- npx my-mcp-server
|
|
@@ -90,7 +93,10 @@ export function parseMcpArgs(argv) {
|
|
|
90
93
|
rest.push(...after.slice(i + 1));
|
|
91
94
|
break;
|
|
92
95
|
}
|
|
93
|
-
if (flag === "s" ||
|
|
96
|
+
if (flag === "s" ||
|
|
97
|
+
flag === "t" ||
|
|
98
|
+
flag === "client-id" ||
|
|
99
|
+
flag === "callback-port") {
|
|
94
100
|
if (flag === "s") {
|
|
95
101
|
scope = scopeFrom(arg);
|
|
96
102
|
scopeExplicit = true;
|
|
@@ -204,11 +210,28 @@ export function parseMcpArgs(argv) {
|
|
|
204
210
|
}
|
|
205
211
|
rest.push(arg);
|
|
206
212
|
}
|
|
207
|
-
if (flag === "s" ||
|
|
213
|
+
if (flag === "s" ||
|
|
214
|
+
flag === "t" ||
|
|
215
|
+
flag === "client-id" ||
|
|
216
|
+
flag === "callback-port") {
|
|
208
217
|
throw new Error("Missing flag value.");
|
|
209
218
|
}
|
|
210
219
|
const [name, ...commandArgs] = rest;
|
|
211
|
-
return {
|
|
220
|
+
return {
|
|
221
|
+
subcommand,
|
|
222
|
+
name,
|
|
223
|
+
rest,
|
|
224
|
+
scope,
|
|
225
|
+
scopeExplicit,
|
|
226
|
+
transport,
|
|
227
|
+
transportExplicit,
|
|
228
|
+
env,
|
|
229
|
+
headers,
|
|
230
|
+
commandArgs,
|
|
231
|
+
clientId,
|
|
232
|
+
clientSecret,
|
|
233
|
+
callbackPort,
|
|
234
|
+
};
|
|
212
235
|
}
|
|
213
236
|
function scopeFrom(value) {
|
|
214
237
|
if (value === "local" || value === "user" || value === "project")
|
|
@@ -236,7 +259,11 @@ function describeScopePath(scope, cwd) {
|
|
|
236
259
|
async function defaultPluginMcpEnv(cwd) {
|
|
237
260
|
try {
|
|
238
261
|
const profile = await getActiveProfileName();
|
|
239
|
-
const compat = await claudeCompatArgs({
|
|
262
|
+
const compat = await claudeCompatArgs({
|
|
263
|
+
cwd,
|
|
264
|
+
agentDir: agentDir(profile),
|
|
265
|
+
interactive: false,
|
|
266
|
+
});
|
|
240
267
|
return compat.env[CLAUDE_PLUGIN_MCP_ENV];
|
|
241
268
|
}
|
|
242
269
|
catch {
|
|
@@ -272,6 +299,8 @@ export async function mcpCommand(args, deps = {}) {
|
|
|
272
299
|
return 1;
|
|
273
300
|
}
|
|
274
301
|
switch (parsed.subcommand) {
|
|
302
|
+
case "connect-workers":
|
|
303
|
+
return connectWorkers(mod, parsed, { cwd, stdout, stderr }, deps.env ?? process.env);
|
|
275
304
|
case undefined:
|
|
276
305
|
case "help":
|
|
277
306
|
stdout(USAGE);
|
|
@@ -283,18 +312,105 @@ export async function mcpCommand(args, deps = {}) {
|
|
|
283
312
|
case "remove":
|
|
284
313
|
return mcpRemove(mod, parsed, { cwd, stdout, stderr });
|
|
285
314
|
case "list":
|
|
286
|
-
return mcpList(mod, {
|
|
315
|
+
return mcpList(mod, {
|
|
316
|
+
cwd,
|
|
317
|
+
stdout,
|
|
318
|
+
stderr,
|
|
319
|
+
env: await envWithPluginMcp(deps, cwd),
|
|
320
|
+
probeServer: deps.probeServer,
|
|
321
|
+
});
|
|
287
322
|
case "get":
|
|
288
|
-
return mcpGet(mod, parsed, {
|
|
323
|
+
return mcpGet(mod, parsed, {
|
|
324
|
+
cwd,
|
|
325
|
+
stdout,
|
|
326
|
+
stderr,
|
|
327
|
+
env: await envWithPluginMcp(deps, cwd),
|
|
328
|
+
probeServer: deps.probeServer,
|
|
329
|
+
});
|
|
289
330
|
case "reset-project-choices":
|
|
290
331
|
return mcpResetChoices(mod, { cwd, stdout, stderr });
|
|
291
332
|
case "add-from-claude":
|
|
292
|
-
return mcpAddFromClaude(mod, parsed, {
|
|
333
|
+
return mcpAddFromClaude(mod, parsed, {
|
|
334
|
+
cwd,
|
|
335
|
+
stdout,
|
|
336
|
+
stderr,
|
|
337
|
+
home: deps.home ?? homedir(),
|
|
338
|
+
});
|
|
293
339
|
default:
|
|
294
340
|
stderr(`Unknown mcp subcommand "${parsed.subcommand}".\n${USAGE}`);
|
|
295
341
|
return 1;
|
|
296
342
|
}
|
|
297
343
|
}
|
|
344
|
+
const WORKER_TOOL_NAMES = [
|
|
345
|
+
"get_context",
|
|
346
|
+
"create_team",
|
|
347
|
+
"engage_worker",
|
|
348
|
+
"critique_plan",
|
|
349
|
+
"review_pr",
|
|
350
|
+
"test_pr",
|
|
351
|
+
"get_qa_evidence",
|
|
352
|
+
"get_qa_replay",
|
|
353
|
+
"validate_qa_replay",
|
|
354
|
+
"accept_qa_replay",
|
|
355
|
+
"authorize_qa_fork",
|
|
356
|
+
"list_work",
|
|
357
|
+
"get_work",
|
|
358
|
+
"add_feedback",
|
|
359
|
+
"propose_instruction_change",
|
|
360
|
+
"prepare_review_publication",
|
|
361
|
+
"resolve_decision",
|
|
362
|
+
];
|
|
363
|
+
async function connectWorkers(mod, parsed, io, env) {
|
|
364
|
+
if (parsed.rest.length ||
|
|
365
|
+
parsed.scopeExplicit ||
|
|
366
|
+
parsed.transportExplicit ||
|
|
367
|
+
Object.keys(parsed.headers).length ||
|
|
368
|
+
Object.keys(parsed.env).length ||
|
|
369
|
+
parsed.clientId ||
|
|
370
|
+
parsed.clientSecret ||
|
|
371
|
+
parsed.callbackPort) {
|
|
372
|
+
io.stderr("Usage: yagni mcp connect-workers (uses your active environment and private user configuration)\n");
|
|
373
|
+
return 1;
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
if (!mod.authenticate)
|
|
377
|
+
throw new Error("Update YAGNI Code to connect Workers");
|
|
378
|
+
const profile = await readActiveProfile(env);
|
|
379
|
+
const url = new URL("/mcp", profile.baseUrl);
|
|
380
|
+
if (url.username ||
|
|
381
|
+
url.password ||
|
|
382
|
+
(url.protocol !== "https:" &&
|
|
383
|
+
!(url.protocol === "http:" &&
|
|
384
|
+
["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))))
|
|
385
|
+
throw new Error("Worker connections require HTTPS or a local development server");
|
|
386
|
+
const config = {
|
|
387
|
+
type: "http",
|
|
388
|
+
url: url.href,
|
|
389
|
+
tools: WORKER_TOOL_NAMES,
|
|
390
|
+
};
|
|
391
|
+
const { file, errors } = mod.readUserMcpConfig();
|
|
392
|
+
if (errors.length)
|
|
393
|
+
throw new Error("Fix the existing MCP configuration before connecting Workers");
|
|
394
|
+
const previous = file
|
|
395
|
+
.mcpServers?.["yagni-workers"];
|
|
396
|
+
if (previous && JSON.stringify(previous) !== JSON.stringify(config))
|
|
397
|
+
throw new Error("An existing yagni-workers connection uses different settings. Remove it with yagni mcp remove yagni-workers before reconnecting");
|
|
398
|
+
const shadow = mod
|
|
399
|
+
.loadMcpServers(io.cwd, env)
|
|
400
|
+
.servers.find((server) => server.name === "yagni-workers" && server.scope !== "user");
|
|
401
|
+
if (shadow)
|
|
402
|
+
throw new Error("A project or local yagni-workers entry would override this connection. Remove that entry before connecting");
|
|
403
|
+
io.stdout("Opening YAGNI in your browser. Choose your workspace and permissions; paid Worker requests use that workspace's balance.\n");
|
|
404
|
+
await mod.authenticate("yagni-workers", config);
|
|
405
|
+
writeServerToScope(mod, "yagni-workers", config, "user", io.cwd);
|
|
406
|
+
io.stdout("Workers connected. Start a new YAGNI Code session to use them; /mcp shows connection status.\n");
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
io.stderr("Worker connection did not complete. Check your environment and existing yagni-workers configuration, then retry browser consent.\n");
|
|
411
|
+
return 1;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
298
414
|
function mcpAdd(mod, parsed, io) {
|
|
299
415
|
const { name, rest } = parsed;
|
|
300
416
|
const commandOrUrl = rest[1];
|
|
@@ -308,7 +424,9 @@ function mcpAdd(mod, parsed, io) {
|
|
|
308
424
|
serverConfig = {
|
|
309
425
|
type: parsed.transport,
|
|
310
426
|
url: commandOrUrl,
|
|
311
|
-
...(Object.keys(parsed.headers).length > 0
|
|
427
|
+
...(Object.keys(parsed.headers).length > 0
|
|
428
|
+
? { headers: parsed.headers }
|
|
429
|
+
: {}),
|
|
312
430
|
...(oauthBlock(parsed) ? { oauth: oauthBlock(parsed) } : {}),
|
|
313
431
|
};
|
|
314
432
|
}
|
|
@@ -398,18 +516,29 @@ function readClientSecret(io) {
|
|
|
398
516
|
function validateConfigShape(config) {
|
|
399
517
|
const type = config["type"];
|
|
400
518
|
if (type === undefined || type === "stdio") {
|
|
401
|
-
if (typeof config["command"] !== "string" ||
|
|
402
|
-
|
|
519
|
+
if (typeof config["command"] !== "string" ||
|
|
520
|
+
config["command"].length === 0) {
|
|
521
|
+
return {
|
|
522
|
+
ok: false,
|
|
523
|
+
message: 'stdio server requires a non-empty "command"',
|
|
524
|
+
};
|
|
403
525
|
}
|
|
404
526
|
return { ok: true };
|
|
405
527
|
}
|
|
406
528
|
if (type === "http" || type === "sse") {
|
|
407
|
-
if (typeof config["url"] !== "string" ||
|
|
408
|
-
|
|
529
|
+
if (typeof config["url"] !== "string" ||
|
|
530
|
+
config["url"].length === 0) {
|
|
531
|
+
return {
|
|
532
|
+
ok: false,
|
|
533
|
+
message: `${type} server requires a non-empty "url"`,
|
|
534
|
+
};
|
|
409
535
|
}
|
|
410
536
|
return { ok: true };
|
|
411
537
|
}
|
|
412
|
-
return {
|
|
538
|
+
return {
|
|
539
|
+
ok: false,
|
|
540
|
+
message: 'unknown "type" — expected stdio, http, or sse',
|
|
541
|
+
};
|
|
413
542
|
}
|
|
414
543
|
function writeServerToScope(mod, name, config, scope, cwd) {
|
|
415
544
|
if (scope === "project") {
|
|
@@ -584,7 +713,9 @@ async function mcpGet(mod, parsed, io) {
|
|
|
584
713
|
else {
|
|
585
714
|
io.stdout(` Type: stdio\n`);
|
|
586
715
|
io.stdout(` Command: ${config["command"]}\n`);
|
|
587
|
-
const args = Array.isArray(config["args"])
|
|
716
|
+
const args = Array.isArray(config["args"])
|
|
717
|
+
? config["args"]
|
|
718
|
+
: [];
|
|
588
719
|
if (args.length > 0)
|
|
589
720
|
io.stdout(` Args: ${args.join(" ")}\n`);
|
|
590
721
|
for (const [key, value] of Object.entries(config["env"] ?? {})) {
|
|
@@ -610,7 +741,9 @@ function printOAuthDetail(mod, name, config, stdout) {
|
|
|
610
741
|
const cfg = config;
|
|
611
742
|
const oauth = cfg["oauth"] ?? {};
|
|
612
743
|
const clientId = typeof oauth["clientId"] === "string" ? oauth["clientId"] : undefined;
|
|
613
|
-
const callbackPort = typeof oauth["callbackPort"] === "number"
|
|
744
|
+
const callbackPort = typeof oauth["callbackPort"] === "number"
|
|
745
|
+
? oauth["callbackPort"]
|
|
746
|
+
: undefined;
|
|
614
747
|
const stored = mod.getStoredOAuthEntry(name, config);
|
|
615
748
|
if (clientId || callbackPort || stored?.clientSecret) {
|
|
616
749
|
stdout(` OAuth: client_id ${clientId ? "configured" : "(DCR)"}, client_secret ${stored?.clientSecret ? "configured" : "not set"}${callbackPort ? `, callback_port ${callbackPort}` : ""}\n`);
|
|
@@ -642,10 +775,20 @@ async function mcpList(mod, io) {
|
|
|
642
775
|
return 0;
|
|
643
776
|
}
|
|
644
777
|
const lines = [];
|
|
645
|
-
const byScope = {
|
|
778
|
+
const byScope = {
|
|
779
|
+
user: [],
|
|
780
|
+
project: [],
|
|
781
|
+
local: [],
|
|
782
|
+
plugin: [],
|
|
783
|
+
};
|
|
646
784
|
for (const s of servers)
|
|
647
785
|
(byScope[s.scope] ??= []).push(s);
|
|
648
|
-
const labels = {
|
|
786
|
+
const labels = {
|
|
787
|
+
local: "Local",
|
|
788
|
+
project: "Project",
|
|
789
|
+
user: "User",
|
|
790
|
+
plugin: "Plugin",
|
|
791
|
+
};
|
|
649
792
|
for (const scope of ["local", "project", "user", "plugin"]) {
|
|
650
793
|
const group = byScope[scope];
|
|
651
794
|
if (!group?.length)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "1.0.9-staging.
|
|
3
|
+
"version": "1.0.9-staging.1288.1",
|
|
4
4
|
"description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"turndown": "^7.2.4",
|
|
59
59
|
"typebox": "^1.3.15"
|
|
60
60
|
},
|
|
61
|
-
"yagniSourceSha": "
|
|
61
|
+
"yagniSourceSha": "8afdf669270c6b0b2cd82437939af8d18427f1c6"
|
|
62
62
|
}
|