@moor-sh/mcp 0.22.0 → 0.23.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/package.json +1 -1
- package/src/index.ts +59 -5
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -114,7 +114,11 @@ async function resolveProject(name: string): Promise<Project> {
|
|
|
114
114
|
|
|
115
115
|
// --- SSE stream reader ---
|
|
116
116
|
|
|
117
|
-
async function readSSE(res: Response): Promise<{
|
|
117
|
+
async function readSSE(res: Response): Promise<{
|
|
118
|
+
logs: string;
|
|
119
|
+
error?: string;
|
|
120
|
+
structuredError?: { code: string; message: string };
|
|
121
|
+
}> {
|
|
118
122
|
const reader = res.body?.getReader();
|
|
119
123
|
if (!reader) return { logs: "" };
|
|
120
124
|
|
|
@@ -123,6 +127,7 @@ async function readSSE(res: Response): Promise<{ logs: string; error?: string }>
|
|
|
123
127
|
let currentEvent = "";
|
|
124
128
|
let logs = "";
|
|
125
129
|
let error: string | undefined;
|
|
130
|
+
let structuredError: { code: string; message: string } | undefined;
|
|
126
131
|
|
|
127
132
|
while (true) {
|
|
128
133
|
const { done, value } = await reader.read();
|
|
@@ -139,11 +144,16 @@ async function readSSE(res: Response): Promise<{ logs: string; error?: string }>
|
|
|
139
144
|
const data = JSON.parse(line.slice(6));
|
|
140
145
|
if (currentEvent === "log") logs += data;
|
|
141
146
|
else if (currentEvent === "error") error = data;
|
|
147
|
+
// #119: structured-error fires alongside event: error when the
|
|
148
|
+
// server classifies a build failure (today: source_credential_required).
|
|
149
|
+
// Captured here so deploy/rebuild tools can surface the code to
|
|
150
|
+
// the agent instead of throwing a generic message.
|
|
151
|
+
else if (currentEvent === "structured-error") structuredError = data;
|
|
142
152
|
currentEvent = "";
|
|
143
153
|
}
|
|
144
154
|
}
|
|
145
155
|
}
|
|
146
|
-
return { logs, error };
|
|
156
|
+
return { logs, error, structuredError };
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
// --- Validators ---
|
|
@@ -385,7 +395,29 @@ server.registerTool(
|
|
|
385
395
|
const p = await resolveProject(project);
|
|
386
396
|
const query = no_cache ? "?nocache=true" : "";
|
|
387
397
|
const res = await apiPost(`/api/projects/${p.id}/run${query}`);
|
|
388
|
-
|
|
398
|
+
// /run can fail BEFORE opening the SSE stream — resolver validation,
|
|
399
|
+
// drain mode, invalid URL, credential_not_active. Those land as a
|
|
400
|
+
// plain JSON or text body that readSSE walks without matching any
|
|
401
|
+
// event:/data: lines, returning empty everything. Without this guard
|
|
402
|
+
// the tool would silently report "Rebuild complete." on a failed build.
|
|
403
|
+
// Mirrors the existing moor_deploy guard at the /run call site.
|
|
404
|
+
if (!res.ok) throw new Error(`[run] ${await res.text()}`);
|
|
405
|
+
const { logs, error, structuredError } = await readSSE(res);
|
|
406
|
+
// #119: a classified failure (today: source_credential_required) gets
|
|
407
|
+
// returned as isError with a structured payload the agent can branch
|
|
408
|
+
// on. Unclassified errors keep throwing so the existing UX is preserved.
|
|
409
|
+
if (structuredError) {
|
|
410
|
+
return {
|
|
411
|
+
content: [
|
|
412
|
+
{
|
|
413
|
+
type: "text",
|
|
414
|
+
text: `rebuild failed: code=${structuredError.code} message=${structuredError.message}`,
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
structuredContent: structuredError,
|
|
418
|
+
isError: true,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
389
421
|
if (error) throw new Error(error);
|
|
390
422
|
return { content: [{ type: "text", text: logs || "Rebuild complete." }] };
|
|
391
423
|
},
|
|
@@ -2099,12 +2131,20 @@ server.registerTool(
|
|
|
2099
2131
|
|
|
2100
2132
|
// Step 3: run, default true. Wait for the full SSE stream like moor_rebuild.
|
|
2101
2133
|
let runLogs = "";
|
|
2134
|
+
let runStructuredError: { code: string; message: string } | undefined;
|
|
2102
2135
|
if (input.run) {
|
|
2103
2136
|
const runRes = await apiPost(`/api/projects/${projectId}/run`);
|
|
2104
2137
|
if (!runRes.ok) throw new Error(`[run] ${await runRes.text()}`);
|
|
2105
|
-
const { logs, error } = await readSSE(runRes);
|
|
2138
|
+
const { logs, error, structuredError } = await readSSE(runRes);
|
|
2106
2139
|
runLogs = logs;
|
|
2107
|
-
|
|
2140
|
+
// #119: classified failure (today: source_credential_required) is
|
|
2141
|
+
// returned as isError below so the agent can branch on the code
|
|
2142
|
+
// instead of parsing a thrown message.
|
|
2143
|
+
if (structuredError) {
|
|
2144
|
+
runStructuredError = structuredError;
|
|
2145
|
+
} else if (error) {
|
|
2146
|
+
throw new Error(`[run] ${error}`);
|
|
2147
|
+
}
|
|
2108
2148
|
}
|
|
2109
2149
|
|
|
2110
2150
|
const lines: string[] = [];
|
|
@@ -2129,6 +2169,20 @@ server.registerTool(
|
|
|
2129
2169
|
lines.push("Build/run output:");
|
|
2130
2170
|
lines.push(runLogs || "(no output)");
|
|
2131
2171
|
}
|
|
2172
|
+
// #119: if the build was classified as auth-failure, return isError
|
|
2173
|
+
// with the structured payload so the agent can call _check + add a
|
|
2174
|
+
// credential and retry. The project row exists (create/update already
|
|
2175
|
+
// committed) so the agent just needs to fix the credential and run
|
|
2176
|
+
// deploy again with the pinned id.
|
|
2177
|
+
if (runStructuredError) {
|
|
2178
|
+
lines.push("");
|
|
2179
|
+
lines.push(`Failed: code=${runStructuredError.code} message=${runStructuredError.message}`);
|
|
2180
|
+
return {
|
|
2181
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
2182
|
+
structuredContent: { ...runStructuredError, project_id: projectId },
|
|
2183
|
+
isError: true,
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2132
2186
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2133
2187
|
},
|
|
2134
2188
|
);
|