@moor-sh/mcp 0.22.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +52 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moor-sh/mcp",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "MCP server for moor - lets AI agents (Claude Code, Cursor, etc.) manage your moor projects via the moor HTTP API.",
5
5
  "license": "MIT",
6
6
  "repository": {
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<{ logs: string; error?: string }> {
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
  },
@@ -2099,12 +2124,20 @@ server.registerTool(
2099
2124
 
2100
2125
  // Step 3: run, default true. Wait for the full SSE stream like moor_rebuild.
2101
2126
  let runLogs = "";
2127
+ let runStructuredError: { code: string; message: string } | undefined;
2102
2128
  if (input.run) {
2103
2129
  const runRes = await apiPost(`/api/projects/${projectId}/run`);
2104
2130
  if (!runRes.ok) throw new Error(`[run] ${await runRes.text()}`);
2105
- const { logs, error } = await readSSE(runRes);
2131
+ const { logs, error, structuredError } = await readSSE(runRes);
2106
2132
  runLogs = logs;
2107
- if (error) throw new Error(`[run] ${error}`);
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
+ }
2108
2141
  }
2109
2142
 
2110
2143
  const lines: string[] = [];
@@ -2129,6 +2162,20 @@ server.registerTool(
2129
2162
  lines.push("Build/run output:");
2130
2163
  lines.push(runLogs || "(no output)");
2131
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
+ }
2132
2179
  return { content: [{ type: "text", text: lines.join("\n") }] };
2133
2180
  },
2134
2181
  );