kimi-plugin-cc 2.2.2 → 2.4.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +5 -3
- package/.mcp.json +13 -0
- package/CHANGELOG.md +18 -0
- package/README.md +57 -5
- package/agents/kimi-challenge.md +3 -3
- package/agents/kimi-pursue.md +1 -1
- package/agents/kimi-rescue.md +2 -2
- package/agents/kimi-review.md +3 -3
- package/commands/README.md +2 -2
- package/commands/challenge.md +1 -1
- package/commands/pursue.md +1 -1
- package/commands/review.md +1 -1
- package/dist/background-spawn.js +9 -17
- package/dist/cli-client.js +24 -63
- package/dist/commands/ask.js +28 -31
- package/dist/commands/cancel.js +50 -23
- package/dist/commands/cli-helpers.js +7 -16
- package/dist/commands/pursue.js +24 -33
- package/dist/commands/registry.js +6 -19
- package/dist/commands/rescue.js +23 -22
- package/dist/commands/review-gate.js +18 -28
- package/dist/commands/review.js +41 -45
- package/dist/commands/setup.js +85 -171
- package/dist/commands/swarm.js +107 -91
- package/dist/commands/worker.js +0 -6
- package/dist/companion.js +13 -40
- package/dist/git.js +14 -7
- package/dist/help.js +2 -0
- package/dist/hooks/approval-hook.js +9 -20
- package/dist/hooks/approval-policy.js +36 -69
- package/dist/hooks/config-safety.js +6 -4
- package/dist/hooks/install-paths.js +62 -138
- package/dist/hooks/install.js +37 -108
- package/dist/hooks/managed-block.js +88 -214
- package/dist/job-id.js +12 -0
- package/dist/job-store.js +65 -28
- package/dist/jobs.js +9 -1
- package/dist/kimi-command.js +1 -3
- package/dist/kimi-engine.js +1 -1
- package/dist/kimi-timeouts.js +9 -21
- package/dist/kimi-version-probe.js +16 -1210
- package/dist/mcp/binding.js +58 -0
- package/dist/mcp/job-control.js +345 -0
- package/dist/mcp/launcher.js +257 -0
- package/dist/mcp/operations.js +301 -0
- package/dist/mcp/protocol.js +275 -0
- package/dist/mcp/server.js +8 -0
- package/dist/mcp/stdio.js +78 -0
- package/dist/mcp/supervised-child.js +15 -0
- package/dist/mcp/tool-result.js +7 -0
- package/dist/mcp/workspace.js +34 -0
- package/dist/model-command.js +32 -0
- package/dist/native-v2-preflight.js +1 -1
- package/dist/parsing.js +4 -18
- package/dist/rescue-approval.js +78 -164
- package/dist/session-recovery.js +7 -10
- package/dist/stream-json.js +6 -174
- package/dist/version.js +1 -1
- package/docs/migration.md +2 -6
- package/docs/registry-releases.md +23 -7
- package/docs/safety.md +30 -14
- package/package.json +7 -3
- package/plugins/kimi-codex/.codex-plugin/plugin.json +8 -5
- package/plugins/kimi-codex/.mcp.json +28 -0
- package/plugins/kimi-codex/dist/background-spawn.js +9 -17
- package/plugins/kimi-codex/dist/cli-client.js +24 -63
- package/plugins/kimi-codex/dist/commands/ask.js +28 -31
- package/plugins/kimi-codex/dist/commands/cancel.js +50 -23
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +7 -16
- package/plugins/kimi-codex/dist/commands/pursue.js +24 -33
- package/plugins/kimi-codex/dist/commands/registry.js +6 -19
- package/plugins/kimi-codex/dist/commands/rescue.js +23 -22
- package/plugins/kimi-codex/dist/commands/review-gate.js +18 -28
- package/plugins/kimi-codex/dist/commands/review.js +41 -45
- package/plugins/kimi-codex/dist/commands/setup.js +85 -171
- package/plugins/kimi-codex/dist/commands/swarm.js +107 -91
- package/plugins/kimi-codex/dist/commands/worker.js +0 -6
- package/plugins/kimi-codex/dist/companion.js +13 -40
- package/plugins/kimi-codex/dist/git.js +14 -7
- package/plugins/kimi-codex/dist/help.js +2 -0
- package/plugins/kimi-codex/dist/hooks/approval-hook.js +9 -20
- package/plugins/kimi-codex/dist/hooks/approval-policy.js +36 -69
- package/plugins/kimi-codex/dist/hooks/config-safety.js +6 -4
- package/plugins/kimi-codex/dist/hooks/install-paths.js +62 -138
- package/plugins/kimi-codex/dist/hooks/install.js +37 -108
- package/plugins/kimi-codex/dist/hooks/managed-block.js +88 -214
- package/plugins/kimi-codex/dist/job-id.js +12 -0
- package/plugins/kimi-codex/dist/job-store.js +65 -28
- package/plugins/kimi-codex/dist/jobs.js +9 -1
- package/plugins/kimi-codex/dist/kimi-command.js +1 -3
- package/plugins/kimi-codex/dist/kimi-engine.js +1 -1
- package/plugins/kimi-codex/dist/kimi-timeouts.js +9 -21
- package/plugins/kimi-codex/dist/kimi-version-probe.js +16 -1210
- package/plugins/kimi-codex/dist/mcp/binding.js +58 -0
- package/plugins/kimi-codex/dist/mcp/job-control.js +345 -0
- package/plugins/kimi-codex/dist/mcp/launcher.js +257 -0
- package/plugins/kimi-codex/dist/mcp/operations.js +301 -0
- package/plugins/kimi-codex/dist/mcp/protocol.js +275 -0
- package/plugins/kimi-codex/dist/mcp/server.js +8 -0
- package/plugins/kimi-codex/dist/mcp/stdio.js +78 -0
- package/plugins/kimi-codex/dist/mcp/supervised-child.js +15 -0
- package/plugins/kimi-codex/dist/mcp/tool-result.js +7 -0
- package/plugins/kimi-codex/dist/mcp/workspace.js +34 -0
- package/plugins/kimi-codex/dist/model-command.js +32 -0
- package/plugins/kimi-codex/dist/native-v2-preflight.js +1 -1
- package/plugins/kimi-codex/dist/parsing.js +4 -18
- package/plugins/kimi-codex/dist/rescue-approval.js +78 -164
- package/plugins/kimi-codex/dist/session-recovery.js +7 -10
- package/plugins/kimi-codex/dist/stream-json.js +6 -174
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +2 -2
- package/plugins/kimi-codex/skills/kimi-cancel/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-replay/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-result/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-review/SKILL.md +3 -3
- package/plugins/kimi-codex/skills/kimi-setup/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-status/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-swarm/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-swarm-write/SKILL.md +1 -1
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { RuntimeError } from "../errors.js";
|
|
2
|
+
import { parseDurationMs, validateThinkingEffort } from "../parsing.js";
|
|
3
|
+
const WORKSPACE_PROPERTY = {
|
|
4
|
+
type: "string",
|
|
5
|
+
description: "Absolute workspace directory. Required when the host does not configure a default; Codex normally supplies it explicitly.",
|
|
6
|
+
};
|
|
7
|
+
const MODEL_PROPERTY = {
|
|
8
|
+
type: "string",
|
|
9
|
+
description: "One configured Kimi model alias. Omit to preserve the normal session default; never guess an alias.",
|
|
10
|
+
};
|
|
11
|
+
const THINKING_PROPERTY = {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Per-run thinking-effort request such as off, low, medium, or high. Provider/model support varies and is not observable by the plugin.",
|
|
14
|
+
};
|
|
15
|
+
const BUDGET_PROPERTY = {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Finite duration such as 30m, 1h, or 90s; bare numbers mean minutes. Maximum 24h. Background operation does not extend it.",
|
|
18
|
+
};
|
|
19
|
+
const COMMON_PROPERTIES = {
|
|
20
|
+
workspace: WORKSPACE_PROPERTY,
|
|
21
|
+
model: MODEL_PROPERTY,
|
|
22
|
+
thinking: THINKING_PROPERTY,
|
|
23
|
+
budget: BUDGET_PROPERTY,
|
|
24
|
+
};
|
|
25
|
+
const RESUME_PROPERTIES = {
|
|
26
|
+
fresh: {
|
|
27
|
+
type: "boolean",
|
|
28
|
+
description: "Start a fresh Kimi session. Incompatible with resume and resume_latest.",
|
|
29
|
+
},
|
|
30
|
+
resume: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Resume this job ID or Kimi session ID with the standard continuation instruction. Do not also supply prompt.",
|
|
33
|
+
},
|
|
34
|
+
resume_latest: {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
description: "Resume the latest matching session for this repository. May be combined with a new prompt.",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const PROMPT_PROPERTY = {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "The complete user request, preserved verbatim. Required except when resume or resume_latest supplies the continuation context.",
|
|
42
|
+
};
|
|
43
|
+
const OBJECTIVE_PROPERTY = {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "The complete objective, preserved verbatim.",
|
|
46
|
+
};
|
|
47
|
+
const FOCUS_PROPERTY = {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Optional review focus or adversarial framing, preserved verbatim.",
|
|
50
|
+
};
|
|
51
|
+
const BASE_PROPERTY = {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Optional Git base ref. The committed branch diff is authoritative; dirty current files are not treated as committed snapshots.",
|
|
54
|
+
};
|
|
55
|
+
const TURNS_PROPERTY = {
|
|
56
|
+
type: "integer",
|
|
57
|
+
minimum: 1,
|
|
58
|
+
description: "Soft turn-count hint included in the objective. The finite budget is the hard bound.",
|
|
59
|
+
};
|
|
60
|
+
const CAP_PROPERTY = {
|
|
61
|
+
type: "integer",
|
|
62
|
+
minimum: 1,
|
|
63
|
+
description: "Soft hint for total subagent count, not a hook-enforced limit.",
|
|
64
|
+
};
|
|
65
|
+
const MAX_CONCURRENCY_PROPERTY = {
|
|
66
|
+
type: "integer",
|
|
67
|
+
minimum: 1,
|
|
68
|
+
description: "Hard peak subagent concurrency on supported Kimi Code versions. Distinct from the soft total cap.",
|
|
69
|
+
};
|
|
70
|
+
const START_RESULT_GUIDANCE = "Task-capable clients receive an MCP task. Other clients receive a job ID immediately after launch; use kimi_job_status and kimi_job_result until terminal, and kimi_job_cancel to stop. Do not present a launch handle as the finished answer.";
|
|
71
|
+
function schema(properties, required = []) {
|
|
72
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
73
|
+
}
|
|
74
|
+
const OPERATION_SPECS = {
|
|
75
|
+
kimi_ask: {
|
|
76
|
+
kind: "ask",
|
|
77
|
+
command: ["ask"],
|
|
78
|
+
write: false,
|
|
79
|
+
summary: "Start an independent read-only Kimi repository explanation.",
|
|
80
|
+
inputSchema: schema({ ...COMMON_PROPERTIES, prompt: PROMPT_PROPERTY, ...RESUME_PROPERTIES }),
|
|
81
|
+
},
|
|
82
|
+
kimi_review: {
|
|
83
|
+
kind: "review",
|
|
84
|
+
command: ["review"],
|
|
85
|
+
write: false,
|
|
86
|
+
summary: "Start an independent read-only Kimi review of working-tree changes, including untracked files, or a committed base diff.",
|
|
87
|
+
inputSchema: schema({ ...COMMON_PROPERTIES, base: BASE_PROPERTY, focus: FOCUS_PROPERTY }),
|
|
88
|
+
},
|
|
89
|
+
kimi_challenge: {
|
|
90
|
+
kind: "challenge",
|
|
91
|
+
command: ["task", "challenge"],
|
|
92
|
+
write: false,
|
|
93
|
+
summary: "Start a read-only Kimi challenge of assumptions, design choices, and tradeoffs rather than a defect-only review.",
|
|
94
|
+
inputSchema: schema({ ...COMMON_PROPERTIES, base: BASE_PROPERTY, focus: FOCUS_PROPERTY }),
|
|
95
|
+
},
|
|
96
|
+
kimi_rescue: {
|
|
97
|
+
kind: "rescue",
|
|
98
|
+
command: ["task", "rescue"],
|
|
99
|
+
write: true,
|
|
100
|
+
summary: "Start a bounded write-capable Kimi investigation or fix inside the validated workspace. Review the resulting changes; no commit is made.",
|
|
101
|
+
inputSchema: schema({ ...COMMON_PROPERTIES, prompt: PROMPT_PROPERTY, ...RESUME_PROPERTIES }),
|
|
102
|
+
},
|
|
103
|
+
kimi_pursue: {
|
|
104
|
+
kind: "pursue",
|
|
105
|
+
command: ["task", "pursue"],
|
|
106
|
+
write: true,
|
|
107
|
+
summary: "Start explicitly autonomous Kimi goal mode with a finite hard budget and workspace write limits. Use only for hands-off objectives, not a single bounded fix.",
|
|
108
|
+
inputSchema: schema({ ...COMMON_PROPERTIES, objective: OBJECTIVE_PROPERTY, turns: TURNS_PROPERTY }, ["objective"]),
|
|
109
|
+
},
|
|
110
|
+
kimi_swarm: {
|
|
111
|
+
kind: "swarm",
|
|
112
|
+
command: ["task", "swarm"],
|
|
113
|
+
write: false,
|
|
114
|
+
summary: "Start a read-only parallel Kimi fan-out across many independent review targets.",
|
|
115
|
+
inputSchema: schema({ ...COMMON_PROPERTIES, objective: OBJECTIVE_PROPERTY, cap: CAP_PROPERTY, max_concurrency: MAX_CONCURRENCY_PROPERTY }, ["objective"]),
|
|
116
|
+
},
|
|
117
|
+
kimi_swarm_write: {
|
|
118
|
+
kind: "swarm-write",
|
|
119
|
+
command: ["task", "swarm"],
|
|
120
|
+
write: true,
|
|
121
|
+
summary: "Start parallel Kimi implementation in a disposable worktree off HEAD. The real tree is not edited; the result is a patch to review and apply separately.",
|
|
122
|
+
inputSchema: schema({ ...COMMON_PROPERTIES, objective: OBJECTIVE_PROPERTY, cap: CAP_PROPERTY, max_concurrency: MAX_CONCURRENCY_PROPERTY }, ["objective"]),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const JOB_CONTROL_SCHEMA = schema({
|
|
126
|
+
workspace: WORKSPACE_PROPERTY,
|
|
127
|
+
job_id: { type: "string", description: "The UUID returned by an MCP-launched Kimi operation." },
|
|
128
|
+
}, ["job_id"]);
|
|
129
|
+
export const MCP_TOOL_DEFINITIONS = [
|
|
130
|
+
...Object.entries(OPERATION_SPECS).map(([name, spec]) => ({
|
|
131
|
+
name: name,
|
|
132
|
+
description: `${spec.summary} ${START_RESULT_GUIDANCE}`,
|
|
133
|
+
inputSchema: spec.inputSchema,
|
|
134
|
+
annotations: {
|
|
135
|
+
title: name.replaceAll("_", " "),
|
|
136
|
+
readOnlyHint: !spec.write,
|
|
137
|
+
destructiveHint: spec.write,
|
|
138
|
+
idempotentHint: false,
|
|
139
|
+
openWorldHint: true,
|
|
140
|
+
},
|
|
141
|
+
})),
|
|
142
|
+
{
|
|
143
|
+
name: "kimi_job_status",
|
|
144
|
+
description: "Read one MCP-launched job's durable state and phase in the same workspace. This is always an ordinary MCP result, never a task.",
|
|
145
|
+
inputSchema: JOB_CONTROL_SCHEMA,
|
|
146
|
+
annotations: { title: "kimi job status", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "kimi_job_result",
|
|
150
|
+
description: "Read the terminal artifact for one MCP-launched job in the same workspace. Failed jobs return isError:true with their artifact; running jobs return JOB_NOT_TERMINAL without changing state.",
|
|
151
|
+
inputSchema: JOB_CONTROL_SCHEMA,
|
|
152
|
+
annotations: { title: "kimi job result", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "kimi_job_cancel",
|
|
156
|
+
description: "Cooperatively cancel one running MCP-launched job in the same workspace. Terminal jobs are a no-op. Cancellation does not roll back edits, and write-swarm still captures its patch before cleanup.",
|
|
157
|
+
inputSchema: JOB_CONTROL_SCHEMA,
|
|
158
|
+
annotations: { title: "kimi job cancel", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
export function isMcpModelToolName(name) {
|
|
162
|
+
return Object.hasOwn(OPERATION_SPECS, name);
|
|
163
|
+
}
|
|
164
|
+
export function buildMcpOperationInvocation(name, input) {
|
|
165
|
+
const spec = OPERATION_SPECS[name];
|
|
166
|
+
const properties = spec.inputSchema.properties;
|
|
167
|
+
const args = readArguments(input, Object.keys(properties));
|
|
168
|
+
const workspace = optionalString(args, "workspace");
|
|
169
|
+
const argv = [...spec.command];
|
|
170
|
+
appendCommonOptions(argv, args, spec.kind === "pursue" || spec.kind === "swarm" || spec.kind === "swarm-write" ? "safety" : "response");
|
|
171
|
+
switch (spec.kind) {
|
|
172
|
+
case "ask":
|
|
173
|
+
case "rescue": {
|
|
174
|
+
const prompt = args.prompt === undefined ? undefined : requiredNonEmptyString(args, "prompt");
|
|
175
|
+
const fresh = optionalBoolean(args, "fresh") ?? false;
|
|
176
|
+
const resumeLatest = optionalBoolean(args, "resume_latest") ?? false;
|
|
177
|
+
const resume = args.resume === undefined ? undefined : requiredNonEmptyString(args, "resume");
|
|
178
|
+
if (fresh && (resumeLatest || resume !== undefined)) {
|
|
179
|
+
throw invalidArguments("fresh cannot be combined with resume or resume_latest.");
|
|
180
|
+
}
|
|
181
|
+
if (resumeLatest && resume !== undefined) {
|
|
182
|
+
throw invalidArguments("resume and resume_latest are alternatives; choose one.");
|
|
183
|
+
}
|
|
184
|
+
if (resume !== undefined && prompt !== undefined) {
|
|
185
|
+
throw invalidArguments("resume uses the standard continuation instruction and cannot include prompt. Use resume_latest with a new prompt instead.");
|
|
186
|
+
}
|
|
187
|
+
if (prompt === undefined && !resumeLatest && resume === undefined) {
|
|
188
|
+
throw invalidArguments(`${spec.kind} requires prompt unless resume or resume_latest is selected.`);
|
|
189
|
+
}
|
|
190
|
+
if (fresh)
|
|
191
|
+
argv.push("--fresh");
|
|
192
|
+
if (resumeLatest)
|
|
193
|
+
argv.push("-r");
|
|
194
|
+
if (resume !== undefined)
|
|
195
|
+
argv.push("--resume", resume);
|
|
196
|
+
if (prompt !== undefined)
|
|
197
|
+
argv.push("--", prompt);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case "review":
|
|
201
|
+
case "challenge": {
|
|
202
|
+
const base = args.base === undefined ? undefined : requiredNonEmptyString(args, "base");
|
|
203
|
+
const focus = optionalString(args, "focus");
|
|
204
|
+
if (base !== undefined)
|
|
205
|
+
argv.push("--base", base);
|
|
206
|
+
if (focus !== undefined)
|
|
207
|
+
argv.push("--", focus);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "pursue": {
|
|
211
|
+
const turns = optionalPositiveInteger(args, "turns");
|
|
212
|
+
if (turns !== undefined)
|
|
213
|
+
argv.push("--turns", String(turns));
|
|
214
|
+
argv.push("--", requiredNonEmptyString(args, "objective"));
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "swarm":
|
|
218
|
+
case "swarm-write": {
|
|
219
|
+
const cap = optionalPositiveInteger(args, "cap");
|
|
220
|
+
const maxConcurrency = optionalPositiveInteger(args, "max_concurrency");
|
|
221
|
+
if (cap !== undefined)
|
|
222
|
+
argv.push("--cap", String(cap));
|
|
223
|
+
if (maxConcurrency !== undefined)
|
|
224
|
+
argv.push("--max-concurrency", String(maxConcurrency));
|
|
225
|
+
if (spec.kind === "swarm-write")
|
|
226
|
+
argv.push("--write");
|
|
227
|
+
argv.push("--", requiredNonEmptyString(args, "objective"));
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { operationKind: spec.kind, ...(workspace !== undefined ? { workspace } : {}), argv };
|
|
232
|
+
}
|
|
233
|
+
export function readJobControlArguments(input) {
|
|
234
|
+
const args = readArguments(input, ["workspace", "job_id"]);
|
|
235
|
+
return {
|
|
236
|
+
...(optionalString(args, "workspace") !== undefined ? { workspace: optionalString(args, "workspace") } : {}),
|
|
237
|
+
jobId: requiredNonEmptyString(args, "job_id"),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function appendCommonOptions(argv, args, budgetKind) {
|
|
241
|
+
const budget = optionalString(args, "budget");
|
|
242
|
+
const model = optionalString(args, "model");
|
|
243
|
+
const thinking = optionalString(args, "thinking");
|
|
244
|
+
if (budget !== undefined) {
|
|
245
|
+
parseDurationMs(budget, "--budget", "mcp.args", budgetKind);
|
|
246
|
+
argv.push("--budget", budget);
|
|
247
|
+
}
|
|
248
|
+
if (model !== undefined)
|
|
249
|
+
argv.push("-m", requiredNonEmptyString(args, "model"));
|
|
250
|
+
if (thinking !== undefined) {
|
|
251
|
+
validateThinkingEffort(thinking, "mcp.args");
|
|
252
|
+
argv.push("--thinking", thinking);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function readArguments(input, allowedKeys) {
|
|
256
|
+
if (input === undefined)
|
|
257
|
+
return {};
|
|
258
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
259
|
+
throw invalidArguments("Tool arguments must be a JSON object.");
|
|
260
|
+
}
|
|
261
|
+
const args = input;
|
|
262
|
+
const allowed = new Set(allowedKeys);
|
|
263
|
+
for (const key of Object.keys(args)) {
|
|
264
|
+
if (!allowed.has(key))
|
|
265
|
+
throw invalidArguments(`Unknown argument ${key}. Allowed arguments: ${allowedKeys.join(", ")}.`);
|
|
266
|
+
}
|
|
267
|
+
return args;
|
|
268
|
+
}
|
|
269
|
+
function optionalString(args, key) {
|
|
270
|
+
const value = args[key];
|
|
271
|
+
if (value === undefined)
|
|
272
|
+
return undefined;
|
|
273
|
+
if (typeof value !== "string")
|
|
274
|
+
throw invalidArguments(`${key} must be a string.`);
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
function requiredNonEmptyString(args, key) {
|
|
278
|
+
const value = optionalString(args, key);
|
|
279
|
+
if (value === undefined || value.trim().length === 0)
|
|
280
|
+
throw invalidArguments(`${key} must be a non-empty string.`);
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
function optionalBoolean(args, key) {
|
|
284
|
+
const value = args[key];
|
|
285
|
+
if (value === undefined)
|
|
286
|
+
return undefined;
|
|
287
|
+
if (typeof value !== "boolean")
|
|
288
|
+
throw invalidArguments(`${key} must be a boolean.`);
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
function optionalPositiveInteger(args, key) {
|
|
292
|
+
const value = args[key];
|
|
293
|
+
if (value === undefined)
|
|
294
|
+
return undefined;
|
|
295
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
296
|
+
throw invalidArguments(`${key} must be a positive safe integer.`);
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
function invalidArguments(message) {
|
|
300
|
+
return new RuntimeError("MCP_INVALID_ARGUMENTS", message, "mcp.args");
|
|
301
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { formatError, RuntimeError } from "../errors.js";
|
|
2
|
+
import { KIMI_PLUGIN_CC_VERSION } from "../version.js";
|
|
3
|
+
import { cancelMcpJob, getMcpJobResult, getMcpJobStatus, getMcpTaskView } from "./job-control.js";
|
|
4
|
+
import { launchMcpOperation } from "./launcher.js";
|
|
5
|
+
import { buildMcpOperationInvocation, isMcpModelToolName, MCP_TOOL_DEFINITIONS, } from "./operations.js";
|
|
6
|
+
import { textResult } from "./tool-result.js";
|
|
7
|
+
const MODERN_VERSION = "2026-07-28";
|
|
8
|
+
const LEGACY_VERSIONS = new Set(["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"]);
|
|
9
|
+
const PROTOCOL_VERSION_META = "io.modelcontextprotocol/protocolVersion";
|
|
10
|
+
const CLIENT_INFO_META = "io.modelcontextprotocol/clientInfo";
|
|
11
|
+
const CLIENT_CAPABILITIES_META = "io.modelcontextprotocol/clientCapabilities";
|
|
12
|
+
const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
13
|
+
const SERVER_INFO_META = "io.modelcontextprotocol/serverInfo";
|
|
14
|
+
export function createMcpMessageHandler(env) {
|
|
15
|
+
let mode;
|
|
16
|
+
return async (message) => {
|
|
17
|
+
let requestId = isRecord(message) && "id" in message &&
|
|
18
|
+
(typeof message.id === "string" || typeof message.id === "number" || message.id === null)
|
|
19
|
+
? message.id
|
|
20
|
+
: null;
|
|
21
|
+
try {
|
|
22
|
+
const request = parseRequest(message);
|
|
23
|
+
if (!request)
|
|
24
|
+
return undefined;
|
|
25
|
+
requestId = request.id;
|
|
26
|
+
const { id, method, params } = request;
|
|
27
|
+
if (method === "server/discover") {
|
|
28
|
+
requireModernMetadata(params);
|
|
29
|
+
mode = "modern";
|
|
30
|
+
return response(id, {
|
|
31
|
+
resultType: "complete",
|
|
32
|
+
supportedVersions: [MODERN_VERSION],
|
|
33
|
+
capabilities: { tools: {}, extensions: { [TASKS_EXTENSION]: {} } },
|
|
34
|
+
_meta: { [SERVER_INFO_META]: { name: "kimi-plugin-cc", version: KIMI_PLUGIN_CC_VERSION } },
|
|
35
|
+
ttlMs: 0,
|
|
36
|
+
cacheScope: "private",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (method === "initialize") {
|
|
40
|
+
const requested = params.protocolVersion;
|
|
41
|
+
if (typeof requested !== "string" || !LEGACY_VERSIONS.has(requested)) {
|
|
42
|
+
throw rpcError(-32_602, "Unsupported protocol version", { supportedVersions: [...LEGACY_VERSIONS] });
|
|
43
|
+
}
|
|
44
|
+
mode = "legacy";
|
|
45
|
+
return response(id, {
|
|
46
|
+
protocolVersion: requested,
|
|
47
|
+
capabilities: { tools: {} },
|
|
48
|
+
serverInfo: { name: "kimi-plugin-cc", version: KIMI_PLUGIN_CC_VERSION },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (method === "notifications/initialized")
|
|
52
|
+
return undefined;
|
|
53
|
+
if (method.startsWith("notifications/"))
|
|
54
|
+
return undefined;
|
|
55
|
+
if (!mode && hasModernVersion(params))
|
|
56
|
+
mode = "modern";
|
|
57
|
+
if (!mode)
|
|
58
|
+
throw rpcError(-32_002, "Server is not initialized");
|
|
59
|
+
const modern = requireProtocolMode(mode, method);
|
|
60
|
+
const capabilities = modern ? requireModernMetadata(params) : undefined;
|
|
61
|
+
switch (method) {
|
|
62
|
+
case "ping":
|
|
63
|
+
return response(id, modern ? { resultType: "complete" } : {});
|
|
64
|
+
case "tools/list":
|
|
65
|
+
return response(id, modern
|
|
66
|
+
? { resultType: "complete", tools: MCP_TOOL_DEFINITIONS, ttlMs: 0, cacheScope: "private" }
|
|
67
|
+
: { tools: MCP_TOOL_DEFINITIONS });
|
|
68
|
+
case "tools/call":
|
|
69
|
+
return response(id, await callTool(env, params, modern, capabilities));
|
|
70
|
+
case "tasks/get": {
|
|
71
|
+
requireModern(method, modern);
|
|
72
|
+
requireTasksCapability(method, capabilities);
|
|
73
|
+
const taskId = requiredTaskId(params);
|
|
74
|
+
try {
|
|
75
|
+
const view = await getMcpTaskView(env, { job_id: taskId });
|
|
76
|
+
return response(id, { resultType: "complete", ...view });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof RuntimeError && error.code === "JOB_NOT_FOUND") {
|
|
80
|
+
throw rpcError(-32_602, `Unknown task ID: ${taskId}`);
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
case "tasks/cancel": {
|
|
86
|
+
requireModern(method, modern);
|
|
87
|
+
requireTasksCapability(method, capabilities);
|
|
88
|
+
const taskId = requiredTaskId(params);
|
|
89
|
+
try {
|
|
90
|
+
await cancelMcpJob(env, { job_id: taskId });
|
|
91
|
+
return response(id, { resultType: "complete" });
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof RuntimeError && error.code === "JOB_NOT_FOUND") {
|
|
95
|
+
throw rpcError(-32_602, `Unknown task ID: ${taskId}`);
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
default:
|
|
101
|
+
throw rpcError(-32_601, `Method not found: ${method}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (isRpcError(error))
|
|
106
|
+
return response(requestId, undefined, error);
|
|
107
|
+
if (error instanceof RuntimeError) {
|
|
108
|
+
return response(requestId, undefined, rpcError(-32_603, formatError(error), error.details));
|
|
109
|
+
}
|
|
110
|
+
return response(requestId, undefined, rpcError(-32_603, error instanceof Error ? error.message : String(error)));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function callTool(env, params, modern, capabilities) {
|
|
115
|
+
const name = params.name;
|
|
116
|
+
if (typeof name !== "string")
|
|
117
|
+
throw rpcError(-32_602, "tools/call requires a tool name");
|
|
118
|
+
try {
|
|
119
|
+
let result;
|
|
120
|
+
if (isMcpModelToolName(name)) {
|
|
121
|
+
const launched = await launchMcpOperation(buildMcpOperationInvocation(name, params.arguments), env);
|
|
122
|
+
if (modern && hasTasksCapability(capabilities))
|
|
123
|
+
return taskHandle(launched);
|
|
124
|
+
result = launched.job.status === "running"
|
|
125
|
+
? launchResult(launched)
|
|
126
|
+
: await getMcpJobResult(env, { job_id: launched.task.task_id });
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
switch (name) {
|
|
130
|
+
case "kimi_job_status":
|
|
131
|
+
result = await getMcpJobStatus(env, params.arguments);
|
|
132
|
+
break;
|
|
133
|
+
case "kimi_job_result":
|
|
134
|
+
result = await getMcpJobResult(env, params.arguments);
|
|
135
|
+
break;
|
|
136
|
+
case "kimi_job_cancel":
|
|
137
|
+
result = await cancelMcpJob(env, params.arguments);
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
throw rpcError(-32_602, `Unknown tool: ${name}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return modern ? { resultType: "complete", ...result } : result;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (isRpcError(error))
|
|
147
|
+
throw error;
|
|
148
|
+
if (error instanceof RuntimeError) {
|
|
149
|
+
const result = textResult(formatError(error), {
|
|
150
|
+
error: { code: error.code, stage: error.stage, details: error.details },
|
|
151
|
+
}, true);
|
|
152
|
+
return modern ? { resultType: "complete", ...result } : result;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function launchResult(launched) {
|
|
158
|
+
const { task, job } = launched;
|
|
159
|
+
const metadata = {
|
|
160
|
+
job_id: task.task_id,
|
|
161
|
+
operation_kind: task.operation_kind,
|
|
162
|
+
workspace: task.workspace_cwd,
|
|
163
|
+
status: job.status,
|
|
164
|
+
phase: job.phase,
|
|
165
|
+
created_at: task.created_at,
|
|
166
|
+
};
|
|
167
|
+
return textResult(`Started ${task.operation_kind} job ${task.task_id}. This is a launch handle, not the finished answer. Use kimi_job_status and kimi_job_result with this job_id until the job is terminal; use kimi_job_cancel to stop it.`, metadata);
|
|
168
|
+
}
|
|
169
|
+
function taskHandle({ task, job }) {
|
|
170
|
+
return {
|
|
171
|
+
resultType: "task",
|
|
172
|
+
taskId: task.task_id,
|
|
173
|
+
status: job.status === "running" ? "working" : job.status === "cancelled" ? "cancelled" : "completed",
|
|
174
|
+
...(job.status === "running" ? { statusMessage: `${task.operation_kind}: ${job.phase ?? "running"}` } : job.summary ? { statusMessage: job.summary } : {}),
|
|
175
|
+
createdAt: task.created_at,
|
|
176
|
+
lastUpdatedAt: job.updated_at,
|
|
177
|
+
ttlMs: null,
|
|
178
|
+
pollIntervalMs: 2_000,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function parseRequest(message) {
|
|
182
|
+
if (!isRecord(message))
|
|
183
|
+
throw new McpJsonRpcError(-32_600, "Invalid JSON-RPC request");
|
|
184
|
+
if (!("method" in message))
|
|
185
|
+
return undefined;
|
|
186
|
+
if (message.jsonrpc !== "2.0" || typeof message.method !== "string" || message.method.length === 0) {
|
|
187
|
+
throw new McpJsonRpcError(-32_600, "Invalid JSON-RPC request");
|
|
188
|
+
}
|
|
189
|
+
if (!("id" in message))
|
|
190
|
+
return undefined;
|
|
191
|
+
const id = message.id;
|
|
192
|
+
if (id !== null && typeof id !== "string" &&
|
|
193
|
+
(typeof id !== "number" || !Number.isFinite(id))) {
|
|
194
|
+
throw new McpJsonRpcError(-32_600, "Invalid JSON-RPC request ID");
|
|
195
|
+
}
|
|
196
|
+
const params = message.params === undefined ? {} : message.params;
|
|
197
|
+
if (!isRecord(params))
|
|
198
|
+
throw new McpJsonRpcError(-32_602, "Request params must be an object");
|
|
199
|
+
return { id, method: message.method, params };
|
|
200
|
+
}
|
|
201
|
+
function requireProtocolMode(mode, method) {
|
|
202
|
+
if (mode === "modern")
|
|
203
|
+
return true;
|
|
204
|
+
if (method === "tasks/get" || method === "tasks/cancel") {
|
|
205
|
+
throw new McpJsonRpcError(-32_601, `Method not found: ${method}`);
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
function requireModern(method, modern) {
|
|
210
|
+
if (!modern)
|
|
211
|
+
throw new McpJsonRpcError(-32_601, `Method not found: ${method}`);
|
|
212
|
+
}
|
|
213
|
+
function hasModernVersion(params) {
|
|
214
|
+
return isRecord(params._meta) && params._meta[PROTOCOL_VERSION_META] === MODERN_VERSION;
|
|
215
|
+
}
|
|
216
|
+
function requireModernMetadata(params) {
|
|
217
|
+
const meta = params._meta;
|
|
218
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
219
|
+
throw new McpJsonRpcError(-32_602, "Modern MCP requests require protocol metadata");
|
|
220
|
+
}
|
|
221
|
+
const record = meta;
|
|
222
|
+
if (record[PROTOCOL_VERSION_META] !== MODERN_VERSION) {
|
|
223
|
+
throw new McpJsonRpcError(-32_022, "Unsupported protocol version", {
|
|
224
|
+
supported: [MODERN_VERSION],
|
|
225
|
+
requested: record[PROTOCOL_VERSION_META],
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (record[CLIENT_INFO_META] !== undefined && !isRecord(record[CLIENT_INFO_META])) {
|
|
229
|
+
throw new McpJsonRpcError(-32_602, "clientInfo must be an object when supplied");
|
|
230
|
+
}
|
|
231
|
+
if (!isRecord(record[CLIENT_CAPABILITIES_META])) {
|
|
232
|
+
throw new McpJsonRpcError(-32_602, "Modern MCP requests require client capabilities");
|
|
233
|
+
}
|
|
234
|
+
return record[CLIENT_CAPABILITIES_META];
|
|
235
|
+
}
|
|
236
|
+
function hasTasksCapability(capabilities) {
|
|
237
|
+
if (!capabilities || !isRecord(capabilities.extensions))
|
|
238
|
+
return false;
|
|
239
|
+
return isRecord(capabilities.extensions[TASKS_EXTENSION]);
|
|
240
|
+
}
|
|
241
|
+
function requireTasksCapability(method, capabilities) {
|
|
242
|
+
if (!hasTasksCapability(capabilities)) {
|
|
243
|
+
throw new McpJsonRpcError(-32_021, "Missing required client capability", {
|
|
244
|
+
requiredCapabilities: { extensions: { [TASKS_EXTENSION]: {} } },
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function requiredTaskId(params) {
|
|
249
|
+
if (typeof params.taskId !== "string" || params.taskId.length === 0 || params.taskId.length > 128 || !/^[\x21-\x7e]+$/.test(params.taskId)) {
|
|
250
|
+
throw new McpJsonRpcError(-32_602, "taskId must be 1-128 printable ASCII characters without spaces");
|
|
251
|
+
}
|
|
252
|
+
return params.taskId;
|
|
253
|
+
}
|
|
254
|
+
function response(id, result, error) {
|
|
255
|
+
return error ? { jsonrpc: "2.0", id, error: { code: error.code, message: error.message, ...(error.data !== undefined ? { data: error.data } : {}) } } : { jsonrpc: "2.0", id, result };
|
|
256
|
+
}
|
|
257
|
+
function rpcError(code, message, data) {
|
|
258
|
+
return new McpJsonRpcError(code, message, data);
|
|
259
|
+
}
|
|
260
|
+
class McpJsonRpcError extends Error {
|
|
261
|
+
code;
|
|
262
|
+
data;
|
|
263
|
+
constructor(code, message, data) {
|
|
264
|
+
super(message);
|
|
265
|
+
this.code = code;
|
|
266
|
+
this.data = data;
|
|
267
|
+
this.name = "McpJsonRpcError";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function isRpcError(error) {
|
|
271
|
+
return error instanceof McpJsonRpcError || (error instanceof Error && "code" in error && typeof error.code === "number");
|
|
272
|
+
}
|
|
273
|
+
function isRecord(value) {
|
|
274
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
275
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
2
|
+
import { createMcpMessageHandler } from "./protocol.js";
|
|
3
|
+
import { serveMcpStdio } from "./stdio.js";
|
|
4
|
+
export async function runMcpServer(context) {
|
|
5
|
+
const paths = resolvePluginPaths(context.env);
|
|
6
|
+
await ensurePluginPaths(paths);
|
|
7
|
+
await serveMcpStdio(process.stdin, process.stdout, createMcpMessageHandler(context.env));
|
|
8
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const MAX_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
2
|
+
export async function serveMcpStdio(input, output, handle) {
|
|
3
|
+
let buffer = Buffer.alloc(0);
|
|
4
|
+
let writeTail = Promise.resolve();
|
|
5
|
+
let writeError;
|
|
6
|
+
const pending = new Set();
|
|
7
|
+
output.on("error", (error) => { writeError ??= error; });
|
|
8
|
+
const write = (message) => {
|
|
9
|
+
if (writeError)
|
|
10
|
+
return Promise.reject(writeError);
|
|
11
|
+
const frame = `${JSON.stringify(message)}\n`;
|
|
12
|
+
writeTail = writeTail.then(() => new Promise((resolve, reject) => {
|
|
13
|
+
try {
|
|
14
|
+
if (output.write(frame))
|
|
15
|
+
resolve();
|
|
16
|
+
else
|
|
17
|
+
output.once("drain", resolve);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
reject(error);
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
return writeTail;
|
|
24
|
+
};
|
|
25
|
+
const track = (promise) => {
|
|
26
|
+
const tracked = promise.catch((error) => { writeError ??= error; }).finally(() => pending.delete(tracked));
|
|
27
|
+
pending.add(tracked);
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
for await (const chunk of input) {
|
|
31
|
+
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
32
|
+
for (;;) {
|
|
33
|
+
const newline = buffer.indexOf(0x0a);
|
|
34
|
+
if (newline < 0)
|
|
35
|
+
break;
|
|
36
|
+
const line = buffer.subarray(0, newline);
|
|
37
|
+
buffer = buffer.subarray(newline + 1);
|
|
38
|
+
if (line.length > MAX_MESSAGE_BYTES)
|
|
39
|
+
throw new Error("MCP frame exceeds 8 MiB");
|
|
40
|
+
if (line.toString("utf8").trim().length === 0)
|
|
41
|
+
continue;
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(line.toString("utf8"));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
track(write({ jsonrpc: "2.0", id: null, error: { code: -32_700, message: "Parse error" } }));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
track((async () => {
|
|
51
|
+
try {
|
|
52
|
+
const response = await handle(parsed);
|
|
53
|
+
if (response !== undefined)
|
|
54
|
+
await write(response);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
await write({
|
|
58
|
+
jsonrpc: "2.0",
|
|
59
|
+
id: null,
|
|
60
|
+
error: { code: -32_603, message: error instanceof Error ? error.message : String(error) },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
})());
|
|
64
|
+
}
|
|
65
|
+
if (buffer.length > MAX_MESSAGE_BYTES)
|
|
66
|
+
throw new Error("MCP frame exceeds 8 MiB");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
await write({ jsonrpc: "2.0", id: null, error: { code: -32_700, message: error instanceof Error ? error.message : String(error) } });
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await Promise.all(pending);
|
|
74
|
+
await writeTail;
|
|
75
|
+
if (writeError)
|
|
76
|
+
throw writeError;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RuntimeError } from "../errors.js";
|
|
2
|
+
import { allocateJobId } from "../job-id.js";
|
|
3
|
+
import { runModelCommand } from "../model-command.js";
|
|
4
|
+
export async function runMcpChild(argv, context) {
|
|
5
|
+
const [jobId, ...command] = argv;
|
|
6
|
+
if (!jobId || command.length === 0) {
|
|
7
|
+
throw new RuntimeError("MCP_INVALID_CHILD", "mcp-child expects a job UUID followed by one model operation.", "mcp.child");
|
|
8
|
+
}
|
|
9
|
+
const mcpJobId = allocateJobId({ ...context, mcpJobId: jobId });
|
|
10
|
+
const result = await runModelCommand(command, { ...context, mcpJobId });
|
|
11
|
+
if (result === undefined) {
|
|
12
|
+
throw new RuntimeError("MCP_INVALID_CHILD", "mcp-child accepts only the public model operations.", "mcp.child");
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|