@moor-sh/mcp 0.21.0 → 0.23.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/package.json +1 -1
- package/src/index.ts +82 -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,22 @@ 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
|
-
const { logs, error } = await readSSE(res);
|
|
398
|
+
const { logs, error, structuredError } = await readSSE(res);
|
|
399
|
+
// #119: a classified failure (today: source_credential_required) gets
|
|
400
|
+
// returned as isError with a structured payload the agent can branch
|
|
401
|
+
// on. Unclassified errors keep throwing so the existing UX is preserved.
|
|
402
|
+
if (structuredError) {
|
|
403
|
+
return {
|
|
404
|
+
content: [
|
|
405
|
+
{
|
|
406
|
+
type: "text",
|
|
407
|
+
text: `rebuild failed: code=${structuredError.code} message=${structuredError.message}`,
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
structuredContent: structuredError,
|
|
411
|
+
isError: true,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
389
414
|
if (error) throw new Error(error);
|
|
390
415
|
return { content: [{ type: "text", text: logs || "Rebuild complete." }] };
|
|
391
416
|
},
|
|
@@ -1174,6 +1199,15 @@ server.registerTool(
|
|
|
1174
1199
|
.describe(
|
|
1175
1200
|
"Named Docker volumes to attach. Each entry creates a per-project volume (stored as moor-<project>-<name>) and mounts it at the given target on next container recreate. Data survives container/project rebuilds unless explicitly purged via project delete with purge_volumes=true.",
|
|
1176
1201
|
),
|
|
1202
|
+
source_credential_id: z
|
|
1203
|
+
.number()
|
|
1204
|
+
.int()
|
|
1205
|
+
.positive()
|
|
1206
|
+
.nullable()
|
|
1207
|
+
.optional()
|
|
1208
|
+
.describe(
|
|
1209
|
+
"For github_url projects: pin the source credential row (from moor_source_credential_add) the build path should use. Build synthesizes the credentialed clone URL in memory; the secret is never stored on the project. Ignored when docker_image is set; save-time validation is structural only (id exists).",
|
|
1210
|
+
),
|
|
1177
1211
|
}),
|
|
1178
1212
|
},
|
|
1179
1213
|
async (input) => {
|
|
@@ -1254,6 +1288,15 @@ server.registerTool(
|
|
|
1254
1288
|
.describe(
|
|
1255
1289
|
"Max CPU cores (fractional OK; min 0.001). Pass null to clear. Max host core count. Takes effect on container recreate.",
|
|
1256
1290
|
),
|
|
1291
|
+
source_credential_id: z
|
|
1292
|
+
.number()
|
|
1293
|
+
.int()
|
|
1294
|
+
.positive()
|
|
1295
|
+
.nullable()
|
|
1296
|
+
.optional()
|
|
1297
|
+
.describe(
|
|
1298
|
+
"Pin (or unlink, by passing null) the source credential the build path should use for this github_url project. Switching to docker_image force-clears the id regardless of input. Save-time validation is structural only; host-mismatch / not-active is enforced at build time.",
|
|
1299
|
+
),
|
|
1257
1300
|
}),
|
|
1258
1301
|
},
|
|
1259
1302
|
async (input) => {
|
|
@@ -1879,6 +1922,15 @@ server.registerTool(
|
|
|
1879
1922
|
.describe(
|
|
1880
1923
|
"Env vars to MERGE into existing project envs. Omit to leave envs untouched. Pass {} for an explicit no-op. Use moor_env_delete to remove keys.",
|
|
1881
1924
|
),
|
|
1925
|
+
source_credential_id: z
|
|
1926
|
+
.number()
|
|
1927
|
+
.int()
|
|
1928
|
+
.positive()
|
|
1929
|
+
.nullable()
|
|
1930
|
+
.optional()
|
|
1931
|
+
.describe(
|
|
1932
|
+
"For github_url projects: pin the source credential row (created via moor_source_credential_add). Build path synthesizes the credentialed clone URL in memory; secret never gets stored on the project row. Pass null to detach without switching source type. Ignored when docker_image is set. Save-time validation is structural only (id exists); host-mismatch / not-active is enforced at build time so configuration can survive transient credential outages.",
|
|
1933
|
+
),
|
|
1882
1934
|
run: z
|
|
1883
1935
|
.boolean()
|
|
1884
1936
|
.optional()
|
|
@@ -1978,6 +2030,7 @@ server.registerTool(
|
|
|
1978
2030
|
restart_policy: input.restart_policy,
|
|
1979
2031
|
memory_limit_mb: input.memory_limit_mb,
|
|
1980
2032
|
cpus: input.cpus,
|
|
2033
|
+
source_credential_id: input.source_credential_id,
|
|
1981
2034
|
};
|
|
1982
2035
|
const res = await apiPost("/api/projects", createBody);
|
|
1983
2036
|
if (!res.ok) throw new Error(`[create] ${await res.text()}`);
|
|
@@ -1997,6 +2050,8 @@ server.registerTool(
|
|
|
1997
2050
|
if (input.restart_policy !== undefined) updateBody.restart_policy = input.restart_policy;
|
|
1998
2051
|
if (input.memory_limit_mb !== undefined) updateBody.memory_limit_mb = input.memory_limit_mb;
|
|
1999
2052
|
if (input.cpus !== undefined) updateBody.cpus = input.cpus;
|
|
2053
|
+
if (input.source_credential_id !== undefined)
|
|
2054
|
+
updateBody.source_credential_id = input.source_credential_id;
|
|
2000
2055
|
|
|
2001
2056
|
if (Object.keys(updateBody).length > 0) {
|
|
2002
2057
|
const res = await apiPut(`/api/projects/${existing.id}`, updateBody);
|
|
@@ -2069,12 +2124,20 @@ server.registerTool(
|
|
|
2069
2124
|
|
|
2070
2125
|
// Step 3: run, default true. Wait for the full SSE stream like moor_rebuild.
|
|
2071
2126
|
let runLogs = "";
|
|
2127
|
+
let runStructuredError: { code: string; message: string } | undefined;
|
|
2072
2128
|
if (input.run) {
|
|
2073
2129
|
const runRes = await apiPost(`/api/projects/${projectId}/run`);
|
|
2074
2130
|
if (!runRes.ok) throw new Error(`[run] ${await runRes.text()}`);
|
|
2075
|
-
const { logs, error } = await readSSE(runRes);
|
|
2131
|
+
const { logs, error, structuredError } = await readSSE(runRes);
|
|
2076
2132
|
runLogs = logs;
|
|
2077
|
-
|
|
2133
|
+
// #119: classified failure (today: source_credential_required) is
|
|
2134
|
+
// returned as isError below so the agent can branch on the code
|
|
2135
|
+
// instead of parsing a thrown message.
|
|
2136
|
+
if (structuredError) {
|
|
2137
|
+
runStructuredError = structuredError;
|
|
2138
|
+
} else if (error) {
|
|
2139
|
+
throw new Error(`[run] ${error}`);
|
|
2140
|
+
}
|
|
2078
2141
|
}
|
|
2079
2142
|
|
|
2080
2143
|
const lines: string[] = [];
|
|
@@ -2099,6 +2162,20 @@ server.registerTool(
|
|
|
2099
2162
|
lines.push("Build/run output:");
|
|
2100
2163
|
lines.push(runLogs || "(no output)");
|
|
2101
2164
|
}
|
|
2165
|
+
// #119: if the build was classified as auth-failure, return isError
|
|
2166
|
+
// with the structured payload so the agent can call _check + add a
|
|
2167
|
+
// credential and retry. The project row exists (create/update already
|
|
2168
|
+
// committed) so the agent just needs to fix the credential and run
|
|
2169
|
+
// deploy again with the pinned id.
|
|
2170
|
+
if (runStructuredError) {
|
|
2171
|
+
lines.push("");
|
|
2172
|
+
lines.push(`Failed: code=${runStructuredError.code} message=${runStructuredError.message}`);
|
|
2173
|
+
return {
|
|
2174
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
2175
|
+
structuredContent: { ...runStructuredError, project_id: projectId },
|
|
2176
|
+
isError: true,
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2102
2179
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2103
2180
|
},
|
|
2104
2181
|
);
|