@yagni-app/code-staging 1.0.9-staging.1286.1 → 1.0.9-staging.1292.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/tools.js +66 -8
- package/dist/mcpCommand.js +127 -23
- package/package.json +2 -2
|
@@ -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,9 @@ export async function registerServerTools(pi, manager, serverName, env = process
|
|
|
26
30
|
return result;
|
|
27
31
|
}
|
|
28
32
|
const toolTimeout = toolTimeoutFromEnv(env);
|
|
29
|
-
const selectedTools = server.config.type === "http" || server.config.type === "sse"
|
|
33
|
+
const selectedTools = server.config.type === "http" || server.config.type === "sse"
|
|
34
|
+
? server.config.tools
|
|
35
|
+
: undefined;
|
|
30
36
|
for (const tool of toolList.tools ?? []) {
|
|
31
37
|
if (selectedTools && !selectedTools.includes(tool.name))
|
|
32
38
|
continue;
|
|
@@ -48,7 +54,10 @@ export async function registerServerTools(pi, manager, serverName, env = process
|
|
|
48
54
|
throw new Error(`MCP server "${serverName}" is not connected (try /mcp reconnect).`);
|
|
49
55
|
}
|
|
50
56
|
const args = (params && typeof params === "object" ? params : {});
|
|
51
|
-
const call = active.client.callTool({
|
|
57
|
+
const call = active.client.callTool({
|
|
58
|
+
name: tool.name,
|
|
59
|
+
arguments: args,
|
|
60
|
+
});
|
|
52
61
|
const settled = toolTimeout
|
|
53
62
|
? await withTimeout(call, toolTimeout, `MCP tool call timed out after ${toolTimeout}ms`)
|
|
54
63
|
: await call;
|
|
@@ -56,7 +65,12 @@ export async function registerServerTools(pi, manager, serverName, env = process
|
|
|
56
65
|
},
|
|
57
66
|
};
|
|
58
67
|
pi.registerTool(definition);
|
|
59
|
-
result.tools.push({
|
|
68
|
+
result.tools.push({
|
|
69
|
+
toolName: fullToolName,
|
|
70
|
+
serverName,
|
|
71
|
+
originalName: tool.name,
|
|
72
|
+
description,
|
|
73
|
+
});
|
|
60
74
|
if (mutating)
|
|
61
75
|
result.mutatingToolNames.push(fullToolName);
|
|
62
76
|
}
|
|
@@ -70,7 +84,18 @@ export function capDescription(description) {
|
|
|
70
84
|
/** Cheap heuristic in the spirit of Claude Code's input-hint check; per-tool annotations arrive via listTools only in newer servers. */
|
|
71
85
|
export function looksMutating(toolName, description) {
|
|
72
86
|
const name = toolName.toLowerCase();
|
|
73
|
-
if ([
|
|
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))
|
|
74
99
|
return true;
|
|
75
100
|
const writeHints = /^(create|add|update|edit|delete|remove|set|write|send|post|put|patch|deploy|publish|close|merge|assign|move|archive|trash|restore)/;
|
|
76
101
|
if (writeHints.test(name))
|
|
@@ -96,16 +121,49 @@ function schemaFor(inputSchema) {
|
|
|
96
121
|
}
|
|
97
122
|
function renderCallResult(settled, serverName, toolName) {
|
|
98
123
|
const parts = [];
|
|
124
|
+
const images = [];
|
|
125
|
+
let imageBytes = 0;
|
|
99
126
|
for (const item of settled.content ?? []) {
|
|
100
|
-
if (item &&
|
|
127
|
+
if (item &&
|
|
128
|
+
typeof item === "object" &&
|
|
129
|
+
item.type === "text") {
|
|
101
130
|
parts.push(String(item.text ?? ""));
|
|
102
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
|
+
}
|
|
103
155
|
}
|
|
104
|
-
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)");
|
|
105
160
|
if (settled.isError === true) {
|
|
106
161
|
throw new Error(`MCP tool error from "${serverName}.${toolName}": ${text}`);
|
|
107
162
|
}
|
|
108
|
-
return {
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: "text", text }, ...images],
|
|
165
|
+
details: { server: serverName, tool: toolName },
|
|
166
|
+
};
|
|
109
167
|
}
|
|
110
168
|
function withTimeout(promise, ms, message) {
|
|
111
169
|
return new Promise((resolve, reject) => {
|
package/dist/mcpCommand.js
CHANGED
|
@@ -93,7 +93,10 @@ export function parseMcpArgs(argv) {
|
|
|
93
93
|
rest.push(...after.slice(i + 1));
|
|
94
94
|
break;
|
|
95
95
|
}
|
|
96
|
-
if (flag === "s" ||
|
|
96
|
+
if (flag === "s" ||
|
|
97
|
+
flag === "t" ||
|
|
98
|
+
flag === "client-id" ||
|
|
99
|
+
flag === "callback-port") {
|
|
97
100
|
if (flag === "s") {
|
|
98
101
|
scope = scopeFrom(arg);
|
|
99
102
|
scopeExplicit = true;
|
|
@@ -207,11 +210,28 @@ export function parseMcpArgs(argv) {
|
|
|
207
210
|
}
|
|
208
211
|
rest.push(arg);
|
|
209
212
|
}
|
|
210
|
-
if (flag === "s" ||
|
|
213
|
+
if (flag === "s" ||
|
|
214
|
+
flag === "t" ||
|
|
215
|
+
flag === "client-id" ||
|
|
216
|
+
flag === "callback-port") {
|
|
211
217
|
throw new Error("Missing flag value.");
|
|
212
218
|
}
|
|
213
219
|
const [name, ...commandArgs] = rest;
|
|
214
|
-
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
|
+
};
|
|
215
235
|
}
|
|
216
236
|
function scopeFrom(value) {
|
|
217
237
|
if (value === "local" || value === "user" || value === "project")
|
|
@@ -239,7 +259,11 @@ function describeScopePath(scope, cwd) {
|
|
|
239
259
|
async function defaultPluginMcpEnv(cwd) {
|
|
240
260
|
try {
|
|
241
261
|
const profile = await getActiveProfileName();
|
|
242
|
-
const compat = await claudeCompatArgs({
|
|
262
|
+
const compat = await claudeCompatArgs({
|
|
263
|
+
cwd,
|
|
264
|
+
agentDir: agentDir(profile),
|
|
265
|
+
interactive: false,
|
|
266
|
+
});
|
|
243
267
|
return compat.env[CLAUDE_PLUGIN_MCP_ENV];
|
|
244
268
|
}
|
|
245
269
|
catch {
|
|
@@ -288,21 +312,63 @@ export async function mcpCommand(args, deps = {}) {
|
|
|
288
312
|
case "remove":
|
|
289
313
|
return mcpRemove(mod, parsed, { cwd, stdout, stderr });
|
|
290
314
|
case "list":
|
|
291
|
-
return mcpList(mod, {
|
|
315
|
+
return mcpList(mod, {
|
|
316
|
+
cwd,
|
|
317
|
+
stdout,
|
|
318
|
+
stderr,
|
|
319
|
+
env: await envWithPluginMcp(deps, cwd),
|
|
320
|
+
probeServer: deps.probeServer,
|
|
321
|
+
});
|
|
292
322
|
case "get":
|
|
293
|
-
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
|
+
});
|
|
294
330
|
case "reset-project-choices":
|
|
295
331
|
return mcpResetChoices(mod, { cwd, stdout, stderr });
|
|
296
332
|
case "add-from-claude":
|
|
297
|
-
return mcpAddFromClaude(mod, parsed, {
|
|
333
|
+
return mcpAddFromClaude(mod, parsed, {
|
|
334
|
+
cwd,
|
|
335
|
+
stdout,
|
|
336
|
+
stderr,
|
|
337
|
+
home: deps.home ?? homedir(),
|
|
338
|
+
});
|
|
298
339
|
default:
|
|
299
340
|
stderr(`Unknown mcp subcommand "${parsed.subcommand}".\n${USAGE}`);
|
|
300
341
|
return 1;
|
|
301
342
|
}
|
|
302
343
|
}
|
|
303
|
-
const WORKER_TOOL_NAMES = [
|
|
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
|
+
];
|
|
304
363
|
async function connectWorkers(mod, parsed, io, env) {
|
|
305
|
-
if (parsed.rest.length ||
|
|
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) {
|
|
306
372
|
io.stderr("Usage: yagni mcp connect-workers (uses your active environment and private user configuration)\n");
|
|
307
373
|
return 1;
|
|
308
374
|
}
|
|
@@ -311,16 +377,27 @@ async function connectWorkers(mod, parsed, io, env) {
|
|
|
311
377
|
throw new Error("Update YAGNI Code to connect Workers");
|
|
312
378
|
const profile = await readActiveProfile(env);
|
|
313
379
|
const url = new URL("/mcp", profile.baseUrl);
|
|
314
|
-
if (url.username ||
|
|
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))))
|
|
315
385
|
throw new Error("Worker connections require HTTPS or a local development server");
|
|
316
|
-
const config = {
|
|
386
|
+
const config = {
|
|
387
|
+
type: "http",
|
|
388
|
+
url: url.href,
|
|
389
|
+
tools: WORKER_TOOL_NAMES,
|
|
390
|
+
};
|
|
317
391
|
const { file, errors } = mod.readUserMcpConfig();
|
|
318
392
|
if (errors.length)
|
|
319
393
|
throw new Error("Fix the existing MCP configuration before connecting Workers");
|
|
320
|
-
const previous = file
|
|
394
|
+
const previous = file
|
|
395
|
+
.mcpServers?.["yagni-workers"];
|
|
321
396
|
if (previous && JSON.stringify(previous) !== JSON.stringify(config))
|
|
322
397
|
throw new Error("An existing yagni-workers connection uses different settings. Remove it with yagni mcp remove yagni-workers before reconnecting");
|
|
323
|
-
const shadow = mod
|
|
398
|
+
const shadow = mod
|
|
399
|
+
.loadMcpServers(io.cwd, env)
|
|
400
|
+
.servers.find((server) => server.name === "yagni-workers" && server.scope !== "user");
|
|
324
401
|
if (shadow)
|
|
325
402
|
throw new Error("A project or local yagni-workers entry would override this connection. Remove that entry before connecting");
|
|
326
403
|
io.stdout("Opening YAGNI in your browser. Choose your workspace and permissions; paid Worker requests use that workspace's balance.\n");
|
|
@@ -347,7 +424,9 @@ function mcpAdd(mod, parsed, io) {
|
|
|
347
424
|
serverConfig = {
|
|
348
425
|
type: parsed.transport,
|
|
349
426
|
url: commandOrUrl,
|
|
350
|
-
...(Object.keys(parsed.headers).length > 0
|
|
427
|
+
...(Object.keys(parsed.headers).length > 0
|
|
428
|
+
? { headers: parsed.headers }
|
|
429
|
+
: {}),
|
|
351
430
|
...(oauthBlock(parsed) ? { oauth: oauthBlock(parsed) } : {}),
|
|
352
431
|
};
|
|
353
432
|
}
|
|
@@ -437,18 +516,29 @@ function readClientSecret(io) {
|
|
|
437
516
|
function validateConfigShape(config) {
|
|
438
517
|
const type = config["type"];
|
|
439
518
|
if (type === undefined || type === "stdio") {
|
|
440
|
-
if (typeof config["command"] !== "string" ||
|
|
441
|
-
|
|
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
|
+
};
|
|
442
525
|
}
|
|
443
526
|
return { ok: true };
|
|
444
527
|
}
|
|
445
528
|
if (type === "http" || type === "sse") {
|
|
446
|
-
if (typeof config["url"] !== "string" ||
|
|
447
|
-
|
|
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
|
+
};
|
|
448
535
|
}
|
|
449
536
|
return { ok: true };
|
|
450
537
|
}
|
|
451
|
-
return {
|
|
538
|
+
return {
|
|
539
|
+
ok: false,
|
|
540
|
+
message: 'unknown "type" — expected stdio, http, or sse',
|
|
541
|
+
};
|
|
452
542
|
}
|
|
453
543
|
function writeServerToScope(mod, name, config, scope, cwd) {
|
|
454
544
|
if (scope === "project") {
|
|
@@ -623,7 +713,9 @@ async function mcpGet(mod, parsed, io) {
|
|
|
623
713
|
else {
|
|
624
714
|
io.stdout(` Type: stdio\n`);
|
|
625
715
|
io.stdout(` Command: ${config["command"]}\n`);
|
|
626
|
-
const args = Array.isArray(config["args"])
|
|
716
|
+
const args = Array.isArray(config["args"])
|
|
717
|
+
? config["args"]
|
|
718
|
+
: [];
|
|
627
719
|
if (args.length > 0)
|
|
628
720
|
io.stdout(` Args: ${args.join(" ")}\n`);
|
|
629
721
|
for (const [key, value] of Object.entries(config["env"] ?? {})) {
|
|
@@ -649,7 +741,9 @@ function printOAuthDetail(mod, name, config, stdout) {
|
|
|
649
741
|
const cfg = config;
|
|
650
742
|
const oauth = cfg["oauth"] ?? {};
|
|
651
743
|
const clientId = typeof oauth["clientId"] === "string" ? oauth["clientId"] : undefined;
|
|
652
|
-
const callbackPort = typeof oauth["callbackPort"] === "number"
|
|
744
|
+
const callbackPort = typeof oauth["callbackPort"] === "number"
|
|
745
|
+
? oauth["callbackPort"]
|
|
746
|
+
: undefined;
|
|
653
747
|
const stored = mod.getStoredOAuthEntry(name, config);
|
|
654
748
|
if (clientId || callbackPort || stored?.clientSecret) {
|
|
655
749
|
stdout(` OAuth: client_id ${clientId ? "configured" : "(DCR)"}, client_secret ${stored?.clientSecret ? "configured" : "not set"}${callbackPort ? `, callback_port ${callbackPort}` : ""}\n`);
|
|
@@ -681,10 +775,20 @@ async function mcpList(mod, io) {
|
|
|
681
775
|
return 0;
|
|
682
776
|
}
|
|
683
777
|
const lines = [];
|
|
684
|
-
const byScope = {
|
|
778
|
+
const byScope = {
|
|
779
|
+
user: [],
|
|
780
|
+
project: [],
|
|
781
|
+
local: [],
|
|
782
|
+
plugin: [],
|
|
783
|
+
};
|
|
685
784
|
for (const s of servers)
|
|
686
785
|
(byScope[s.scope] ??= []).push(s);
|
|
687
|
-
const labels = {
|
|
786
|
+
const labels = {
|
|
787
|
+
local: "Local",
|
|
788
|
+
project: "Project",
|
|
789
|
+
user: "User",
|
|
790
|
+
plugin: "Plugin",
|
|
791
|
+
};
|
|
688
792
|
for (const scope of ["local", "project", "user", "plugin"]) {
|
|
689
793
|
const group = byScope[scope];
|
|
690
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.1292.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": "be851fcdc82a066edbe8ebb394685bc1b61169f8"
|
|
62
62
|
}
|