@superblocksteam/gateway 2.0.177 → 2.0.178-next.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/README.md +52 -19
- package/dist/agents/hosted-boundary.d.ts +36 -0
- package/dist/agents/hosted-boundary.d.ts.map +1 -0
- package/dist/agents/hosted-boundary.js +66 -0
- package/dist/agents/hosted-boundary.js.map +1 -0
- package/dist/agents/resolve-orchestrator-url.d.ts +8 -0
- package/dist/agents/resolve-orchestrator-url.d.ts.map +1 -1
- package/dist/agents/resolve-orchestrator-url.js +5 -0
- package/dist/agents/resolve-orchestrator-url.js.map +1 -1
- package/dist/capabilities/integration-metadata.d.ts.map +1 -1
- package/dist/capabilities/integration-metadata.js +12 -1
- package/dist/capabilities/integration-metadata.js.map +1 -1
- package/dist/capabilities/lifecycle.d.ts +1 -6
- package/dist/capabilities/lifecycle.d.ts.map +1 -1
- package/dist/capabilities/lifecycle.js +339 -21
- package/dist/capabilities/lifecycle.js.map +1 -1
- package/dist/capabilities/live-state-attaches.d.ts +28 -0
- package/dist/capabilities/live-state-attaches.d.ts.map +1 -1
- package/dist/capabilities/live-state-attaches.js +33 -0
- package/dist/capabilities/live-state-attaches.js.map +1 -1
- package/dist/capabilities/query-integration.d.ts.map +1 -1
- package/dist/capabilities/query-integration.js +12 -1
- package/dist/capabilities/query-integration.js.map +1 -1
- package/dist/capabilities/tool-permission-reply.d.ts.map +1 -1
- package/dist/capabilities/tool-permission-reply.js.map +1 -1
- package/dist/capabilities/types.d.ts +3 -4
- package/dist/capabilities/types.d.ts.map +1 -1
- package/dist/capabilities/types.js.map +1 -1
- package/dist/capture/browser-contract.d.ts +1 -18
- package/dist/capture/browser-contract.d.ts.map +1 -1
- package/dist/capture/browser-contract.js +1 -13
- package/dist/capture/browser-contract.js.map +1 -1
- package/dist/capture/browser-instructions.d.ts +3 -3
- package/dist/capture/browser-instructions.d.ts.map +1 -1
- package/dist/capture/browser-instructions.js +4 -5
- package/dist/capture/browser-instructions.js.map +1 -1
- package/dist/capture/capture-library-probe.d.ts +0 -1
- package/dist/capture/capture-library-probe.d.ts.map +1 -1
- package/dist/capture/capture-library-probe.js +1 -1
- package/dist/capture/capture-library-probe.js.map +1 -1
- package/dist/capture/capture-library.d.ts +2 -1
- package/dist/capture/capture-library.d.ts.map +1 -1
- package/dist/capture/classify-library-paint.d.ts +2 -1
- package/dist/capture/classify-library-paint.d.ts.map +1 -1
- package/dist/capture/library-parent-script.d.ts +3 -2
- package/dist/capture/library-parent-script.d.ts.map +1 -1
- package/dist/capture/mode.d.ts +2 -3
- package/dist/capture/mode.d.ts.map +1 -1
- package/dist/capture/mode.js +2 -3
- package/dist/capture/mode.js.map +1 -1
- package/dist/config.d.ts +21 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/debug.js.map +1 -1
- package/dist/deps.d.ts +3 -3
- package/dist/health/readiness.d.ts +2 -1
- package/dist/health/readiness.d.ts.map +1 -1
- package/dist/http/main.d.ts +3 -0
- package/dist/http/main.d.ts.map +1 -0
- package/dist/http/main.js +11 -0
- package/dist/http/main.js.map +1 -0
- package/dist/http/serve.d.ts +26 -0
- package/dist/http/serve.d.ts.map +1 -0
- package/dist/http/serve.js +298 -0
- package/dist/http/serve.js.map +1 -0
- package/dist/integrations/filters.d.ts.map +1 -1
- package/dist/integrations/filters.js +1 -16
- package/dist/integrations/filters.js.map +1 -1
- package/dist/integrations/map.d.ts +0 -5
- package/dist/integrations/map.d.ts.map +1 -1
- package/dist/integrations/map.js +1 -1
- package/dist/integrations/map.js.map +1 -1
- package/dist/internal-ingress.d.ts +47 -0
- package/dist/internal-ingress.d.ts.map +1 -0
- package/dist/internal-ingress.js +90 -0
- package/dist/internal-ingress.js.map +1 -0
- package/dist/orchestrator/client.d.ts +11 -5
- package/dist/orchestrator/client.d.ts.map +1 -1
- package/dist/orchestrator/client.js +14 -3
- package/dist/orchestrator/client.js.map +1 -1
- package/dist/playwright/ensure-chromium.js.map +1 -1
- package/dist/preview/capture-screenshot.d.ts +2 -2
- package/dist/preview/capture-screenshot.d.ts.map +1 -1
- package/dist/preview/capture-screenshot.js +2 -2
- package/dist/preview/capture-screenshot.js.map +1 -1
- package/dist/process/fault-barrier.d.ts +4 -3
- package/dist/process/fault-barrier.d.ts.map +1 -1
- package/dist/sabs/app-state.d.ts +2 -13
- package/dist/sabs/app-state.d.ts.map +1 -1
- package/dist/sabs/app-state.js +7 -2
- package/dist/sabs/app-state.js.map +1 -1
- package/dist/sabs/awaited-decision.d.ts.map +1 -1
- package/dist/sabs/browser-context.d.ts +4 -1
- package/dist/sabs/browser-context.d.ts.map +1 -1
- package/dist/sabs/browser-context.js +1 -0
- package/dist/sabs/browser-context.js.map +1 -1
- package/dist/sabs/editor-client-methods.d.ts.map +1 -1
- package/dist/sabs/editor-client-methods.js +1 -1
- package/dist/sabs/editor-client-methods.js.map +1 -1
- package/dist/sabs/editor-socket.d.ts.map +1 -1
- package/dist/sabs/editor-socket.js.map +1 -1
- package/dist/sabs/session-peer.d.ts +13 -1
- package/dist/sabs/session-peer.d.ts.map +1 -1
- package/dist/sabs/session-peer.js.map +1 -1
- package/dist/sabs/streamed-reply.d.ts.map +1 -1
- package/dist/sabs/streamed-reply.js.map +1 -1
- package/dist/sabs/turn-collector.d.ts.map +1 -1
- package/dist/sabs/turn-collector.js.map +1 -1
- package/dist/sabs/websocket-session-peer.d.ts +17 -1
- package/dist/sabs/websocket-session-peer.d.ts.map +1 -1
- package/dist/sabs/websocket-session-peer.js +89 -15
- package/dist/sabs/websocket-session-peer.js.map +1 -1
- package/dist/server/client.d.ts +48 -0
- package/dist/server/client.d.ts.map +1 -1
- package/dist/server/client.js +91 -173
- package/dist/server/client.js.map +1 -1
- package/dist/server/identity-reads.d.ts +5 -3
- package/dist/server/identity-reads.d.ts.map +1 -1
- package/dist/server/identity-reads.js +31 -4
- package/dist/server/identity-reads.js.map +1 -1
- package/dist/start.d.ts +5 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +23 -9
- package/dist/start.js.map +1 -1
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/types.d.ts +2 -1
- package/dist/stores/types.d.ts.map +1 -1
- package/dist/telemetry/mcp-client.d.ts +2 -1
- package/dist/telemetry/mcp-client.d.ts.map +1 -1
- package/dist/telemetry/mcp-client.js.map +1 -1
- package/dist/telemetry/mcp-usage.d.ts +22 -0
- package/dist/telemetry/mcp-usage.d.ts.map +1 -0
- package/dist/telemetry/mcp-usage.js +102 -0
- package/dist/telemetry/mcp-usage.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +9 -0
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +21 -0
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/telemetry/runtime.d.ts +2 -1
- package/dist/telemetry/runtime.d.ts.map +1 -1
- package/dist/telemetry/runtime.js +18 -5
- package/dist/telemetry/runtime.js.map +1 -1
- package/dist/transports/mcp/admin-tools.d.ts +3 -7
- package/dist/transports/mcp/admin-tools.d.ts.map +1 -1
- package/dist/transports/mcp/admin-tools.js +9 -82
- package/dist/transports/mcp/admin-tools.js.map +1 -1
- package/dist/transports/mcp/app-status-html.d.ts +2 -2
- package/dist/transports/mcp/app-status-html.d.ts.map +1 -1
- package/dist/transports/mcp/app-status-html.js +1 -27
- package/dist/transports/mcp/app-status-html.js.map +1 -1
- package/dist/transports/mcp/client-presentation.d.ts +10 -13
- package/dist/transports/mcp/client-presentation.d.ts.map +1 -1
- package/dist/transports/mcp/client-presentation.js +2 -14
- package/dist/transports/mcp/client-presentation.js.map +1 -1
- package/dist/transports/mcp/decision-elicitation.js +23 -1
- package/dist/transports/mcp/decision-elicitation.js.map +1 -1
- package/dist/transports/mcp/dispatch-probe.js.map +1 -1
- package/dist/transports/mcp/editor-document-probe.js.map +1 -1
- package/dist/transports/mcp/editor-integration-setup-url.d.ts +0 -1
- package/dist/transports/mcp/editor-integration-setup-url.d.ts.map +1 -1
- package/dist/transports/mcp/editor-integration-setup-url.js +0 -3
- package/dist/transports/mcp/editor-integration-setup-url.js.map +1 -1
- package/dist/transports/mcp/format-tool-content.js +1 -1
- package/dist/transports/mcp/format-tool-content.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/build-app.d.ts +2 -3
- package/dist/transports/mcp/instructions/tools/build-app.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/build-app.js +5 -11
- package/dist/transports/mcp/instructions/tools/build-app.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/check-app-progress.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/check-app-progress.js +1 -25
- package/dist/transports/mcp/instructions/tools/check-app-progress.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/check-publish-progress.d.ts +1 -2
- package/dist/transports/mcp/instructions/tools/check-publish-progress.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/check-publish-progress.js +2 -11
- package/dist/transports/mcp/instructions/tools/check-publish-progress.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/copy.d.ts +1 -8
- package/dist/transports/mcp/instructions/tools/copy.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/copy.js +4 -28
- package/dist/transports/mcp/instructions/tools/copy.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/create-integration.d.ts +1 -2
- package/dist/transports/mcp/instructions/tools/create-integration.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/create-integration.js +4 -8
- package/dist/transports/mcp/instructions/tools/create-integration.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/edit-app.js +2 -2
- package/dist/transports/mcp/instructions/tools/edit-app.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/get-app.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/get-app.js +1 -23
- package/dist/transports/mcp/instructions/tools/get-app.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/index.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/index.js +0 -4
- package/dist/transports/mcp/instructions/tools/index.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/publish-app.d.ts +1 -2
- package/dist/transports/mcp/instructions/tools/publish-app.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/publish-app.js +1 -10
- package/dist/transports/mcp/instructions/tools/publish-app.js.map +1 -1
- package/dist/transports/mcp/instructions/tools/start-app.d.ts +1 -2
- package/dist/transports/mcp/instructions/tools/start-app.d.ts.map +1 -1
- package/dist/transports/mcp/instructions/tools/start-app.js +3 -3
- package/dist/transports/mcp/instructions/tools/start-app.js.map +1 -1
- package/dist/transports/mcp/mount.d.ts +8 -15
- package/dist/transports/mcp/mount.d.ts.map +1 -1
- package/dist/transports/mcp/mount.js +233 -206
- package/dist/transports/mcp/mount.js.map +1 -1
- package/dist/transports/mcp/plan-approval.d.ts.map +1 -1
- package/dist/transports/mcp/plan-approval.js.map +1 -1
- package/dist/transports/mcp/tool-permissions.d.ts +30 -0
- package/dist/transports/mcp/tool-permissions.d.ts.map +1 -0
- package/dist/transports/mcp/tool-permissions.js +39 -0
- package/dist/transports/mcp/tool-permissions.js.map +1 -0
- package/package.json +28 -22
- package/skills/superblocks-import/SKILL.md +5 -3
- package/dist/capture/cowork-editor-url.d.ts +0 -7
- package/dist/capture/cowork-editor-url.d.ts.map +0 -1
- package/dist/capture/cowork-editor-url.js +0 -12
- package/dist/capture/cowork-editor-url.js.map +0 -1
- package/dist/transports/mcp/native-browser-presence.d.ts +0 -46
- package/dist/transports/mcp/native-browser-presence.d.ts.map +0 -1
- package/dist/transports/mcp/native-browser-presence.js +0 -158
- package/dist/transports/mcp/native-browser-presence.js.map +0 -1
- package/skills/superblocks-build/SKILL.md +0 -59
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getUiCapability, registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY, } from "@modelcontextprotocol/ext-apps/server";
|
|
3
3
|
import { McpServer, } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { trace } from "@opentelemetry/api";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import { assertEnterprisePlan, EntitlementError, } from "@superblocksteam/mcp-server";
|
|
6
7
|
import { getIntegrationMetadata } from "../../capabilities/integration-metadata.js";
|
|
@@ -8,16 +9,15 @@ import { askUser, checkAppProgress, checkPublishProgress, editApp, getApp, getPr
|
|
|
8
9
|
import { LiveStateAttaches } from "../../capabilities/live-state-attaches.js";
|
|
9
10
|
import { ToolPermissionReply } from "../../capabilities/tool-permission-reply.js";
|
|
10
11
|
import { IMPORT_ZIP_BASE64_MAX_CHARS } from "../../capabilities/types.js";
|
|
11
|
-
import { HOST_BROWSER_TOOLS_COWORK, HOST_BROWSER_TOOLS_UNKNOWN, } from "../../capture/browser-contract.js";
|
|
12
12
|
import { BROWSER_DRIVER_INSTRUCTIONS } from "../../capture/browser-instructions.js";
|
|
13
13
|
import { captureLibraryScreenshotWithPlaywright } from "../../capture/capture-library.js";
|
|
14
|
-
import { withCoworkEditorHost } from "../../capture/cowork-editor-url.js";
|
|
15
14
|
import { gatewayDebug, gatewayDebugStack } from "../../debug.js";
|
|
16
15
|
import { capturePreviewScreenshotWithPlaywright } from "../../preview/capture-screenshot.js";
|
|
17
16
|
import { createStores } from "../../stores/memory.js";
|
|
17
|
+
import { McpUsage } from "../../telemetry/mcp-usage.js";
|
|
18
18
|
import { registerAdminTools } from "./admin-tools.js";
|
|
19
19
|
import { APP_STATUS_HTML, APP_STATUS_RESOURCE_URI } from "./app-status-html.js";
|
|
20
|
-
import {
|
|
20
|
+
import { collectsUserApproval, } from "./client-presentation.js";
|
|
21
21
|
import { APP_DECISION_HTML, APP_DECISION_RESOURCE_URI, CONFIRM_PLAN_APPROVAL_TOOL, PLAN_APPROVAL_TOKEN_META_KEY, PLAN_MARKUP_META_KEY, } from "./decision-card-html.js";
|
|
22
22
|
import { clientSupportsFormElicitation, elicitDecision, pendingDecision, resolveDecisionByElicitation, resultApplicationId, resultBranch, } from "./decision-elicitation.js";
|
|
23
23
|
import { createEditorDocumentProbe, } from "./editor-document-probe.js";
|
|
@@ -25,10 +25,10 @@ import { EDITOR_SETUP_RECENT_APP_WINDOW_MS, editorIntegrationSetupUrl, } from ".
|
|
|
25
25
|
import { formatMcpToolContent, withDisplayedMentions, } from "./format-tool-content.js";
|
|
26
26
|
import { attachAgentInstructions, instructionFor, } from "./instructions/index.js";
|
|
27
27
|
import { isRecord, readResult } from "./instructions/result.js";
|
|
28
|
-
import { APP_LOAD_CHECK,
|
|
29
|
-
import { NativeBrowserPresence, nativeBrowserPresenceDirectory, } from "./native-browser-presence.js";
|
|
28
|
+
import { APP_LOAD_CHECK, PLAIN_HOST_TOOL_PERMISSION, } from "./instructions/tools/copy.js";
|
|
30
29
|
import { planApprovalDirectory, PlanApprovalChallenges, } from "./plan-approval.js";
|
|
31
30
|
import { createProgressNotifier } from "./progress-notifier.js";
|
|
31
|
+
import { permissionMeta } from "./tool-permissions.js";
|
|
32
32
|
const DESTRUCTIVE_TOOL_ANNOTATIONS = {
|
|
33
33
|
destructiveHint: true,
|
|
34
34
|
idempotentHint: false,
|
|
@@ -68,16 +68,15 @@ class ResultAcknowledgingMcpServer extends McpServer {
|
|
|
68
68
|
connect(transport) {
|
|
69
69
|
const send = transport.send.bind(transport);
|
|
70
70
|
transport.send = async (message, options) => {
|
|
71
|
-
const outgoing = withClientInstructions(message, this.server.getClientVersion());
|
|
72
71
|
try {
|
|
73
|
-
await send(
|
|
74
|
-
if (isRecord(
|
|
75
|
-
this.onResultDelivered?.(
|
|
72
|
+
await send(message, options);
|
|
73
|
+
if (isRecord(message) && "result" in message && "id" in message) {
|
|
74
|
+
this.onResultDelivered?.(message.result, message.id);
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
77
|
catch (error) {
|
|
79
|
-
if (isRecord(
|
|
80
|
-
this.onResultDeliveryFailed?.(
|
|
78
|
+
if (isRecord(message) && "result" in message && "id" in message) {
|
|
79
|
+
this.onResultDeliveryFailed?.(message.result, message.id);
|
|
81
80
|
}
|
|
82
81
|
throw error;
|
|
83
82
|
}
|
|
@@ -183,7 +182,14 @@ async function principalFrom(deps) {
|
|
|
183
182
|
...(deps.cliIdentity ?? {}),
|
|
184
183
|
};
|
|
185
184
|
}
|
|
186
|
-
const
|
|
185
|
+
const SOURCE_ARTIFACT_RULES = "Local paths and prose descriptions are not a substitute for the source artifact: Superblocks cannot read the host's workspace. Ask Superblocks to read the attached source and preserve its UI and behavior. Reuse the artifact on retries. Follow the superblocks-import skill when available; Superblocks writes the application, not the host.";
|
|
186
|
+
const SOURCE_ARTIFACT_INSTRUCTIONS = `When the source is a readable tree on this machine, archive the source and assets with their original relative paths, excluding .git, node_modules, build output, secrets, and production data. Inspect the archive, upload it with upload_artifact filePath, then pass the returned artifact unchanged in start_app or edit_app artifacts. ${SOURCE_ARTIFACT_RULES}`;
|
|
187
|
+
// Hosted, the gateway cannot read the user's disk, so source arrives as text.
|
|
188
|
+
const HOSTED_SOURCE_ARTIFACT_INSTRUCTIONS = `To migrate source, send its text files with their original relative paths through upload_artifact files, excluding .git, node_modules, build output, secrets, and production data, then pass the returned artifact unchanged in start_app or edit_app artifacts. ${SOURCE_ARTIFACT_RULES}`;
|
|
189
|
+
const UPLOAD_ARTIFACT_INPUTS = "Pass filePath with the full host path when you have it. A bare file name falls back to the gateway's search folders. If the file exists only in your own sandbox or scratchpad, where the gateway cannot read it, send text source through files: [{ path, content }] instead of filePath. The gateway reads filePath bytes itself. Only source you are writing out yourself goes in files, which is archived as text: never put an image, PDF, or other binary in there, and never base64 a file into it. Doing that spends minutes emitting the encoding and uploads those characters under the file's name instead of the file. zipBase64 is only for test fixtures.";
|
|
190
|
+
// shortcut: zipBase64 keeps its small cap, so hosted takes only tiny binaries until it gets an upload path for real files.
|
|
191
|
+
const HOSTED_BINARY_UPLOAD = `For an image, PDF, or other binary file, put it in a ZIP and pass that as zipBase64. zipBase64 takes at most ${IMPORT_ZIP_BASE64_MAX_CHARS} base64 characters, so a larger binary cannot be uploaded on this connection yet: tell the user that instead of retrying.`;
|
|
192
|
+
const HOSTED_UPLOAD_ARTIFACT_INPUTS = `This server cannot read files on the user's machine or in this chat's sandbox. Only source you are writing out yourself goes in files, which is archived as text: never put an image, PDF, or other binary in there, and never base64 a file into it. ${HOSTED_BINARY_UPLOAD}`;
|
|
187
193
|
function compactRecord(value) {
|
|
188
194
|
if (!value) {
|
|
189
195
|
return undefined;
|
|
@@ -196,6 +202,10 @@ function compactRecord(value) {
|
|
|
196
202
|
}
|
|
197
203
|
return Object.keys(compacted).length > 0 ? compacted : undefined;
|
|
198
204
|
}
|
|
205
|
+
const UPLOAD_FILE_INSTRUCTIONS = "When the user attached a file in this chat (a log, CSV, screenshot, image, zip, or anything else), or when migrating source visible in this conversation, call upload_artifact. Pass upload_artifact filePath with the full host path when you have it; a bare file name falls back to the gateway's search folders. If the file exists only in your own sandbox or scratchpad, where the gateway cannot read it, send text source through files: [{ path, content }] instead of filePath. The gateway reads filePath bytes itself, so an image or PDF costs nothing to send. Only source you are writing out yourself goes in files, which is archived as text - never base64 a file into it. Then pass the returned artifact in start_app or edit_app artifacts. Do not create an artifact in the host or use the host's own file-upload UI.";
|
|
206
|
+
const HOSTED_UPLOAD_FILE_INSTRUCTIONS = `When the user attached a file in this chat (a log, CSV, screenshot, image, zip, or anything else), or when migrating source visible in this conversation, call upload_artifact. This server cannot read files on the user's machine or in this chat's sandbox. Send text through files: [{ path, content }], which is archived as text. ${HOSTED_BINARY_UPLOAD} Then pass the returned artifact in start_app or edit_app artifacts. Do not create an artifact in the host or use the host's own file-upload UI.`;
|
|
207
|
+
// Hosted has no login or set_server; the relay already signed the user in.
|
|
208
|
+
const SIGN_IN_INSTRUCTIONS = 'This connection may be established before browser sign-in. Call the tool that does the user\'s job. If it reports "MCP browser login required" or "Saved Superblocks login changed outside this task", explicitly call login when available, then retry the requested tool in this current task. Account-dependent tools never start browser sign-in themselves. Do not ask the user to restart the app they are chatting in. Do not call whoami as a preflight. If the user asks which Superblocks account is in use, call whoami. If the user asks to sign in or switch accounts, call login when available. If the user asks to change the Superblocks server, use a native question form when available to confirm https://app.superblocks.com or collect another server URL, then call set_server; continue in the current task after sign-in succeeds. Report the sign-in status from set_server\'s result; a successful whoami does not show whether browser sign-in occurred.';
|
|
199
209
|
/**
|
|
200
210
|
* Told to the model on connect, so it can match what a user actually says
|
|
201
211
|
* against these tools.
|
|
@@ -207,7 +217,8 @@ function compactRecord(value) {
|
|
|
207
217
|
* is input-only, because a client that reads "Clark" here repeats it back and
|
|
208
218
|
* reintroduces the retired name to the user.
|
|
209
219
|
*/
|
|
210
|
-
|
|
220
|
+
function serverInstructions(hosted) {
|
|
221
|
+
return `Superblocks builds, edits, and publishes internal web applications. Prompts sent through these tools are handed to Superblocks, which writes the application code in a live-edit session.
|
|
211
222
|
|
|
212
223
|
Name the builder "Superblocks" in every word you write. Users sometimes call it "Clark", an older name for this same system: understand them, and answer about Superblocks. Never write "Clark" yourself — not as a name, not as an aside, not even when the user just used it, and never as a separate agent or persona. If Superblocks' own words come back naming Clark, relay the substance and say Superblocks.
|
|
213
224
|
|
|
@@ -229,75 +240,66 @@ Building or editing an app follows one loop: start_app hands a new application t
|
|
|
229
240
|
|
|
230
241
|
${APP_LOAD_CHECK}
|
|
231
242
|
|
|
232
|
-
|
|
243
|
+
${hosted ? HOSTED_UPLOAD_FILE_INSTRUCTIONS : UPLOAD_FILE_INSTRUCTIONS}
|
|
233
244
|
|
|
234
|
-
${SOURCE_ARTIFACT_INSTRUCTIONS}
|
|
245
|
+
${hosted ? HOSTED_SOURCE_ARTIFACT_INSTRUCTIONS : SOURCE_ARTIFACT_INSTRUCTIONS}
|
|
235
246
|
|
|
236
247
|
For a migrated app with missing integrations, call list_integrations first. Reuse only the same plugin and the same connection: the same REST base URL, or the same host, port, and database. A similar name is not a match. list_integrations and get_integration_metadata do not expose connection URLs or hosts, so do not use them to guess a match. When there is no exact match, follow the result's agentInstructions to create and wire every declared integration.
|
|
237
248
|
|
|
238
|
-
This connector is the single Superblocks MCP: Builder tools (upload_artifact, start_app, edit_app, check_app_progress, ask_user, get_app, build_app, publish_app, check_publish_progress) and Admin tools (integrations, apps, users, folders, deploy_application, and the rest of the customer Admin surface) share this endpoint. Prefer create_integration / list_integrations here over sending the user to a second MCP. create_integration takes name, plugin_id, and configuration, never the raw integrations API envelope. create_integration is Admin CRUD for org datasources; start_app creates an application and hands it a prompt. deploy_application is Admin deploy of an existing commit; publish_app is the Builder path that commits current work and waits for it to go live.
|
|
249
|
+
This connector is the single Superblocks MCP: Builder tools (upload_artifact, start_app, edit_app, check_app_progress, ask_user, get_app, build_app, publish_app, check_publish_progress) and Admin tools (integrations, apps, users, folders, deploy_application, and the rest of the customer Admin surface) share this endpoint. Prefer create_integration / list_integrations here over sending the user to a second MCP. create_integration takes name, plugin_id, and configuration, never the raw integrations API envelope; for a REST API integration backed by an OpenAPI spec, also pass openapi_spec (the spec file contents as JSON or YAML text) and file_name so the spec is stored and linked in the same call. create_integration is Admin CRUD for org datasources; start_app creates an application and hands it a prompt. deploy_application is Admin deploy of an existing commit; publish_app is the Builder path that commits current work and waits for it to go live. checkout_application is not exposed on this network surface (host filesystem writes); use the CLI/workspace checkout path instead.
|
|
239
250
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Never tell the user to restart the MCP server; keep the MCP server running.
|
|
251
|
+
${hosted ? "" : `${SIGN_IN_INSTRUCTIONS}\n\n`}Never tell the user to restart the MCP server; keep the MCP server running.
|
|
243
252
|
|
|
244
253
|
A cancelled tool call does not undo work Superblocks already did. The application still exists; read applicationId on the error and follow agentInstructions (or pendingAction) instead of starting over.
|
|
245
254
|
|
|
246
|
-
get_app's previewUrl opens the current development app with existing app permissions; it is not a published end-user URL. Live-editor previews, including fullscreen links, require app edit/build permission (apps:update); End-User or READ access alone is insufficient. For a requested live-preview link, return that exact URL; do not open Share, change permissions, invite anyone, or publish. Call build_app only when the user explicitly requests an unpublished build or a shareable versioned preview, never automatically after an ordinary app creation, edit, or get_app. It creates a commit preview without publishing; retain its previewUrl and use get_preview_status for subsequent checks. If a sharing request is ambiguous (including "Can I share this app?"), ask before calling any tools whether they mean live preview, an unpublished commit preview, or publishing. Access permissions and deployment history cannot resolve this intent; an empty deployment history does not mean publishing is required to share a preview. Call publish_app or deploy_application only when the user explicitly asks to publish or deploy. Publishing does not extend the hosting environment's lifetime; URLs in ephemeral environments remain temporary. Otherwise prefer MCP Apps Open editor / Open preview buttons over dumping editUrl / previewUrl / publishUrl into chat, especially preview JWTs.
|
|
255
|
+
get_app's previewUrl opens the current development app with existing app permissions; it is not a published end-user URL. Live-editor previews, including fullscreen links, require app edit/build permission (apps:update); End-User or READ access alone is insufficient. For a requested live-preview link, return that exact URL; do not open Share, change permissions, invite anyone, or publish. Call build_app only when the user explicitly requests an unpublished build or a shareable versioned preview, never automatically after an ordinary app creation, edit, or get_app. It creates a commit preview without publishing; retain its previewUrl and use get_preview_status for subsequent checks. If a sharing request is ambiguous (including "Can I share this app?"), ask before calling any tools whether they mean live preview, an unpublished commit preview, or publishing. Access permissions and deployment history cannot resolve this intent; an empty deployment history does not mean publishing is required to share a preview. Call publish_app or deploy_application only when the user explicitly asks to publish or deploy. Publishing does not extend the hosting environment's lifetime; URLs in ephemeral environments remain temporary. Otherwise prefer MCP Apps Open editor / Open preview buttons over dumping editUrl / previewUrl / publishUrl into chat, especially preview JWTs. Some hosts can only open https links; when a URL is http and you cannot open it, say so and rely on any screenshot instead.
|
|
247
256
|
|
|
248
257
|
${BROWSER_DRIVER_INSTRUCTIONS}`;
|
|
249
|
-
const COWORK_EMBEDDED_BROWSER_TOOLS = `Load your browser pane tools, you will need them. Call ToolSearch with query ${COWORK_BROWSER_TOOLSEARCH}. When a check_app_progress or get_app result includes browserNavigation, ensure its exact URL is open in Cowork's embedded Page browser before any other action. If that app is already open, do not reload it; otherwise, focus the Page URL field, enter that exact URL, and press Enter. Verify the app loaded; normal authentication or canonical URL redirects are allowed. A blank tab is insufficient. Keep the Page open so the user can watch the live canvas update.`;
|
|
250
|
-
function serverInstructionsFor(client) {
|
|
251
|
-
if (!clientPresentationCapabilities(client).hasNativeBrowser) {
|
|
252
|
-
return SERVER_INSTRUCTIONS;
|
|
253
|
-
}
|
|
254
|
-
return `${SERVER_INSTRUCTIONS}
|
|
255
|
-
|
|
256
|
-
${COWORK_EMBEDDED_BROWSER_TOOLS}`;
|
|
257
258
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
259
|
+
// The stdio text, kept for tests. Live servers pick stdio or hosted text from config.
|
|
260
|
+
export const SERVER_INSTRUCTIONS = serverInstructions(false);
|
|
261
|
+
// Every client reads the tool descriptions, and check_app_progress returns
|
|
262
|
+
// browserNavigation as soon as the canvas is ready, while the build still runs.
|
|
263
|
+
// Opening the editor in an ordinary browser then can take over the live-edit
|
|
264
|
+
// turn, so mid-build opening needs agentInstructions, the host or the plugin
|
|
265
|
+
// to allow it.
|
|
266
|
+
const BROWSER_NAVIGATION_IS_DATA = 'A result may include "browserNavigation": the editor URL, added once the editor answers. While status is "building", do not open it unless "agentInstructions" or instructions from your host or the Superblocks plugin say you may open or show it then. Once status is no longer "building", you may open it in your browser or show it to the user.';
|
|
267
|
+
const PUBLISH_APP_DESCRIPTION = 'Publish/deploy the recent or specified Superblocks application. Pass exactly one of commitMessage or commitId. commitMessage snapshots current work as a new commit: you write it yourself, as a short summary of what changed in the app since the last publish, like a git commit message ("Add a login page"). It is the message on that commit and it is shown in the app\'s history. commitId publishes an earlier commit and then omit commitMessage. Returns status "deployed" once the app is live, or status "publishing" when the deploy was accepted and is still rolling out - that is a success, not a failure: the commit is made and the policy gates passed. On "publishing", tell the user the publish went through and is rolling out, then follow it with check_publish_progress until it reports "deployed"; carry the returned commitId back on every check_publish_progress poll. On either successful status, include publishDetailsUrl immediately in your next user-facing response before the first check_publish_progress poll.';
|
|
268
|
+
/**
|
|
269
|
+
* ask_user's description. Only a host that renders the decision card is told
|
|
270
|
+
* about the card; any other host is told how the decision reaches its user.
|
|
271
|
+
*/
|
|
272
|
+
function askUserDescription(decisionCard) {
|
|
273
|
+
return [
|
|
274
|
+
'Render a form to put the plan or question Superblocks stopped on to the user — call this the moment any tool reports status "needs_decision", except pendingAction "decide_in_editor" (nothing to show; point the user at the edit URL instead). Do not call this if check_app_progress agentInstructions says not too because you already have a native ask user form. Do not call this for missing integration setup during an import: follow check_app_progress agentInstructions instead. This is the one call to make before they answer: do not call check_app_progress, get_app, build_app, or any other Builder tool until the user has answered — polling cannot move a turn waiting on a person, and Superblocks decides, not you: you MUST NOT answer, supply, or decide it yourself. Do not guess, do not pick the option that looks obvious, do not read an answer out of something the user said earlier in the conversation, and do not approve a plan because it looks correct to you — an earlier "just build it" is not approval of this plan.',
|
|
275
|
+
decisionCard
|
|
276
|
+
? "Hosts that support MCP Apps render the plan with Build it / Change something next to it, or the question with one button per option, and the user's answer arrives as their own next message; when that form or card is showing, say nothing in chat. Hosts that do not render it get the whole decision in this result, which you must write out in your own reply — say anything else first, then the question, then end that reply with it."
|
|
277
|
+
: "When this host supports form elicitation, this call shows the decision as a form and sends the user's answer to Superblocks itself; the result is then the turn that follows, so follow the build. When the form is dismissed or the host shows none, the whole decision comes back in this result, which you must write out in your own reply. Say anything else first, then the question, then end that reply with it.",
|
|
278
|
+
'When you do write it out, show what Superblocks actually wrote: for "decision.kind" "plan" that is the whole "decision.plan" markdown, laid out as markdown, not a paraphrase — the user is approving this plan and has to be able to read it. For "multi_choice" it is "decision.question" plus every entry of "decision.options", none dropped or merged. Put the options to the user as something they can act on: if your host can render them as a picker, a form, or suggested replies, use that, carrying Superblocks\' own wording; if it cannot, ask in plain prose. Either way the options are an affordance, not typing — never type a numbered menu, a lettered list, or a row of quoted labels into the chat as a stand-in for one, least of all next to a picker already showing them.',
|
|
279
|
+
decisionCard
|
|
280
|
+
? 'Answers go back through edit_app: planAction "approve" and no prompt only after the user clicks Build it in the MCP App or accepts a native elicitation form; a host that rendered neither must point the user to the Superblocks editor to approve. Use planAction "refine" with their words for changes, or the chosen option as prompt for "multi_choice".'
|
|
281
|
+
: 'Answers given in chat go back through edit_app: planAction "refine" with their words for changes, or the chosen option as prompt for "multi_choice". A plan approval or permission grant sent from chat is refused: call ask_user again to show the form, or, when the host shows no form, point the user to the Superblocks editor to approve.',
|
|
282
|
+
"Returns the current status instead when nothing is waiting on the user, which is the signal to follow the build rather than ask again.",
|
|
283
|
+
].join(" ");
|
|
282
284
|
}
|
|
283
285
|
/**
|
|
284
|
-
* The MCP server behind one stdio
|
|
286
|
+
* The MCP server behind one MCP session, over stdio or HTTP.
|
|
285
287
|
*
|
|
286
|
-
* Tools run as the authenticated Superblocks user.
|
|
288
|
+
* Tools run as the authenticated Superblocks user. `builderToolsOnly` skips
|
|
289
|
+
* the Admin tools, for the hosted Platform MCP whose caller serves them itself.
|
|
287
290
|
*/
|
|
288
|
-
export function createMcpServer(deps, probeEditorDocument = createEditorDocumentProbe()) {
|
|
291
|
+
export function createMcpServer(deps, { builderToolsOnly = false, probeEditorDocument = createEditorDocumentProbe(), } = {}) {
|
|
289
292
|
// `cliIdentity` is optional and the stdio runtime does not set it, so the
|
|
290
293
|
// only identity this session has is whatever a capability already resolved.
|
|
291
294
|
let resolvedIdentity;
|
|
292
295
|
const liveStateAttaches = new LiveStateAttaches();
|
|
296
|
+
const usage = new McpUsage((input) => deps.server.reportMcpUsage(input));
|
|
293
297
|
const context = async (progress, notify, signal) => ({
|
|
298
|
+
mcpClientName: server.server.getClientVersion()?.name.slice(0, 80),
|
|
294
299
|
appState: deps.appState,
|
|
295
300
|
captureLibraryScreenshot: captureLibraryScreenshotWithPlaywright,
|
|
296
301
|
capturePreviewScreenshot: capturePreviewScreenshotWithPlaywright,
|
|
297
302
|
config: deps.config,
|
|
298
|
-
hostBrowserTools: clientPresentationCapabilities(server.server.getClientVersion()).hasNativeBrowser
|
|
299
|
-
? HOST_BROWSER_TOOLS_COWORK
|
|
300
|
-
: HOST_BROWSER_TOOLS_UNKNOWN,
|
|
301
303
|
liveStateAttaches,
|
|
302
304
|
onProgress: (event) => {
|
|
303
305
|
progress.push(event);
|
|
@@ -358,17 +360,24 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
358
360
|
// and a waiter running for an answer nobody will read.
|
|
359
361
|
const signal = extra?.signal;
|
|
360
362
|
const capabilityContext = await context(progress, notify, signal);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
363
|
+
return await usage.run({
|
|
364
|
+
apiKey: capabilityContext.principal.apiKey,
|
|
365
|
+
toolName,
|
|
366
|
+
clientName: capabilityContext.mcpClientName,
|
|
367
|
+
arguments: callArguments,
|
|
368
|
+
}, async () => {
|
|
369
|
+
signal?.throwIfAborted();
|
|
370
|
+
if (!deps.config.localAgentMode) {
|
|
371
|
+
// Same ENTERPRISE/POC gate as Admin tools; skipped for local-agent.
|
|
372
|
+
await assertEnterprisePlan(deps.config.serverUrl.replace(/\/$/, ""), capabilityContext.principal.apiKey);
|
|
371
373
|
}
|
|
374
|
+
return await capability(capabilityContext, () => {
|
|
375
|
+
for (let index = progress.length - 1; index >= 0; index -= 1) {
|
|
376
|
+
if (progress[index]?.type === "completed") {
|
|
377
|
+
progress.splice(index, 1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
});
|
|
372
381
|
});
|
|
373
382
|
}
|
|
374
383
|
catch (error) {
|
|
@@ -449,34 +458,26 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
449
458
|
: {}),
|
|
450
459
|
};
|
|
451
460
|
};
|
|
452
|
-
const clientProfileFor = () => {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
supportsFormElicitation: clientSupportsFormElicitation(server.server.getClientCapabilities()),
|
|
458
|
-
};
|
|
459
|
-
};
|
|
461
|
+
const clientProfileFor = () => ({
|
|
462
|
+
hostCollectsApprovalsAndShowsApp: hostCollectsApprovalsAndShowsApp(),
|
|
463
|
+
supportsDecisionCard: clientSupportsDecisionCard(),
|
|
464
|
+
supportsFormElicitation: clientSupportsFormElicitation(server.server.getClientCapabilities()),
|
|
465
|
+
});
|
|
460
466
|
const server = new ResultAcknowledgingMcpServer({
|
|
461
467
|
name: "superblocks",
|
|
462
468
|
// The product the user is thinking of, not the implementation process
|
|
463
469
|
// serving it. The client shows this string.
|
|
464
470
|
title: "Superblocks",
|
|
465
471
|
version: "0.0.1",
|
|
466
|
-
}, { instructions:
|
|
472
|
+
}, { instructions: serverInstructions(deps.config.hosted) });
|
|
467
473
|
let planApprovals = new PlanApprovalChallenges({
|
|
468
474
|
directory: planApprovalDirectory(deps.sessionKey ?? deps.cliApiKey, deps.config.serverUrl),
|
|
469
475
|
});
|
|
470
|
-
let nativeBrowsers = new NativeBrowserPresence({
|
|
471
|
-
directory: nativeBrowserPresenceDirectory(deps.sessionKey ?? deps.cliApiKey, deps.config.serverUrl),
|
|
472
|
-
});
|
|
473
476
|
const nativeDecisionApprovals = new Map();
|
|
474
477
|
const pendingNativeDecisionApprovals = new Map();
|
|
475
|
-
let announcedNativeBrowser = false;
|
|
476
|
-
let stopWatchingNativeBrowsers;
|
|
477
478
|
let askUserTool;
|
|
478
479
|
let confirmPlanApprovalTool;
|
|
479
|
-
let
|
|
480
|
+
let decisionCardResource;
|
|
480
481
|
const resetForLogin = (sessionKey) => {
|
|
481
482
|
deps.sessionPeer.resetForLogin();
|
|
482
483
|
deps.appState.reset();
|
|
@@ -487,32 +488,36 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
487
488
|
resolvedIdentity = undefined;
|
|
488
489
|
nativeDecisionApprovals.clear();
|
|
489
490
|
pendingNativeDecisionApprovals.clear();
|
|
490
|
-
stopWatchingNativeBrowsers?.();
|
|
491
|
-
if (announcedNativeBrowser)
|
|
492
|
-
nativeBrowsers.withdraw();
|
|
493
491
|
deps.sessionKey = sessionKey;
|
|
494
492
|
planApprovals = new PlanApprovalChallenges({
|
|
495
493
|
directory: planApprovalDirectory(sessionKey, deps.config.serverUrl),
|
|
496
494
|
});
|
|
497
|
-
nativeBrowsers = new NativeBrowserPresence({
|
|
498
|
-
directory: nativeBrowserPresenceDirectory(sessionKey, deps.config.serverUrl),
|
|
499
|
-
});
|
|
500
|
-
if (announcedNativeBrowser)
|
|
501
|
-
nativeBrowsers.announce();
|
|
502
|
-
else if (clientSupportsAppCard() && nativeBrowsers.active()) {
|
|
503
|
-
stopWatchingNativeBrowsers =
|
|
504
|
-
nativeBrowsers.whenInactive(enableStatusCards);
|
|
505
|
-
}
|
|
506
495
|
};
|
|
507
496
|
const statusCardTools = [];
|
|
508
|
-
const enableStatusCards = () => {
|
|
509
|
-
for (const tool of statusCardTools) {
|
|
510
|
-
tool.update({ _meta: APP_STATUS_CARD_META });
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
497
|
const clientSupportsAppCard = () => getUiCapability(server.server.getClientCapabilities())?.mimeTypes?.includes(RESOURCE_MIME_TYPE) === true;
|
|
514
|
-
|
|
515
|
-
|
|
498
|
+
/**
|
|
499
|
+
* Whether the host puts decisions to the user with its own question form and
|
|
500
|
+
* shows the app in its own browser pane. Then an approval the agent sends for
|
|
501
|
+
* a decision this session delivered counts as the user's, and the host gets
|
|
502
|
+
* no status cards and no get_app screenshot. This is the only place that
|
|
503
|
+
* reads the signal.
|
|
504
|
+
*
|
|
505
|
+
* Only the plugin that starts the gateway sets it, never the client. Today
|
|
506
|
+
* that is the local CLI's GATEWAY_HOST_COLLECTS_APPROVALS. A host that
|
|
507
|
+
* declares form elicitation is asked through that instead, so a client with
|
|
508
|
+
* forms keeps them even if the signal reaches it. The hosted gateway ignores
|
|
509
|
+
* it until the hosted way to say it is settled; that carrier goes here too.
|
|
510
|
+
*/
|
|
511
|
+
const hostCollectsApprovalsAndShowsApp = () => deps.config.hostCollectsApprovals &&
|
|
512
|
+
!deps.config.hosted &&
|
|
513
|
+
!clientSupportsFormElicitation(server.server.getClientCapabilities());
|
|
514
|
+
// A host with form elicitation gets decisions as forms it draws itself, so
|
|
515
|
+
// the card is only for hosts that render MCP Apps and nothing else.
|
|
516
|
+
// A host with form elicitation gets decisions as forms it draws itself, so
|
|
517
|
+
// the card is only for hosts that render MCP Apps and nothing else.
|
|
518
|
+
const clientSupportsDecisionCard = () => !hostCollectsApprovalsAndShowsApp() &&
|
|
519
|
+
clientSupportsAppCard() &&
|
|
520
|
+
!clientSupportsFormElicitation(server.server.getClientCapabilities());
|
|
516
521
|
server.onResultDelivered = (result, requestId) => {
|
|
517
522
|
const delivered = deliveredNativeDecision(result);
|
|
518
523
|
if (delivered) {
|
|
@@ -536,8 +541,6 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
536
541
|
};
|
|
537
542
|
const withBrowserNavigation = async (result, ready, signal) => {
|
|
538
543
|
if (signal?.aborted ||
|
|
539
|
-
!clientPresentationCapabilities(server.server.getClientVersion())
|
|
540
|
-
.hasNativeBrowser ||
|
|
541
544
|
result.kind !== "ok" ||
|
|
542
545
|
!result.value ||
|
|
543
546
|
typeof result.value !== "object") {
|
|
@@ -553,74 +556,54 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
553
556
|
typeof editUrl !== "string") {
|
|
554
557
|
return result;
|
|
555
558
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
},
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
if (ready === "status" && value.status === "ready") {
|
|
567
|
-
return {
|
|
568
|
-
kind: "ok",
|
|
569
|
-
value: {
|
|
570
|
-
...value,
|
|
571
|
-
agentInstructions: "The editor is temporarily unavailable. Call `get_app` again in a few seconds. Do not tell the user the app is open until the result includes `browserNavigation`.",
|
|
572
|
-
},
|
|
573
|
-
};
|
|
574
|
-
}
|
|
559
|
+
const probeStarted = Date.now();
|
|
560
|
+
const editorAvailable = await probeEditorDocument(editUrl, signal);
|
|
561
|
+
trace.getActiveSpan()?.setAttributes({
|
|
562
|
+
"editor_probe.available": editorAvailable,
|
|
563
|
+
"editor_probe.duration_ms": Date.now() - probeStarted,
|
|
564
|
+
});
|
|
565
|
+
if (!editorAvailable) {
|
|
575
566
|
return result;
|
|
576
567
|
}
|
|
577
568
|
return {
|
|
578
569
|
kind: "ok",
|
|
579
570
|
value: {
|
|
580
571
|
...value,
|
|
581
|
-
browserNavigation: {
|
|
582
|
-
action: "open_editor",
|
|
583
|
-
url: withCoworkEditorHost(editUrl),
|
|
584
|
-
},
|
|
572
|
+
browserNavigation: { action: "open_editor", url: editUrl },
|
|
585
573
|
},
|
|
586
574
|
};
|
|
587
575
|
};
|
|
588
576
|
server.server.oninitialized = () => {
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (
|
|
577
|
+
const hostShowsApp = hostCollectsApprovalsAndShowsApp();
|
|
578
|
+
// A host that collects approvals itself asks with its own picker and
|
|
579
|
+
// approves through edit_app. The form-only ask_user text says such an
|
|
580
|
+
// approval is refused, so it keeps the text written for card hosts, and the
|
|
581
|
+
// decision resource stays listed.
|
|
582
|
+
if (hostShowsApp) {
|
|
583
|
+
askUserTool?.update({ _meta: permissionMeta("ask_user") });
|
|
595
584
|
confirmPlanApprovalTool?.disable();
|
|
596
585
|
}
|
|
597
|
-
if (
|
|
598
|
-
|
|
599
|
-
|
|
586
|
+
else if (!clientSupportsDecisionCard()) {
|
|
587
|
+
askUserTool?.update({
|
|
588
|
+
_meta: permissionMeta("ask_user"),
|
|
589
|
+
description: askUserDescription(false),
|
|
600
590
|
});
|
|
601
|
-
|
|
602
|
-
|
|
591
|
+
confirmPlanApprovalTool?.disable();
|
|
592
|
+
decisionCardResource?.disable();
|
|
603
593
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
else {
|
|
610
|
-
enableStatusCards();
|
|
594
|
+
// A host that shows the app in its own browser pane needs no status card.
|
|
595
|
+
if (!hostShowsApp && clientSupportsAppCard()) {
|
|
596
|
+
for (const tool of statusCardTools) {
|
|
597
|
+
// update() replaces _meta, so the permission is carried over.
|
|
598
|
+
tool.update({ _meta: { ...tool._meta, ...APP_STATUS_CARD_META } });
|
|
611
599
|
}
|
|
612
600
|
}
|
|
613
601
|
gatewayDebug("mcp server initialized for client", {
|
|
614
602
|
"client.version": server.server.getClientVersion(),
|
|
615
603
|
"client.capabilities": server.server.getClientCapabilities(),
|
|
604
|
+
"client.host_collects_approvals": hostCollectsApprovalsAndShowsApp(),
|
|
616
605
|
});
|
|
617
606
|
};
|
|
618
|
-
server.server.onclose = () => {
|
|
619
|
-
stopWatchingNativeBrowsers?.();
|
|
620
|
-
if (announcedNativeBrowser) {
|
|
621
|
-
nativeBrowsers.withdraw();
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
607
|
/** Sends a decision answer to Superblocks, however the user gave it. */
|
|
625
608
|
const applyDecisionAnswer = (context, applicationId, branch, answer, approvedPlan) => editApp(context, {
|
|
626
609
|
applicationId,
|
|
@@ -639,7 +622,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
639
622
|
const stageNativeDecisionApproval = (result, requestId) => {
|
|
640
623
|
const decision = pendingDecision(result);
|
|
641
624
|
const applicationId = resultApplicationId(result);
|
|
642
|
-
if (!
|
|
625
|
+
if (!hostCollectsApprovalsAndShowsApp() || !decision || !applicationId) {
|
|
643
626
|
return;
|
|
644
627
|
}
|
|
645
628
|
nativeDecisionApprovals.delete(applicationId);
|
|
@@ -656,6 +639,33 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
656
639
|
pendingNativeDecisionApprovals.set(applicationId, { target, requestId });
|
|
657
640
|
}
|
|
658
641
|
};
|
|
642
|
+
/**
|
|
643
|
+
* Puts a decision to the user as an elicitation form and sends their answer
|
|
644
|
+
* to Superblocks, returning the turn that follows. Undefined leaves the
|
|
645
|
+
* decision in chat: the form was dismissed, or the plan on screen is no
|
|
646
|
+
* longer the one Superblocks is waiting on.
|
|
647
|
+
*/
|
|
648
|
+
const answerByForm = async (context, result, decision, input, extra, forgetFinishedTurn) => {
|
|
649
|
+
// The id the result resolved, not the one the call named: those differ
|
|
650
|
+
// when the call named none, and the answer has to reach the same
|
|
651
|
+
// application the decision came from.
|
|
652
|
+
const decidedApp = resultApplicationId(result) ?? input.applicationId;
|
|
653
|
+
const decidedBranch = resultBranch(result) ?? input.branch;
|
|
654
|
+
const displayedPlan = decision.kind === "plan"
|
|
655
|
+
? rememberDisplayedPlan(context.appState, decidedApp, decidedBranch, decision)
|
|
656
|
+
: undefined;
|
|
657
|
+
if (decision.kind === "plan" && !displayedPlan) {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
return resolveDecisionByElicitation({
|
|
661
|
+
apply: (answer) => {
|
|
662
|
+
forgetFinishedTurn();
|
|
663
|
+
return applyDecisionAnswer(context, decidedApp, decidedBranch, answer, displayedPlan);
|
|
664
|
+
},
|
|
665
|
+
decision,
|
|
666
|
+
elicit: elicitDecision(extra),
|
|
667
|
+
});
|
|
668
|
+
};
|
|
659
669
|
/**
|
|
660
670
|
* A progress poll that also puts a decision to the user as a native form when
|
|
661
671
|
* the host supports one, answering Superblocks itself and returning the turn
|
|
@@ -681,7 +691,6 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
681
691
|
capabilities: server.server.getClientCapabilities(),
|
|
682
692
|
});
|
|
683
693
|
const profile = clientProfileFor();
|
|
684
|
-
const { hasNativeAskForm } = profile;
|
|
685
694
|
// Which surface the agent should ask on is a property of the host, not of
|
|
686
695
|
// whether this particular poll managed to show a form.
|
|
687
696
|
const agentInstructions = decision.kind === "plan" ||
|
|
@@ -690,7 +699,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
690
699
|
? instructionFor("check_app_progress", result, profile)
|
|
691
700
|
: undefined;
|
|
692
701
|
const instructionsChosen = agentInstructions
|
|
693
|
-
?
|
|
702
|
+
? profile.hostCollectsApprovalsAndShowsApp
|
|
694
703
|
? "native_ask_form"
|
|
695
704
|
: "call_ask_user"
|
|
696
705
|
: "none";
|
|
@@ -704,11 +713,6 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
704
713
|
}
|
|
705
714
|
return result;
|
|
706
715
|
};
|
|
707
|
-
const decidedApp = resultApplicationId(result) ?? input.applicationId;
|
|
708
|
-
const decidedBranch = resultBranch(result) ?? input.branch;
|
|
709
|
-
const displayedPlan = decision.kind === "plan"
|
|
710
|
-
? rememberDisplayedPlan(context.appState, decidedApp, decidedBranch, decision)
|
|
711
|
-
: undefined;
|
|
712
716
|
if (!clientSupportsFormElicitation(server.server.getClientCapabilities())) {
|
|
713
717
|
stageNativeDecisionApproval(result, extra.requestId);
|
|
714
718
|
gatewayDebug("progress decision flow finished", {
|
|
@@ -719,20 +723,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
719
723
|
});
|
|
720
724
|
return unresolvedDecision();
|
|
721
725
|
}
|
|
722
|
-
|
|
723
|
-
return unresolvedDecision();
|
|
724
|
-
}
|
|
725
|
-
const resolved = await resolveDecisionByElicitation({
|
|
726
|
-
apply: (answer) => {
|
|
727
|
-
forgetFinishedTurn();
|
|
728
|
-
// The id the poll resolved, not the one the call named: those differ
|
|
729
|
-
// when the call named none, and the answer has to reach the same
|
|
730
|
-
// application the decision came from.
|
|
731
|
-
return applyDecisionAnswer(context, decidedApp, decidedBranch, answer, displayedPlan);
|
|
732
|
-
},
|
|
733
|
-
decision,
|
|
734
|
-
elicit: elicitDecision(extra),
|
|
735
|
-
});
|
|
726
|
+
const resolved = await answerByForm(context, result, decision, input, extra, forgetFinishedTurn);
|
|
736
727
|
gatewayDebug("progress decision flow finished", {
|
|
737
728
|
"decision.kind": decision.kind,
|
|
738
729
|
"decision.instructions": resolved ? "none" : instructionsChosen,
|
|
@@ -743,9 +734,10 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
743
734
|
};
|
|
744
735
|
server.registerTool("whoami", {
|
|
745
736
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
746
|
-
description:
|
|
737
|
+
description: `Show the Superblocks user and organization for this connection. Use when the user asks which account is in use, not before every task.${deps.config.hosted ? "" : " If sign-in is needed, call Login explicitly."}`,
|
|
747
738
|
inputSchema: {},
|
|
748
739
|
title: "Current Superblocks Account",
|
|
740
|
+
_meta: permissionMeta("whoami"),
|
|
749
741
|
}, async (_input, extra) => {
|
|
750
742
|
try {
|
|
751
743
|
const principal = await principalFrom(deps);
|
|
@@ -756,7 +748,9 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
756
748
|
content: [
|
|
757
749
|
{
|
|
758
750
|
type: "text",
|
|
759
|
-
text:
|
|
751
|
+
text: deps.config.hosted
|
|
752
|
+
? "Superblocks login is not valid. Reconnect Superblocks in this client to sign in again."
|
|
753
|
+
: "Superblocks login is not valid. Use the Login tool if available.",
|
|
760
754
|
},
|
|
761
755
|
],
|
|
762
756
|
isError: true,
|
|
@@ -772,13 +766,15 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
772
766
|
};
|
|
773
767
|
}
|
|
774
768
|
catch (error) {
|
|
775
|
-
const message =
|
|
776
|
-
|
|
777
|
-
? "Saved Superblocks login changed outside this task. Call Login to confirm the account in this current task."
|
|
769
|
+
const message = deps.config.hosted
|
|
770
|
+
? "Could not check the Superblocks account. Try again, or reconnect Superblocks in this client to sign in again."
|
|
778
771
|
: error instanceof Error &&
|
|
779
|
-
error.message.startsWith("
|
|
780
|
-
? "
|
|
781
|
-
:
|
|
772
|
+
error.message.startsWith("Saved Superblocks login changed outside this task.")
|
|
773
|
+
? "Saved Superblocks login changed outside this task. Call Login to confirm the account in this current task."
|
|
774
|
+
: error instanceof Error &&
|
|
775
|
+
error.message.startsWith("MCP browser login required.")
|
|
776
|
+
? "MCP browser login required. Call Login to continue in this current task."
|
|
777
|
+
: "Could not check the Superblocks account. Try again or use Login if available.";
|
|
782
778
|
if (message.startsWith("Could not check"))
|
|
783
779
|
console.error("gateway whoami failed", {
|
|
784
780
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
@@ -797,9 +793,10 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
797
793
|
if (deps.reconnect) {
|
|
798
794
|
server.registerTool("login", {
|
|
799
795
|
annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
|
|
800
|
-
description: "Open browser sign-in to log in or switch the Superblocks account. Call when the user explicitly asks or a tool reports MCP browser login required or Saved Superblocks login changed outside this task. The existing login remains saved if sign-in is cancelled. After success, continue in this
|
|
796
|
+
description: "Open browser sign-in to log in or switch the Superblocks account. Call when the user explicitly asks or a tool reports MCP browser login required or Saved Superblocks login changed outside this task. The existing login remains saved if sign-in is cancelled. After success, continue in this task with the updated account.",
|
|
801
797
|
inputSchema: {},
|
|
802
798
|
title: "Login to Superblocks",
|
|
799
|
+
_meta: permissionMeta("login"),
|
|
803
800
|
}, async (_input, extra) => {
|
|
804
801
|
try {
|
|
805
802
|
const sessionKey = await deps.reconnect(extra.signal);
|
|
@@ -842,9 +839,10 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
842
839
|
if (deps.setServerUrl) {
|
|
843
840
|
server.registerTool("set_server", {
|
|
844
841
|
annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
|
|
845
|
-
description: "Switch to a Superblocks server and save its origin across plugin updates. Call only when the user explicitly asks to configure or change the server. Ask for the server URL first. HTTPS is required except for localhost or loopback HTTP. Browser sign-in may open. Continue in this
|
|
842
|
+
description: "Switch to a Superblocks server and save its origin across plugin updates. Call only when the user explicitly asks to configure or change the server. Ask for the server URL first. HTTPS is required except for localhost or loopback HTTP. Browser sign-in may open. Continue in this task after success.",
|
|
846
843
|
inputSchema: { server_url: z.string().min(1) },
|
|
847
844
|
title: "Set Superblocks Server",
|
|
845
|
+
_meta: permissionMeta("set_server"),
|
|
848
846
|
}, async ({ server_url }, extra) => {
|
|
849
847
|
try {
|
|
850
848
|
const previousSessionKey = deps.sessionKey;
|
|
@@ -961,10 +959,13 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
961
959
|
search: z.string().min(1).optional(),
|
|
962
960
|
},
|
|
963
961
|
title: "Get Integration Metadata",
|
|
962
|
+
_meta: permissionMeta("get_integration_metadata"),
|
|
964
963
|
}, async (input, extra) => run("get_integration_metadata", (context) => getIntegrationMetadata(context, input), extra, input));
|
|
965
964
|
server.registerTool("start_app", {
|
|
966
965
|
annotations: NON_DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
967
|
-
description: 'Create a Superblocks application and hand it a prompt. Pass the user\'s own request as prompt, verbatim - do not invent, embellish, or add requirements, scope, or design detail the user did not ask for. Use Plan mode by omitting mode unless the user explicitly and unambiguously asks to bypass planning. A request to "build", "create", or "make" an app is not such a request. Pass mode "BUILD" only for directions like "use build mode", "skip the plan", or "no planning". Returns as soon as Superblocks accepts, with status "building" and an edit URL.
|
|
966
|
+
description: 'Create a Superblocks application and hand it a prompt. Pass the user\'s own request as prompt, verbatim - do not invent, embellish, or add requirements, scope, or design detail the user did not ask for. Use Plan mode by omitting mode unless the user explicitly and unambiguously asks to bypass planning. A request to "build", "create", or "make" an app is not such a request. Pass mode "BUILD" only for directions like "use build mode", "skip the plan", or "no planning". Returns as soon as Superblocks accepts, with status "building" and an edit URL. ' +
|
|
967
|
+
BROWSER_NAVIGATION_IS_DATA +
|
|
968
|
+
' If the user imported via an artifact, close the artifact before opening the editor. Follow with check_app_progress until status is no longer "building". When Superblocks returns needs_decision with a plan and the user says "Build it", call edit_app with planAction "approve" (omit prompt) - do not call start_app again with prompt "approve". Superblocks names the application unless name is given. An error from this tool does not mean nothing happened: the application is created before the build starts, so the error names it in "applicationId". Unless pendingAction is "start_new_app", retry that app with edit_app rather than calling start_app another time, which would leave the first one abandoned. When pendingAction is "start_new_app", call start_app once with the same prompt, mode, and artifacts and set replacesApplicationId to the failed result\'s applicationId. If that replacement ends live_edit_terminated with no pendingAction, stop and report its reply; do not retry or create another application. When migrating source uploaded with upload_artifact, or when Superblocks should read any other uploaded file, pass its returned artifact unchanged in artifacts.',
|
|
968
969
|
inputSchema: {
|
|
969
970
|
artifacts: z.array(ARTIFACT_SCHEMA).min(1).max(20).optional(),
|
|
970
971
|
branch: z.string().optional(),
|
|
@@ -982,6 +983,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
982
983
|
// the tool name: a host that ranks tools by title has to be able to find
|
|
983
984
|
// this one from the request.
|
|
984
985
|
title: "Create Superblocks App",
|
|
986
|
+
_meta: permissionMeta("start_app"),
|
|
985
987
|
}, async ({ artifacts, branch, idempotencyKey, mode, name, prompt, replacesApplicationId, }, extra) => run("start_app", (context) => startApp(context, {
|
|
986
988
|
artifacts,
|
|
987
989
|
branch,
|
|
@@ -1005,7 +1007,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1005
1007
|
}));
|
|
1006
1008
|
server.registerTool("upload_artifact", {
|
|
1007
1009
|
annotations: NON_DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
1008
|
-
description: `Use upload_artifact to upload source once for a later start_app or edit_app migration, or any other file the user attached in this chat (a log, CSV, screenshot, image, document, or a ZIP/.tgz/.gz). Batch related text source files into one files call, up to 100 files and 1 MB (1,048,576 bytes) of UTF-8 content before compression. Split only when those limits require it, and tell the user which batch you are preparing before composing its contents.
|
|
1010
|
+
description: `Use upload_artifact to upload source once for a later start_app or edit_app migration, or any other file the user attached in this chat (a log, CSV, screenshot, image, document, or a ZIP/.tgz/.gz). Batch related text source files into one files call, up to 100 files and 1 MB (1,048,576 bytes) of UTF-8 content before compression. Split only when those limits require it, and tell the user which batch you are preparing before composing its contents. ${deps.config.hosted ? HOSTED_UPLOAD_ARTIFACT_INPUTS : UPLOAD_ARTIFACT_INPUTS} Files larger than 50 MB are rejected - the same cap as Superblocks attachments. ${deps.config.hosted ? HOSTED_SOURCE_ARTIFACT_INSTRUCTIONS : SOURCE_ARTIFACT_INSTRUCTIONS} This uploads an organization-scoped artifact but does not create, edit, build, or commit an application. Pass its returned artifact unchanged in start_app or edit_app artifacts, and reuse it on retries instead of uploading again. Do not create an artifact in the host or use the host's own file-upload UI.`,
|
|
1009
1011
|
inputSchema: {
|
|
1010
1012
|
files: z
|
|
1011
1013
|
.array(z.object({
|
|
@@ -1015,7 +1017,9 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1015
1017
|
.min(1)
|
|
1016
1018
|
.max(100)
|
|
1017
1019
|
.optional(),
|
|
1018
|
-
|
|
1020
|
+
...(deps.config.hosted
|
|
1021
|
+
? {}
|
|
1022
|
+
: { filePath: z.string().min(1).optional() }),
|
|
1019
1023
|
zipBase64: z
|
|
1020
1024
|
.string()
|
|
1021
1025
|
.min(1)
|
|
@@ -1023,6 +1027,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1023
1027
|
.optional(),
|
|
1024
1028
|
},
|
|
1025
1029
|
title: "Upload File or Source Artifact",
|
|
1030
|
+
_meta: permissionMeta("upload_artifact"),
|
|
1026
1031
|
}, async ({ filePath, files, zipBase64 }, extra) => run("upload_artifact", (context) => uploadArtifact(context, {
|
|
1027
1032
|
filePath,
|
|
1028
1033
|
files,
|
|
@@ -1030,7 +1035,9 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1030
1035
|
}), extra, { filePath, files, zipBase64 }));
|
|
1031
1036
|
server.registerTool("edit_app", {
|
|
1032
1037
|
annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
|
|
1033
|
-
description: 'Forward a prompt to Superblocks for the recent or specified application. Pass the user\'s request directly as prompt, verbatim. DO NOT invent, embellish, or add requirements, scope, or design detail they did not ask for. This will cause Superblocks to do things the user did not ask for. For app-behavior questions, debugging questions, and investigation of bug reports, use Plan mode; when no plan or question is pending, Superblocks can answer without changing code or requiring plan approval. Requests to fix an app also default to Plan mode, so the user can approve proposed changes before implementation. Pass the user\'s words verbatim, and prefer this over reading source files yourself. Defaults to Plan mode on every call - an edit never inherits Build mode from an earlier turn. Pass mode "BUILD" only when the user asks for it explicitly (e.g. "use build mode" or "skip the plan") on this edit. Answering a multi_choice question is not skipping the plan: omit mode and stay in Plan until Superblocks has proposed a plan and the user approves it. The result reports the "mode" the turn ran in. When status was needs_decision with a plan and the user says "Build it" / approve / go ahead, call edit_app with planAction "approve" and omit prompt - do not put "approve" or "Build it" in prompt (that is refine feedback, not the Build-it path). Use planAction "refine" with their feedback as prompt to change the plan. Also use edit_app to answer multi_choice questions. Returns status "building"; follow with check_app_progress.
|
|
1038
|
+
description: 'Forward a prompt to Superblocks for the recent or specified application. Pass the user\'s request directly as prompt, verbatim. DO NOT invent, embellish, or add requirements, scope, or design detail they did not ask for. This will cause Superblocks to do things the user did not ask for. For app-behavior questions, debugging questions, and investigation of bug reports, use Plan mode; when no plan or question is pending, Superblocks can answer without changing code or requiring plan approval. Requests to fix an app also default to Plan mode, so the user can approve proposed changes before implementation. Pass the user\'s words verbatim, and prefer this over reading source files yourself. Defaults to Plan mode on every call - an edit never inherits Build mode from an earlier turn. Pass mode "BUILD" only when the user asks for it explicitly (e.g. "use build mode" or "skip the plan") on this edit. Answering a multi_choice question is not skipping the plan: omit mode and stay in Plan until Superblocks has proposed a plan and the user approves it. The result reports the "mode" the turn ran in. When status was needs_decision with a plan and the user says "Build it" / approve / go ahead, call edit_app with planAction "approve" and omit prompt - do not put "approve" or "Build it" in prompt (that is refine feedback, not the Build-it path). Use planAction "refine" with their feedback as prompt to change the plan. Also use edit_app to answer multi_choice questions. Returns status "building"; follow with check_app_progress. ' +
|
|
1039
|
+
BROWSER_NAVIGATION_IS_DATA +
|
|
1040
|
+
' An error from this call still names the application it was working on in "applicationId". When migrating or resuming source uploaded with upload_artifact, or when Superblocks should read any other uploaded file, pass its returned artifact unchanged in artifacts.',
|
|
1034
1041
|
inputSchema: {
|
|
1035
1042
|
applicationId: z.string().uuid().optional(),
|
|
1036
1043
|
artifacts: z.array(ARTIFACT_SCHEMA).min(1).max(20).optional(),
|
|
@@ -1046,8 +1053,9 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1046
1053
|
prompt: z.string().min(1).optional(),
|
|
1047
1054
|
},
|
|
1048
1055
|
title: "Edit App",
|
|
1056
|
+
_meta: permissionMeta("edit_app"),
|
|
1049
1057
|
}, async ({ applicationId, artifacts, branch, idempotencyKey, mode, planAction, permissionMessageId, prompt, }, extra) => run("edit_app", async (context) => {
|
|
1050
|
-
const
|
|
1058
|
+
const hostAsks = hostCollectsApprovalsAndShowsApp();
|
|
1051
1059
|
const approvalRequest = planAction === "approve"
|
|
1052
1060
|
? { kind: "plan" }
|
|
1053
1061
|
: permissionMessageId &&
|
|
@@ -1055,7 +1063,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1055
1063
|
ToolPermissionReply.parse(prompt).isGrant
|
|
1056
1064
|
? { kind: "tool_permission", messageId: permissionMessageId }
|
|
1057
1065
|
: undefined;
|
|
1058
|
-
const nativeApprovalApplicationId = approvalRequest &&
|
|
1066
|
+
const nativeApprovalApplicationId = approvalRequest && hostAsks
|
|
1059
1067
|
? (applicationId ??
|
|
1060
1068
|
(nativeDecisionApprovals.size === 1
|
|
1061
1069
|
? nativeDecisionApprovals.keys().next().value
|
|
@@ -1066,7 +1074,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1066
1074
|
: undefined;
|
|
1067
1075
|
const consumedNativeApplicationId = applicationId ?? nativeApprovalApplicationId;
|
|
1068
1076
|
let restoreNative;
|
|
1069
|
-
if (
|
|
1077
|
+
if (hostAsks && consumedNativeApplicationId) {
|
|
1070
1078
|
const approved = nativeDecisionApprovals.get(consumedNativeApplicationId);
|
|
1071
1079
|
const staged = pendingNativeDecisionApprovals.get(consumedNativeApplicationId);
|
|
1072
1080
|
nativeDecisionApprovals.delete(consumedNativeApplicationId);
|
|
@@ -1096,7 +1104,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1096
1104
|
PlanApprovalChallenges.isCurrent(context.appState, matchingNativeTarget)
|
|
1097
1105
|
? matchingNativeTarget
|
|
1098
1106
|
: undefined;
|
|
1099
|
-
const confirmedApproval = approvalRequest && !
|
|
1107
|
+
const confirmedApproval = approvalRequest && !hostAsks
|
|
1100
1108
|
? await planApprovals.claimConfirmed(context.appState, applicationId, branch, approvalRequest)
|
|
1101
1109
|
: undefined;
|
|
1102
1110
|
const approval = nativeApproval ??
|
|
@@ -1185,39 +1193,48 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1185
1193
|
// by hand in the elicitation form. It writes nothing the model decided
|
|
1186
1194
|
// on its own.
|
|
1187
1195
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
1188
|
-
description: `Polls for the given application's current Superblocks turn. Use this after start_app or edit_app. The result includes the "activity" field, which carries Superblocks' updates since the previous call (or a short heartbeat when Superblocks is quiet). Possible statuses are: "building", "ready", "checkpoint_failed", "needs_decision", "no_changes", "live_edit_terminated", "timeout", "cancelled", "detached", "unknown". The result may carry "agentInstructions" on any poll; follow it whenever present.
|
|
1196
|
+
description: `Polls for the given application's current Superblocks turn. Use this after start_app or edit_app. The result includes the "activity" field, which carries Superblocks' updates since the previous call (or a short heartbeat when Superblocks is quiet). Possible statuses are: "building", "ready", "checkpoint_failed", "needs_decision", "no_changes", "live_edit_terminated", "timeout", "cancelled", "detached", "unknown". The result may carry "agentInstructions" on any poll; follow it whenever present. ${BROWSER_NAVIGATION_IS_DATA} Always follow "agentInstructions" in the result. When "building", continue polling. When "ready", call get_app to get the editor and preview URLs. When status "checkpoint_failed", follow "agentInstructions"; when they say the editor is temporarily unavailable, call get_app again. Do not claim the app was saved or keep polling. When "no_changes", do not present the edit URL as a finished app. A direct app question, an answer to a multi_choice question, or a change Superblocks finds already done can terminate as "no_changes" when Superblocks replies without app edits: with a reply and no pendingAction, report the reply to the user and stop. A "no_changes" with a "pendingAction" asks you to follow it. A "cancelled" with no pendingAction was replaced by a newer prompt on the same app: do not send it again; call check_app_progress again to follow the newer turn. For "no_changes", "timeout", "cancelled", or "live_edit_terminated", follow "pendingAction"; "retry_build" means call edit_app, while "start_new_app" means call start_app once with the same prompt and set replacesApplicationId to this result's applicationId. A terminal "live_edit_terminated" with no pendingAction means stop and report its reply; do not retry or create another application. When "detached", stop polling and point the user at the edit URL. When "unknown", the gateway is not following a turn for that app; the build may still be running, so follow "agentInstructions". The user sees nothing while this call is outstanding, so you MUST summarize Superblocks' updates between every pair of calls: before calling again, summarize the new "activity" lines in your own user-facing words, oldest first, without quoting or reproducing the source wording. Summarize every line, not just the latest - two different lines are two things to watch happen, not one. Never make two check_app_progress calls in a row with no message of your own in between, and never save the lines for a recap at the end. A heartbeat with no new words just means still working - say how long, then poll again. A checkpointCommitId means work is saved and the edit URL opens the finished app. Superblocks tests what it builds: it opens the app in a browser and runs its APIs, and the test run arrives as ordinary activity lines ("Testing the orders page", "Test passed: ...", "Tests failed: ..."). Summarize those like any other activity and say which cases passed and failed. A failing test is a build signal, not an auth problem: keep polling, and never tell the user to re-authenticate over one. This tool also surfaces integration errors from mid-build queries run through the orchestrator.`,
|
|
1189
1197
|
inputSchema: {
|
|
1190
1198
|
applicationId: z.string().uuid().optional(),
|
|
1191
1199
|
branch: z.string().optional(),
|
|
1192
1200
|
},
|
|
1193
1201
|
title: "Check App Progress",
|
|
1202
|
+
_meta: permissionMeta("check_app_progress"),
|
|
1194
1203
|
}, async ({ applicationId, branch }, extra) => run("check_app_progress", (context, forgetFinishedTurn) => progressWithDecisionForm(context, { applicationId, branch }, extra, forgetFinishedTurn), extra, "viewStatus", { applicationId, branch }));
|
|
1195
1204
|
// Its own card, because an MCP App binds to one tool and check_app_progress
|
|
1196
1205
|
// must stay text-only: a card tool reopens its card on every call, which
|
|
1197
1206
|
// would flash a blank form through a whole multi-minute build.
|
|
1198
1207
|
askUserTool = registerAppTool(server, "ask_user", {
|
|
1199
|
-
description:
|
|
1208
|
+
description: askUserDescription(true),
|
|
1200
1209
|
inputSchema: {
|
|
1201
1210
|
applicationId: z.string().uuid().optional(),
|
|
1202
1211
|
branch: z.string().optional(),
|
|
1203
1212
|
},
|
|
1204
1213
|
title: "Ask User",
|
|
1205
1214
|
// Reads the decision Superblocks is already waiting on and puts it on
|
|
1206
|
-
// screen
|
|
1207
|
-
//
|
|
1208
|
-
//
|
|
1215
|
+
// screen, so a host that prompts here is asking permission to show the
|
|
1216
|
+
// user a question they are the one being asked. On a host with form
|
|
1217
|
+
// elicitation it also sends the answer to Superblocks, but only an
|
|
1218
|
+
// answer the user just gave by hand in that form, never one the model
|
|
1219
|
+
// decided on its own.
|
|
1209
1220
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
1210
1221
|
_meta: {
|
|
1222
|
+
...permissionMeta("ask_user"),
|
|
1211
1223
|
ui: { resourceUri: APP_DECISION_RESOURCE_URI },
|
|
1212
1224
|
},
|
|
1213
1225
|
}, async ({ applicationId, branch }, extra) => {
|
|
1214
1226
|
let asked;
|
|
1215
|
-
const response = await run("ask_user", async (context) => {
|
|
1227
|
+
const response = await run("ask_user", async (context, forgetFinishedTurn) => {
|
|
1216
1228
|
asked = await askUser(context, {
|
|
1217
1229
|
applicationId,
|
|
1218
1230
|
branch,
|
|
1219
1231
|
});
|
|
1220
|
-
|
|
1232
|
+
const decision = pendingDecision(asked);
|
|
1233
|
+
if (!decision ||
|
|
1234
|
+
!clientSupportsFormElicitation(server.server.getClientCapabilities())) {
|
|
1235
|
+
return asked;
|
|
1236
|
+
}
|
|
1237
|
+
return ((await answerByForm(context, asked, decision, { applicationId, branch }, extra, forgetFinishedTurn)) ?? asked);
|
|
1221
1238
|
}, extra, { applicationId, branch });
|
|
1222
1239
|
const decision = asked ? pendingDecision(asked) : undefined;
|
|
1223
1240
|
if (!clientSupportsDecisionCard() ||
|
|
@@ -1258,6 +1275,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1258
1275
|
},
|
|
1259
1276
|
title: "Confirm Plan Approval",
|
|
1260
1277
|
_meta: {
|
|
1278
|
+
...permissionMeta("confirm_plan_approval"),
|
|
1261
1279
|
ui: {
|
|
1262
1280
|
resourceUri: APP_DECISION_RESOURCE_URI,
|
|
1263
1281
|
visibility: ["app"],
|
|
@@ -1277,7 +1295,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1277
1295
|
}, extra);
|
|
1278
1296
|
return confirmed ? response : { ...response, isError: true };
|
|
1279
1297
|
});
|
|
1280
|
-
registerAppResource(server, "Gateway decision", APP_DECISION_RESOURCE_URI, {
|
|
1298
|
+
decisionCardResource = registerAppResource(server, "Gateway decision", APP_DECISION_RESOURCE_URI, {
|
|
1281
1299
|
description: "Form for the plan or question Superblocks is waiting on",
|
|
1282
1300
|
mimeType: RESOURCE_MIME_TYPE,
|
|
1283
1301
|
}, async () => ({
|
|
@@ -1291,7 +1309,9 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1291
1309
|
}));
|
|
1292
1310
|
const getAppTool = server.registerTool("get_app", {
|
|
1293
1311
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
1294
|
-
description: 'Return editor and live fullscreen-preview URLs for the recent or specified Superblocks application without committing or building it. While a plan or question is outstanding it returns status "needs_decision" with the "decision" and a "pendingAction" of "approve_plan", "answer_question", or "decide_in_editor" (nothing to show in chat; point the user at the edit URL), while a turn is running it returns status "building", and when the last turn ended without writing anything it returns "timeout", "live_edit_terminated", "no_changes" or "cancelled" with no preview URLs or screenshot. A "no_changes" with a "reply" and no "pendingAction" is Superblocks\' finished answer: report the reply and stop. A "no_changes" with a "pendingAction" asks you to follow it. When status "checkpoint_failed", follow "agentInstructions"
|
|
1312
|
+
description: 'Return editor and live fullscreen-preview URLs for the recent or specified Superblocks application without committing or building it. While a plan or question is outstanding it returns status "needs_decision" with the "decision" and a "pendingAction" of "approve_plan", "answer_question", or "decide_in_editor" (nothing to show in chat; point the user at the edit URL), while a turn is running it returns status "building", and when the last turn ended without writing anything it returns "timeout", "live_edit_terminated", "no_changes" or "cancelled" with no preview URLs or screenshot. A "no_changes" with a "reply" and no "pendingAction" is Superblocks\' finished answer: report the reply and stop. A "no_changes" with a "pendingAction" asks you to follow it. When status "checkpoint_failed", follow "agentInstructions"; when they say the editor is temporarily unavailable, call get_app again. Do not claim the app was saved. Except for this checkpoint retry, follow "pendingAction" instead of calling this again; "retry_build" means call edit_app, while "start_new_app" means call start_app once with the same prompt and set replacesApplicationId to this result\'s applicationId. A terminal "live_edit_terminated" with no pendingAction means stop and report its reply; do not retry or create another application. A "detached" turn may still be running, so do not retry it; open the existing application in the editor. Prefer this after check_app_progress reaches status "ready". Also the right call when you do not know where a build stands (after a reconnect, when no turn of yours is running, or when the conversation moved to another channel): read "status" from the result rather than guessing. ' +
|
|
1313
|
+
BROWSER_NAVIGATION_IS_DATA +
|
|
1314
|
+
' Hosts that support MCP Apps show Open editor / Open preview buttons. When Superblocks has written code the result includes a screenshot of the live Vite canvas, never the edit URL. When this result includes a screenshot, inspect it yourself before reporting that the app looks correct. If it shows skeletons, spinners, empty tables or charts, or "Loading...", call get_app again a few seconds later rather than treating it as a defect. Do not test, debug, or correct a visible problem unless the user requests it; report what you see.',
|
|
1295
1315
|
inputSchema: {
|
|
1296
1316
|
applicationId: z.uuid().optional(),
|
|
1297
1317
|
branch: z.string().optional(),
|
|
@@ -1301,13 +1321,14 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1301
1321
|
.describe("Whether to capture a screenshot. Defaults to true."),
|
|
1302
1322
|
},
|
|
1303
1323
|
title: "Get App",
|
|
1324
|
+
_meta: permissionMeta("get_app"),
|
|
1304
1325
|
}, async ({ applicationId, branch, includeScreenshot }, extra) => run("get_app", async (context) => {
|
|
1305
1326
|
const result = await getApp(context, {
|
|
1306
1327
|
applicationId,
|
|
1307
1328
|
branch,
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1329
|
+
// A host that collects approvals itself shows the app in its own
|
|
1330
|
+
// browser pane, so skip the capture.
|
|
1331
|
+
includeScreenshot: !hostCollectsApprovalsAndShowsApp() && includeScreenshot,
|
|
1311
1332
|
});
|
|
1312
1333
|
stageNativeDecisionApproval(result, extra.requestId);
|
|
1313
1334
|
return result;
|
|
@@ -1322,6 +1343,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1322
1343
|
},
|
|
1323
1344
|
title: "Get App Status",
|
|
1324
1345
|
_meta: {
|
|
1346
|
+
...permissionMeta("get_app_status"),
|
|
1325
1347
|
ui: {
|
|
1326
1348
|
resourceUri: APP_STATUS_RESOURCE_URI,
|
|
1327
1349
|
visibility: ["app"],
|
|
@@ -1346,6 +1368,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1346
1368
|
.describe("Wait until the build finishes or the tool deadline; false returns one status check for the app card."),
|
|
1347
1369
|
},
|
|
1348
1370
|
title: "Get Preview Status",
|
|
1371
|
+
_meta: permissionMeta("get_preview_status"),
|
|
1349
1372
|
}, async (input, extra) => run("get_preview_status", (context) => getPreviewStatus(context, input), extra, input));
|
|
1350
1373
|
// The card is what puts a screenshot on screen: hosts that render an MCP
|
|
1351
1374
|
// App render it instead of the tool's content blocks, so without it this
|
|
@@ -1358,6 +1381,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1358
1381
|
branch: z.string().optional(),
|
|
1359
1382
|
},
|
|
1360
1383
|
title: "Build App",
|
|
1384
|
+
_meta: permissionMeta("build_app"),
|
|
1361
1385
|
}, async ({ applicationId, branch }, extra) => run("build_app", (context) => previewApp(context, { applicationId, branch, wait: false }), extra, { applicationId, branch }));
|
|
1362
1386
|
statusCardTools.push(buildAppTool);
|
|
1363
1387
|
registerAppResource(server, "Gateway app status", APP_STATUS_RESOURCE_URI, {
|
|
@@ -1372,9 +1396,9 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1372
1396
|
},
|
|
1373
1397
|
],
|
|
1374
1398
|
}));
|
|
1375
|
-
|
|
1399
|
+
server.registerTool("publish_app", {
|
|
1376
1400
|
annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
|
|
1377
|
-
description:
|
|
1401
|
+
description: PUBLISH_APP_DESCRIPTION,
|
|
1378
1402
|
inputSchema: {
|
|
1379
1403
|
applicationId: z.string().uuid().optional(),
|
|
1380
1404
|
branch: z.string().optional(),
|
|
@@ -1393,6 +1417,7 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1393
1417
|
idempotencyKey: z.string().optional(),
|
|
1394
1418
|
},
|
|
1395
1419
|
title: "Publish App",
|
|
1420
|
+
_meta: permissionMeta("publish_app"),
|
|
1396
1421
|
}, async ({ applicationId, branch, commitId, commitMessage, idempotencyKey }, extra) => run("publish_app", (context) => {
|
|
1397
1422
|
const parsed = publishAppInput({
|
|
1398
1423
|
applicationId,
|
|
@@ -1413,11 +1438,14 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1413
1438
|
commitId: z.string().optional(),
|
|
1414
1439
|
},
|
|
1415
1440
|
title: "Check Publish Progress",
|
|
1441
|
+
_meta: permissionMeta("check_publish_progress"),
|
|
1416
1442
|
// The same poll loop as check_app_progress, on the deploy instead of
|
|
1417
1443
|
// the build: it reports where the publish got to and does nothing else.
|
|
1418
1444
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
1419
1445
|
}, async ({ applicationId, commitId }, extra) => run("check_publish_progress", (context) => checkPublishProgress(context, { applicationId, commitId }), extra, { applicationId, commitId }));
|
|
1420
1446
|
}
|
|
1447
|
+
if (builderToolsOnly)
|
|
1448
|
+
return server;
|
|
1421
1449
|
const getActiveAccessToken = deps.getAccessToken
|
|
1422
1450
|
? () => deps.getAccessToken()
|
|
1423
1451
|
: undefined;
|
|
@@ -1448,7 +1476,6 @@ export function createMcpServer(deps, probeEditorDocument = createEditorDocument
|
|
|
1448
1476
|
// Branch only, and only for the application the caller named, so a
|
|
1449
1477
|
// stale entry cannot redirect the app itself.
|
|
1450
1478
|
branch: deps.sessionPeer.heldContext(applicationId)?.branch ?? recent?.branch,
|
|
1451
|
-
cowork: clientProfileFor().hasNativeBrowser,
|
|
1452
1479
|
integrationId,
|
|
1453
1480
|
uiBaseUrl: deps.config.uiBaseUrl,
|
|
1454
1481
|
});
|