@superblocksteam/gateway 2.0.157-next.3 → 2.0.157-next.4

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 (40) hide show
  1. package/README.md +3 -30
  2. package/dist/capabilities/lifecycle.d.ts +1 -12
  3. package/dist/capabilities/lifecycle.js +51 -99
  4. package/dist/capabilities/lifecycle.js.map +1 -1
  5. package/dist/capabilities/types.d.ts +0 -5
  6. package/dist/capabilities/types.js.map +1 -1
  7. package/dist/deps.d.ts +0 -2
  8. package/dist/integrations/map.d.ts +0 -1
  9. package/dist/integrations/map.js +1 -1
  10. package/dist/integrations/map.js.map +1 -1
  11. package/dist/sabs/editor-client-methods.js +5 -4
  12. package/dist/sabs/editor-client-methods.js.map +1 -1
  13. package/dist/sabs/session-peer.d.ts +0 -4
  14. package/dist/sabs/websocket-session-peer.d.ts +1 -2
  15. package/dist/sabs/websocket-session-peer.js +11 -21
  16. package/dist/sabs/websocket-session-peer.js.map +1 -1
  17. package/dist/server/client.d.ts +4 -10
  18. package/dist/server/client.js +4 -13
  19. package/dist/server/client.js.map +1 -1
  20. package/dist/start.d.ts +0 -10
  21. package/dist/start.js +2 -5
  22. package/dist/start.js.map +1 -1
  23. package/dist/transports/mcp/admin-tools.d.ts +1 -3
  24. package/dist/transports/mcp/admin-tools.js +1 -12
  25. package/dist/transports/mcp/admin-tools.js.map +1 -1
  26. package/dist/transports/mcp/app-status-html.d.ts +2 -2
  27. package/dist/transports/mcp/app-status-html.js +8 -139
  28. package/dist/transports/mcp/app-status-html.js.map +1 -1
  29. package/dist/transports/mcp/mount.js +24 -89
  30. package/dist/transports/mcp/mount.js.map +1 -1
  31. package/package.json +4 -5
  32. package/dist/capabilities/integration-metadata.d.ts +0 -28
  33. package/dist/capabilities/integration-metadata.js +0 -174
  34. package/dist/capabilities/integration-metadata.js.map +0 -1
  35. package/dist/telemetry/mcp-client.d.ts +0 -22
  36. package/dist/telemetry/mcp-client.js +0 -129
  37. package/dist/telemetry/mcp-client.js.map +0 -1
  38. package/dist/telemetry/runtime.d.ts +0 -9
  39. package/dist/telemetry/runtime.js +0 -121
  40. package/dist/telemetry/runtime.js.map +0 -1
package/README.md CHANGED
@@ -4,8 +4,8 @@ Standalone Superblocks entry point for a **single MCP connector** (Admin + Build
4
4
  over **stdio**, running as the already-logged-in Superblocks CLI user.
5
5
 
6
6
  Builder tools (`start_app`, `import_app`, `edit_app`, `check_app_progress`,
7
- `get_app`, `get_integration_metadata`, `preview_app`, `publish_app`) plus
8
- customer Admin tools from `@superblocksteam/mcp-server` share this process.
7
+ `get_app`, `preview_app`, `publish_app`) plus customer Admin tools from
8
+ `@superblocksteam/mcp-server` share this process.
9
9
 
10
10
  The MCP host owns process lifecycle: it spawns `superblocks gateway serve`.
11
11
  There is no foreground HTTP `/mcp`, no OAuth resource server, and no linked-grant
@@ -147,18 +147,7 @@ package only the customer Admin surface. Builder tools are not registered.
147
147
  ## MCP tools
148
148
 
149
149
  After `gateway setup`, restart Claude Code or Cursor and call `start_app`,
150
- `edit_app`, `check_app_progress`, `get_app`, `get_integration_metadata`,
151
- `preview_app`, and `publish_app`. `get_integration_metadata` reads tables,
152
- columns, and types from a connected integration; use `search`, `limit`, and
153
- `offset` for large results. It needs no application: without one it mints an
154
- `integrations:build` token scoped to that single integration. Pass
155
- `applicationId` only for an integration owned by one application, such as a
156
- Native DB, which is invisible without app context.
157
- It resolves the integration through the RBAC-annotated superset list rather than
158
- the org-wide lookup by id, so it answers `integration_not_permitted` without
159
- build permission on that integration and `integration_not_supported` for a
160
- plugin Clark cannot use as a tool.
161
- `get_integration_config_schema` is the create-integration form, not live data.
150
+ `edit_app`, `check_app_progress`, `get_app`, `preview_app`, and `publish_app`.
162
151
  See `.env.example` for optional configuration.
163
152
 
164
153
  ## Env
@@ -179,19 +168,3 @@ Gateway debug logs include tool names, result states, decision branches, and
179
168
  entry-point stacks. They intentionally omit credentials, prompts, answers, and
180
169
  result payloads. Run `superblocks gateway setup --client <client> --debug`, then
181
170
  restart the MCP host. Re-run setup without `--debug` to turn them off.
182
-
183
- ## Remote traceability
184
-
185
- Gateway exports one OpenTelemetry trace per MCP process through the authenticated
186
- Server `/api/v1/traces` proxy. Each tool call is a child span with the MCP client
187
- name and version, tool name, duration, outcome, organization ID, and application
188
- ID when available.
189
-
190
- Prompts, answers, result payloads, credentials, and raw error messages are never
191
- added to these spans. Use the application ID to correlate a trace with the
192
- existing server-side app and build records.
193
-
194
- Cloud-Prem uses `SUPERBLOCKS_DEPLOYMENT_TYPE=cloud-prem` (or `GATEWAY_LOCAL_AGENT`)
195
- so the stricter telemetry policy is selected. Laptop agents still drop client,
196
- organization, and application attributes until the Tier 2 traces contract lists
197
- them; tool name, duration, and outcome still export.
@@ -5,7 +5,7 @@ import type { CapturePreviewScreenshot } from "../preview/capture-screenshot.js"
5
5
  import type { CallerRef, ProgressStore, RecentAppStore, TurnStore } from "../resume/types.js";
6
6
  import type { SessionPeer } from "../sabs/session-peer.js";
7
7
  import type { SuperblocksServerClient } from "../server/client.js";
8
- import type { CapabilityResult, CheckAppProgressInput, CheckAppProgressResult, EditAppInput, EditAppResult, GetAppInput, GetAppResult, ImportAppInput, ImportAppResult, PreviewAppInput, PreviewAppResult, Principal, ProgressEvent, PublishAppInput, PublishAppResult, ResolvedPrincipal, StartAppInput, StartAppResult } from "./types.js";
8
+ import type { CapabilityResult, CheckAppProgressInput, CheckAppProgressResult, EditAppInput, EditAppResult, GetAppInput, GetAppResult, ImportAppInput, ImportAppResult, PreviewAppInput, PreviewAppResult, Principal, ProgressEvent, PublishAppInput, PublishAppResult, StartAppInput, StartAppResult } from "./types.js";
9
9
  export { IMPORT_ZIP_MAX_BYTES } from "./types.js";
10
10
  export type CapabilityContext = {
11
11
  /**
@@ -23,7 +23,6 @@ export type CapabilityContext = {
23
23
  /** Injected so pacing and stall thresholds are testable without real time. */
24
24
  now?: () => number;
25
25
  onProgress?: (event: ProgressEvent) => void;
26
- onPrincipalResolved?: (principal: ResolvedPrincipal) => void;
27
26
  principal: Principal;
28
27
  /** Where each caller's place in the event stream is kept between calls. */
29
28
  progressCursors?: ProgressStore;
@@ -45,16 +44,6 @@ export type CapabilityContext = {
45
44
  signal?: AbortSignal;
46
45
  turns: TurnStore;
47
46
  };
48
- /**
49
- * Resolves the Superblocks user behind the CLI session this Gateway was
50
- * started with.
51
- *
52
- * Identity is the already-logged-in Superblocks CLI user. A rejection here
53
- * means that personal API key is bad, not that the user needs a second login,
54
- * so it surfaces as an error rather than an interactive-auth elicitation.
55
- */
56
- export declare function ensurePrincipal(ctx: CapabilityContext): Promise<CapabilityResult<ResolvedPrincipal>>;
57
- export declare function resolveApplicationId(ctx: CapabilityContext, principal: ResolvedPrincipal, applicationId: string | undefined): Promise<CapabilityResult<string>>;
58
47
  export declare function startApp(ctx: CapabilityContext, input: StartAppInput): Promise<CapabilityResult<StartAppResult>>;
59
48
  /**
60
49
  * Creates a fullstack app, uploads the caller's archive as an app attachment,
@@ -228,21 +228,19 @@ function defaultAppName(prompt) {
228
228
  * means that personal API key is bad, not that the user needs a second login,
229
229
  * so it surfaces as an error rather than an interactive-auth elicitation.
230
230
  */
231
- export async function ensurePrincipal(ctx) {
231
+ async function ensurePrincipal(ctx) {
232
232
  const { principal } = ctx;
233
233
  // MCP already has the CLI session key. Looking it up again would spend a
234
234
  // second round trip on `/users/me`, the slowest endpoint the Superblocks
235
235
  // API has, when the principal is already resolved (ENG-5806).
236
236
  if (principal.organizationId && principal.userId) {
237
- const resolved = {
238
- ...principal,
239
- organizationId: principal.organizationId,
240
- userId: principal.userId,
241
- };
242
- ctx.onPrincipalResolved?.(resolved);
243
237
  return {
244
238
  kind: "ok",
245
- value: resolved,
239
+ value: {
240
+ ...principal,
241
+ organizationId: principal.organizationId,
242
+ userId: principal.userId,
243
+ },
246
244
  };
247
245
  }
248
246
  let validated;
@@ -279,16 +277,14 @@ export async function ensurePrincipal(ctx) {
279
277
  message: "The Superblocks API rejected the credential issued for this caller.",
280
278
  };
281
279
  }
282
- const resolved = {
283
- ...principal,
284
- email: validated.email,
285
- organizationId: validated.organizationId,
286
- userId: validated.userId,
287
- };
288
- ctx.onPrincipalResolved?.(resolved);
289
280
  return {
290
281
  kind: "ok",
291
- value: resolved,
282
+ value: {
283
+ ...principal,
284
+ email: validated.email,
285
+ organizationId: validated.organizationId,
286
+ userId: validated.userId,
287
+ },
292
288
  };
293
289
  }
294
290
  /**
@@ -383,7 +379,7 @@ async function elicitStepUp(ctx, idempotencyKey, applicationId) {
383
379
  },
384
380
  };
385
381
  }
386
- export async function resolveApplicationId(ctx, principal, applicationId) {
382
+ async function resolveApplicationId(ctx, principal, applicationId) {
387
383
  if (applicationId?.trim()) {
388
384
  return { kind: "ok", value: applicationId.trim() };
389
385
  }
@@ -517,9 +513,7 @@ function turnSummary(turn) {
517
513
  return "Superblocks is still working, but only the editor can show it — open the edit URL";
518
514
  }
519
515
  if (turn.generationStatus === "live_edit_terminated") {
520
- return turn.retryAction === "retry_import"
521
- ? "The Superblocks live-edit session terminated before anything was built — send the archive again with import_app"
522
- : "The Superblocks live-edit session terminated before anything was built — send the prompt again with edit_app";
516
+ return "The Superblocks live-edit session terminated before anything was built — send the prompt again with edit_app";
523
517
  }
524
518
  if (turn.checkpointCommitId) {
525
519
  return `Application ready — checkpoint saved. ${NEXT_STEPS}`;
@@ -675,7 +669,6 @@ async function forwardClarkPrompt(ctx, input) {
675
669
  ...(forward.recentRuns && forward.recentRuns.length > 0
676
670
  ? { recentRuns: forward.recentRuns }
677
671
  : {}),
678
- ...(forward.retryAction ? { retryAction: forward.retryAction } : {}),
679
672
  viewStatus: forward.viewStatus,
680
673
  },
681
674
  };
@@ -765,13 +758,17 @@ function namingCreatedApplication(applicationId, capability, result) {
765
758
  * The recovery to advertise when the machinery around a turn fails: `edit_app`
766
759
  * with the same prompt, which is what `retry_build` means.
767
760
  *
768
- * A regular prompt is retried with `edit_app`. An import prompt carries an
769
- * attachment, so it must be retried with `import_app` to upload the archive
770
- * again and obtain a fresh signed URL.
761
+ * Only for a prompt `edit_app` could actually replay. A prompt carrying
762
+ * attachments is not one: `edit_app` has no way to resupply an attachment, so
763
+ * an import's migration prompt would reach Superblocks describing an archive it
764
+ * cannot see. Advertising a recovery that cannot finish is worse than
765
+ * advertising none, so the import path gets `applicationId` and no hint.
766
+ * TODO(ENG-5917): let `import_app` resume into an application it already
767
+ * created, and give the import path a recovery of its own.
771
768
  */
772
769
  function retryIfReplayable(attachments) {
773
770
  return attachments && attachments.length > 0
774
- ? { pendingAction: "retry_import" }
771
+ ? undefined
775
772
  : { pendingAction: "retry_build" };
776
773
  }
777
774
  /**
@@ -813,9 +810,7 @@ function rememberFailedStart(ctx, input) {
813
810
  organizationId: input.principal.organizationId,
814
811
  // Reaches the caller as the dead start's reply on its next poll, so it
815
812
  // has to stand on its own there rather than lean on this call's error.
816
- reason: input.reason ??
817
- `The Superblocks live-edit session for application ${input.applicationId} never opened, so the prompt never reached Superblocks and nothing was built. Nothing is running for that application; send the same instruction again to retry it. (${cause})`,
818
- ...(input.retryAction ? { retryAction: input.retryAction } : {}),
813
+ reason: `The Superblocks live-edit session for application ${input.applicationId} never opened, so the prompt never reached Superblocks and nothing was built. Nothing is running for that application; send the same instruction again to retry it. (${cause})`,
819
814
  userId: input.principal.userId,
820
815
  });
821
816
  }
@@ -871,12 +866,7 @@ async function promptNewApplication(ctx, args) {
871
866
  });
872
867
  }
873
868
  catch (error) {
874
- rememberFailedStart(ctx, {
875
- applicationId,
876
- error,
877
- principal,
878
- ...(args.attachments?.length ? { retryAction: "retry_import" } : {}),
879
- });
869
+ rememberFailedStart(ctx, { applicationId, error, principal });
880
870
  return liveEditUnavailable(error, args.attachments);
881
871
  }
882
872
  ctx.onProgress?.({
@@ -1161,19 +1151,14 @@ function normalizeImportSource(source) {
1161
1151
  * wizard, without the sessionStorage hop.
1162
1152
  */
1163
1153
  export async function importApp(ctx, input) {
1164
- const resumedApplicationId = input.applicationId?.trim();
1165
1154
  const archiveResult = await resolveImportArchive(input, ctx.config.importSearchDirs);
1166
1155
  if (archiveResult.kind !== "ok") {
1167
- return resumedApplicationId
1168
- ? namingApplication(resumedApplicationId, archiveResult)
1169
- : archiveResult;
1156
+ return archiveResult;
1170
1157
  }
1171
1158
  const archive = archiveResult.value;
1172
1159
  const principalResult = await ensurePrincipal(ctx);
1173
1160
  if (principalResult.kind !== "ok") {
1174
- return resumedApplicationId
1175
- ? namingApplication(resumedApplicationId, principalResult)
1176
- : principalResult;
1161
+ return principalResult;
1177
1162
  }
1178
1163
  const principal = principalResult.value;
1179
1164
  const source = normalizeImportSource(input.source);
@@ -1184,26 +1169,25 @@ export async function importApp(ctx, input) {
1184
1169
  });
1185
1170
  const explicitName = input.name?.trim();
1186
1171
  const name = explicitName || defaultAppName(migrationPrompt);
1187
- let applicationId = resumedApplicationId;
1188
- if (!applicationId) {
1189
- ctx.onProgress?.({
1190
- message: "Creating application for import",
1191
- type: "progress",
1192
- });
1193
- const created = await createApplicationForPrompt(ctx, {
1194
- apiKey: principal.apiKey,
1195
- name,
1196
- organizationId: principal.organizationId,
1197
- templateName: "app-fullstack",
1198
- });
1199
- if (created.kind !== "ok") {
1200
- return created;
1201
- }
1202
- applicationId = created.value.id;
1172
+ ctx.onProgress?.({
1173
+ message: "Creating application for import",
1174
+ type: "progress",
1175
+ });
1176
+ const created = await createApplicationForPrompt(ctx, {
1177
+ apiKey: principal.apiKey,
1178
+ name,
1179
+ organizationId: principal.organizationId,
1180
+ templateName: "app-fullstack",
1181
+ });
1182
+ if (created.kind !== "ok") {
1183
+ return created;
1203
1184
  }
1204
- await rememberApplication(ctx, principal, applicationId);
1205
- const result = await migrateArchiveIntoApplication(ctx, {
1206
- applicationId,
1185
+ const app = created.value;
1186
+ // See startApp: the row outlives any failure below it, so it is already this
1187
+ // caller's most recent app.
1188
+ await rememberApplication(ctx, principal, app.id);
1189
+ return namingCreatedApplication(app.id, "import_app", await migrateArchiveIntoApplication(ctx, {
1190
+ applicationId: app.id,
1207
1191
  archive,
1208
1192
  explicitName,
1209
1193
  input,
@@ -1211,10 +1195,7 @@ export async function importApp(ctx, input) {
1211
1195
  name,
1212
1196
  principal,
1213
1197
  source,
1214
- });
1215
- return resumedApplicationId
1216
- ? namingApplication(applicationId, result)
1217
- : namingCreatedApplication(applicationId, "import_app", result);
1198
+ }));
1218
1199
  }
1219
1200
  /**
1220
1201
  * Uploads the archive onto an application that already exists and hands
@@ -1238,18 +1219,10 @@ async function migrateArchiveIntoApplication(ctx, args) {
1238
1219
  }
1239
1220
  catch (error) {
1240
1221
  const message = error instanceof Error ? error.message : String(error);
1241
- rememberFailedStart(ctx, {
1242
- applicationId,
1243
- error,
1244
- principal,
1245
- reason: `The archive for application ${applicationId} could not be uploaded, so the migration never started and nothing was built. Send the same archive again with import_app to retry it. (${message})`,
1246
- retryAction: "retry_import",
1247
- });
1248
1222
  return {
1249
1223
  kind: "error",
1250
1224
  code: "import_upload_failed",
1251
1225
  message,
1252
- pendingAction: "retry_import",
1253
1226
  };
1254
1227
  }
1255
1228
  return promptNewApplication(ctx, {
@@ -1275,13 +1248,7 @@ async function migrateArchiveIntoApplication(ctx, args) {
1275
1248
  prompt: args.migrationPrompt,
1276
1249
  });
1277
1250
  }
1278
- /**
1279
- * Machine-readable shape a cancelled wait answers with.
1280
- *
1281
- * Callers name the application alongside it. A cancelled wait says nothing
1282
- * about the work, which carries on: a caller asked to resume it has to be able
1283
- * to point at the application rather than start a second one.
1284
- */
1251
+ /** Machine-readable shape a cancelled wait answers with. */
1285
1252
  function cancelled(capability) {
1286
1253
  return {
1287
1254
  kind: "error",
@@ -1370,7 +1337,7 @@ export async function checkAppProgress(ctx, input) {
1370
1337
  // while the credential was being resolved should never reach the point of
1371
1338
  // registering a waiter it will not read.
1372
1339
  if (ctx.signal?.aborted) {
1373
- return namingApplication(applicationId, cancelled("check_app_progress"));
1340
+ return cancelled("check_app_progress");
1374
1341
  }
1375
1342
  const cursorKey = cursorKeyFor(ctx, principal, applicationId);
1376
1343
  const stored = cursorKey
@@ -1384,7 +1351,7 @@ export async function checkAppProgress(ctx, input) {
1384
1351
  waitMs: hostCallBudgetMs(ctx, ctx.config.clarkProgressWaitMs),
1385
1352
  });
1386
1353
  if (ctx.signal?.aborted) {
1387
- return namingApplication(applicationId, cancelled("check_app_progress"));
1354
+ return cancelled("check_app_progress");
1388
1355
  }
1389
1356
  const snapshot = await readSnapshot(ctx, {
1390
1357
  applicationId,
@@ -1502,9 +1469,6 @@ export async function checkAppProgress(ctx, input) {
1502
1469
  reply: update.reply,
1503
1470
  ...(update.decision ? { decision: update.decision } : {}),
1504
1471
  generationStatus: update.generationStatus,
1505
- ...(update.retryAction && emptyTurnStatus(update.generationStatus)
1506
- ? { pendingAction: update.retryAction }
1507
- : {}),
1508
1472
  status,
1509
1473
  viewStatus: update.viewStatus,
1510
1474
  },
@@ -2089,13 +2053,7 @@ function localUnbuiltReason(turn, pending) {
2089
2053
  return undefined;
2090
2054
  }
2091
2055
  const status = emptyTurnStatus(turn.generationStatus);
2092
- return status
2093
- ? {
2094
- kind: "empty_turn",
2095
- retryAction: turn.retryAction ?? "retry_build",
2096
- status,
2097
- }
2098
- : undefined;
2056
+ return status ? { kind: "empty_turn", status } : undefined;
2099
2057
  }
2100
2058
  /**
2101
2059
  * The same question asked of what was persisted, for a caller with no live turn
@@ -2183,8 +2141,6 @@ function persistedTurnEnd(status) {
2183
2141
  // plain question and cannot be approved.
2184
2142
  case "needs_decision":
2185
2143
  return { kind: "awaiting_decision" };
2186
- // The stream never recorded whether the prompt carried an archive, so
2187
- // this path cannot pick retry_build vs retry_import. Omit rather than guess.
2188
2144
  case "live_edit_terminated":
2189
2145
  return { kind: "empty_turn", status };
2190
2146
  // A `cancelled` in the tail may still have a real app behind it: the
@@ -2256,11 +2212,7 @@ async function unbuiltApp(ctx, principal, applicationId) {
2256
2212
  if (reason.kind === "empty_turn") {
2257
2213
  return {
2258
2214
  kind: "ok",
2259
- value: {
2260
- ...app,
2261
- ...(reason.retryAction ? { pendingAction: reason.retryAction } : {}),
2262
- status: reason.status,
2263
- },
2215
+ value: { ...app, pendingAction: "retry_build", status: reason.status },
2264
2216
  };
2265
2217
  }
2266
2218
  return {
@@ -2394,7 +2346,7 @@ async function waitForBuild(ctx, input) {
2394
2346
  let started = false;
2395
2347
  for (;;) {
2396
2348
  if (ctx.signal?.aborted) {
2397
- return namingApplication(input.applicationId, cancelled("get_app"));
2349
+ return cancelled("get_app");
2398
2350
  }
2399
2351
  // Checked before the network call, not only after it: a budget already
2400
2352
  // exhausted when this loop begins (or used up by the previous