* fix(#3384): strip mcp__* tool grants from zcode-installed subagents ZCode's dispatcher treats every mcp__<server>__* entry in an agent's tools: frontmatter as a required MCP server and hard-fails the subagent spawn (CONFIGURATION_ERROR) when it is not connected, whereas Claude Code treats the same grants as an optional allowlist. ZCode shared Claude's verbatim agents copy (converter: null), so all 8 MCP-granted agents failed to spawn out of the box with zero MCP servers configured. Add convertClaudeAgentToZcodeAgent — a line-surgical converter that filters mcp__* entries out of the frontmatter tools: grant list (both inline comma and YAML block-list shapes) and preserves every other byte. Declare it on both of zcode's capability.json agents entries and cut zcode over to the descriptor-driven agents path (_DESCRIPTOR_AGENTS_RUNTIMES) so the legacy inline loop stops deleting+re-copying the converted agents raw. Claude Code, Kimi, and Gemini install behavior is unchanged. * chore(#3384): link changeset fragment to pr 3483 --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -2370,6 +2370,102 @@ function convertClaudeAgentToQwenAgent(content) {
|
||||
return `${fm}\n${body}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Claude Code agent .md for ZCode (#3384).
|
||||
*
|
||||
* ZCode is Claude-shaped (same frontmatter, same named-dispatch subagents), so
|
||||
* the file is preserved verbatim EXCEPT the `tools:` grant list: ZCode's
|
||||
* dispatcher treats every `mcp__<server>__*` entry as a REQUIRED MCP server and
|
||||
* hard-fails the subagent spawn (CONFIGURATION_ERROR: "Required MCP server is
|
||||
* not connected") whenever it is not connected, whereas Claude Code treats the
|
||||
* same entries as an optional allowlist. The `mcp__*` entries are stripped at
|
||||
* install time — the same exclusion Kimi's converter applies via
|
||||
* convertKimiToolName — so subagent spawns succeed with zero MCP servers
|
||||
* configured; connected servers' tools remain reachable (auto-discovered by the
|
||||
* host, not granted by frontmatter).
|
||||
*
|
||||
* Line-surgical by design: ONLY `tools:` lines inside the frontmatter are
|
||||
* touched, so every other byte (description, color, commented-out blocks, the
|
||||
* body) survives identically. Handles both shapes GSD emits — the inline comma
|
||||
* list (`tools: A, B, C`) and the YAML block list (`tools:` + `- A` items).
|
||||
* An agent whose filtered grant list becomes empty (every grant was `mcp__*`)
|
||||
* drops the `tools:` key entirely: an absent key inherits the full toolkit,
|
||||
* which is the degrade-gracefully outcome, never a toolless subagent.
|
||||
*
|
||||
* Byte-identical for an agent with no `mcp__*` grants (the common case) and
|
||||
* for an agent with no frontmatter at all.
|
||||
*/
|
||||
function convertClaudeAgentToZcodeAgent(content) {
|
||||
// Fast path: no MCP grant token anywhere means nothing to strip. (A body
|
||||
// mention alone is not a grant — the line scan below finds no tools-line
|
||||
// change and returns `content` unchanged anyway; this just skips the scan.)
|
||||
if (!content.includes('mcp__')) return content;
|
||||
|
||||
const lines = content.split('\n');
|
||||
if (lines[0] !== '---') return content;
|
||||
let fmEnd = -1;
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
if (lines[i] === '---') {
|
||||
fmEnd = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fmEnd === -1) return content; // unterminated frontmatter — leave verbatim
|
||||
|
||||
const out = [];
|
||||
let changed = false;
|
||||
let i = 1;
|
||||
while (i < fmEnd) {
|
||||
const line = lines[i];
|
||||
const inlineTools = /^tools:[ \t]*(.+)$/.exec(line);
|
||||
if (inlineTools) {
|
||||
const grants = inlineTools[1].split(',').map((tool) => tool.trim()).filter((tool) => tool !== '');
|
||||
const kept = grants.filter((tool) => !tool.startsWith('mcp__'));
|
||||
if (kept.length === grants.length) {
|
||||
out.push(line); // no mcp__* grants — keep the line byte-identical
|
||||
} else if (kept.length > 0) {
|
||||
out.push(`tools: ${kept.join(', ')}`);
|
||||
changed = true;
|
||||
} else {
|
||||
changed = true; // every grant was mcp__*: drop the tools key entirely
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (/^tools:[ \t]*$/.test(line)) {
|
||||
// Block-list form: collect the following `- item` lines.
|
||||
const items = [];
|
||||
let j = i + 1;
|
||||
while (j < fmEnd && /^([ \t]*)-[ \t]*(\S.*)$/.test(lines[j])) {
|
||||
items.push(lines[j]);
|
||||
j++;
|
||||
}
|
||||
const kept = items.filter((item) => {
|
||||
const name = /^([ \t]*)-[ \t]*(\S.*)$/.exec(item)[2].trim();
|
||||
return !name.startsWith('mcp__');
|
||||
});
|
||||
if (kept.length !== items.length) {
|
||||
changed = true;
|
||||
if (kept.length > 0) {
|
||||
out.push(line);
|
||||
out.push(...kept);
|
||||
} // else: drop the tools key and all its items
|
||||
} else {
|
||||
out.push(line, ...items);
|
||||
}
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
out.push(line);
|
||||
i++;
|
||||
}
|
||||
if (!changed) return content;
|
||||
// Opening delimiter + transformed frontmatter + closing delimiter + body.
|
||||
out.unshift(lines[0]);
|
||||
out.push(...lines.slice(fmEnd));
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
function convertClaudeAgentToCodebuddyAgent(content) {
|
||||
const converted = convertClaudeToCodebuddyMarkdown(content);
|
||||
|
||||
@@ -3139,6 +3235,11 @@ export = {
|
||||
// conversionExports[converterName] dispatch (runtime-artifact-layout.cts)
|
||||
// can resolve it from capabilities/qwen/capability.json's agents kind.
|
||||
convertClaudeAgentToQwenAgent,
|
||||
// #3384: ZCode agents are Claude-shaped but its dispatcher treats mcp__*
|
||||
// tools grants as required MCP servers — registered by name for the same
|
||||
// conversionExports[converterName] dispatch, resolved from
|
||||
// capabilities/zcode/capability.json's agents kind.
|
||||
convertClaudeAgentToZcodeAgent,
|
||||
// #1511 ADR-1508 Phase 2: rewrite engine deep seam
|
||||
// Low-level walkers (pathPrefix + attribution pre-resolved by caller):
|
||||
applyRuntimeContentRewritesInPlace,
|
||||
|
||||
Reference in New Issue
Block a user