@sxl-studio/bridge 1.7.2 → 1.8.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 +342 -16
- package/dist/agent-recipes.d.ts +781 -11
- package/dist/agent-recipes.js +886 -13
- package/dist/agent-recipes.js.map +1 -1
- package/dist/agent-runbook.d.ts +50 -0
- package/dist/agent-runbook.js +243 -0
- package/dist/agent-runbook.js.map +1 -0
- package/dist/asset-upload.d.ts +63 -0
- package/dist/asset-upload.js +225 -0
- package/dist/asset-upload.js.map +1 -0
- package/dist/audit-store.d.ts +15 -0
- package/dist/audit-store.js +100 -0
- package/dist/audit-store.js.map +1 -0
- package/dist/audit.d.ts +4 -3
- package/dist/audit.js +37 -4
- package/dist/audit.js.map +1 -1
- package/dist/auth.d.ts +8 -1
- package/dist/auth.js +41 -1
- package/dist/auth.js.map +1 -1
- package/dist/bridge-agent-workflow-validation-cli.d.ts +2 -0
- package/dist/bridge-agent-workflow-validation-cli.js +68 -0
- package/dist/bridge-agent-workflow-validation-cli.js.map +1 -0
- package/dist/bridge-agent-workflow-validation.d.ts +42 -0
- package/dist/bridge-agent-workflow-validation.js +170 -0
- package/dist/bridge-agent-workflow-validation.js.map +1 -0
- package/dist/bridge-contract-audit.d.ts +45 -0
- package/dist/bridge-contract-audit.js +345 -0
- package/dist/bridge-contract-audit.js.map +1 -0
- package/dist/bridge-health-cli.d.ts +2 -0
- package/dist/bridge-health-cli.js +115 -0
- package/dist/bridge-health-cli.js.map +1 -0
- package/dist/bridge-health.d.ts +33 -0
- package/dist/bridge-health.js +594 -0
- package/dist/bridge-health.js.map +1 -0
- package/dist/bridge-live-validation-cli.d.ts +2 -0
- package/dist/bridge-live-validation-cli.js +114 -0
- package/dist/bridge-live-validation-cli.js.map +1 -0
- package/dist/bridge-live-validation.d.ts +39 -0
- package/dist/bridge-live-validation.js +1141 -0
- package/dist/bridge-live-validation.js.map +1 -0
- package/dist/bridge-performance-profile.d.ts +81 -0
- package/dist/bridge-performance-profile.js +227 -0
- package/dist/bridge-performance-profile.js.map +1 -0
- package/dist/bridge-readiness-cli.d.ts +30 -0
- package/dist/bridge-readiness-cli.js +242 -0
- package/dist/bridge-readiness-cli.js.map +1 -0
- package/dist/bridge-runtime-summary.d.ts +50 -0
- package/dist/bridge-runtime-summary.js +112 -0
- package/dist/bridge-runtime-summary.js.map +1 -0
- package/dist/bridge-workflow-smoke-cli.d.ts +2 -0
- package/dist/bridge-workflow-smoke-cli.js +126 -0
- package/dist/bridge-workflow-smoke-cli.js.map +1 -0
- package/dist/bridge-workflow-smoke.d.ts +39 -0
- package/dist/bridge-workflow-smoke.js +431 -0
- package/dist/bridge-workflow-smoke.js.map +1 -0
- package/dist/codeconnect-suggestions.d.ts +74 -0
- package/dist/codeconnect-suggestions.js +398 -0
- package/dist/codeconnect-suggestions.js.map +1 -0
- package/dist/codeconnect-template.d.ts +98 -0
- package/dist/codeconnect-template.js +280 -0
- package/dist/codeconnect-template.js.map +1 -0
- package/dist/command-queue.d.ts +11 -1
- package/dist/command-queue.js +200 -1
- package/dist/command-queue.js.map +1 -1
- package/dist/command-safety.d.ts +13 -0
- package/dist/command-safety.js +59 -0
- package/dist/command-safety.js.map +1 -0
- package/dist/enabled-library-search.d.ts +49 -0
- package/dist/enabled-library-search.js +151 -0
- package/dist/enabled-library-search.js.map +1 -0
- package/dist/figma-mcp-parity.d.ts +49 -0
- package/dist/figma-mcp-parity.js +368 -0
- package/dist/figma-mcp-parity.js.map +1 -0
- package/dist/figma-mcp-skills-parity.d.ts +61 -0
- package/dist/figma-mcp-skills-parity.js +434 -0
- package/dist/figma-mcp-skills-parity.js.map +1 -0
- package/dist/figma-rest-diagnostics.d.ts +50 -0
- package/dist/figma-rest-diagnostics.js +314 -0
- package/dist/figma-rest-diagnostics.js.map +1 -0
- package/dist/figma-rest.d.ts +27 -0
- package/dist/figma-rest.js +116 -0
- package/dist/figma-rest.js.map +1 -0
- package/dist/http-api.d.ts +16 -2
- package/dist/http-api.js +329 -17
- package/dist/http-api.js.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-factory.d.ts +6 -1
- package/dist/mcp-factory.js +23 -4
- package/dist/mcp-factory.js.map +1 -1
- package/dist/mcp-runtime-probe.d.ts +22 -0
- package/dist/mcp-runtime-probe.js +777 -0
- package/dist/mcp-runtime-probe.js.map +1 -0
- package/dist/mcp-server.d.ts +2 -1
- package/dist/mcp-server.js +2 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/sxl-mcp-instructions.js +99 -26
- package/dist/sxl-mcp-instructions.js.map +1 -1
- package/dist/tools/audit.d.ts +22 -6
- package/dist/tools/audit.js +49 -7
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/capability-matrix.d.ts +22 -0
- package/dist/tools/capability-matrix.js +38 -0
- package/dist/tools/capability-matrix.js.map +1 -0
- package/dist/tools/catalogue-bootstrap.d.ts +1 -0
- package/dist/tools/catalogue-bootstrap.js +665 -30
- package/dist/tools/catalogue-bootstrap.js.map +1 -1
- package/dist/tools/code-connect-context.d.ts +3 -0
- package/dist/tools/code-connect-context.js +319 -0
- package/dist/tools/code-connect-context.js.map +1 -0
- package/dist/tools/code-connect-template.d.ts +3 -0
- package/dist/tools/code-connect-template.js +111 -0
- package/dist/tools/code-connect-template.js.map +1 -0
- package/dist/tools/composition.js +15 -30
- package/dist/tools/composition.js.map +1 -1
- package/dist/tools/compositions-orchestration.d.ts +14 -14
- package/dist/tools/compositions-orchestration.js +2 -2
- package/dist/tools/compositions-orchestration.js.map +1 -1
- package/dist/tools/data.js +839 -27
- package/dist/tools/data.js.map +1 -1
- package/dist/tools/design-context.d.ts +3 -0
- package/dist/tools/design-context.js +197 -0
- package/dist/tools/design-context.js.map +1 -0
- package/dist/tools/destructive-confirmation.d.ts +10 -0
- package/dist/tools/destructive-confirmation.js +25 -0
- package/dist/tools/destructive-confirmation.js.map +1 -0
- package/dist/tools/diagnostics.js +76 -51
- package/dist/tools/diagnostics.js.map +1 -1
- package/dist/tools/figma-mcp-design.d.ts +3 -0
- package/dist/tools/figma-mcp-design.js +377 -0
- package/dist/tools/figma-mcp-design.js.map +1 -0
- package/dist/tools/figma-nodes.js +57 -43
- package/dist/tools/figma-nodes.js.map +1 -1
- package/dist/tools/figma-rc-extended.js +23 -6
- package/dist/tools/figma-rc-extended.js.map +1 -1
- package/dist/tools/figma-rest.d.ts +39 -0
- package/dist/tools/figma-rest.js +279 -0
- package/dist/tools/figma-rest.js.map +1 -0
- package/dist/tools/git.js +11 -7
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/large-data.d.ts +14 -0
- package/dist/tools/large-data.js +189 -0
- package/dist/tools/large-data.js.map +1 -0
- package/dist/tools/meta.d.ts +6 -1
- package/dist/tools/meta.js +89 -11
- package/dist/tools/meta.js.map +1 -1
- package/dist/tools/metadata.d.ts +3 -0
- package/dist/tools/metadata.js +140 -0
- package/dist/tools/metadata.js.map +1 -0
- package/dist/tools/mockup.d.ts +15 -156
- package/dist/tools/mockup.js +54 -121
- package/dist/tools/mockup.js.map +1 -1
- package/dist/tools/orchestration.js +75 -47
- package/dist/tools/orchestration.js.map +1 -1
- package/dist/tools/prompts.d.ts +3 -0
- package/dist/tools/prompts.js +219 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/registry.d.ts +19 -1
- package/dist/tools/registry.js +4 -4
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/resources.d.ts +19 -2
- package/dist/tools/resources.js +149 -5
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/schema-contracts.d.ts +4763 -0
- package/dist/tools/schema-contracts.js +814 -0
- package/dist/tools/schema-contracts.js.map +1 -0
- package/dist/tools/screenshot.d.ts +3 -0
- package/dist/tools/screenshot.js +144 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/shared.d.ts +11 -1
- package/dist/tools/shared.js +55 -2
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/styles-orchestration.d.ts +2 -2
- package/dist/tools/styles-orchestration.js +13 -5
- package/dist/tools/styles-orchestration.js.map +1 -1
- package/dist/tools/styles.js +22 -8
- package/dist/tools/styles.js.map +1 -1
- package/dist/tools/tokens.d.ts +31 -692
- package/dist/tools/tokens.js +175 -135
- package/dist/tools/tokens.js.map +1 -1
- package/dist/tools/variable-defs.d.ts +3 -0
- package/dist/tools/variable-defs.js +338 -0
- package/dist/tools/variable-defs.js.map +1 -0
- package/dist/tools/variables-orchestration.js +13 -5
- package/dist/tools/variables-orchestration.js.map +1 -1
- package/dist/tools/variables.js +18 -15
- package/dist/tools/variables.js.map +1 -1
- package/dist/types.d.ts +53 -0
- package/dist/ultimate-readiness-audit.d.ts +37 -0
- package/dist/ultimate-readiness-audit.js +431 -0
- package/dist/ultimate-readiness-audit.js.map +1 -0
- package/dist/workflow-planner.d.ts +57 -0
- package/dist/workflow-planner.js +464 -0
- package/dist/workflow-planner.js.map +1 -0
- package/dist/workspace-blob-store.d.ts +6 -0
- package/dist/workspace-blob-store.js +9 -0
- package/dist/workspace-blob-store.js.map +1 -1
- package/dist/workspace-path-http.d.ts +24 -0
- package/dist/workspace-path-http.js +146 -0
- package/dist/workspace-path-http.js.map +1 -0
- package/dist/ws-server.js +16 -3
- package/dist/ws-server.js.map +1 -1
- package/package.json +21 -2
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import { getBridgePackageVersion } from "./bridge-version.js";
|
|
2
|
+
import { validateFigmaMcpSkillsParityPayload } from "./figma-mcp-skills-parity.js";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
async function fetchText(endpoint, options) {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
9
|
+
try {
|
|
10
|
+
const headers = {
|
|
11
|
+
Accept: "application/json, text/event-stream",
|
|
12
|
+
};
|
|
13
|
+
if (options.authToken) {
|
|
14
|
+
headers.Authorization = `Bearer ${options.authToken}`;
|
|
15
|
+
}
|
|
16
|
+
if (options.sessionId) {
|
|
17
|
+
headers["Mcp-Session-Id"] = options.sessionId;
|
|
18
|
+
}
|
|
19
|
+
if (options.protocolVersion) {
|
|
20
|
+
headers["mcp-protocol-version"] = options.protocolVersion;
|
|
21
|
+
}
|
|
22
|
+
let body;
|
|
23
|
+
if (options.body !== undefined) {
|
|
24
|
+
headers["Content-Type"] = "application/json";
|
|
25
|
+
body = JSON.stringify(options.body);
|
|
26
|
+
}
|
|
27
|
+
const response = await options.fetchImpl(`${options.baseUrl}${endpoint}`, {
|
|
28
|
+
method: options.method,
|
|
29
|
+
headers,
|
|
30
|
+
body,
|
|
31
|
+
signal: controller.signal,
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
status: response.status,
|
|
35
|
+
body: await response.text(),
|
|
36
|
+
headers: response.headers,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timeout);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function parseJsonRpcResponse(response) {
|
|
44
|
+
const contentType = response.headers.get("Content-Type") ?? response.headers.get("content-type") ?? "";
|
|
45
|
+
const text = response.body.trim();
|
|
46
|
+
if (!text)
|
|
47
|
+
return null;
|
|
48
|
+
if (contentType.includes("text/event-stream")) {
|
|
49
|
+
const blocks = text.split(/\r?\n\r?\n/);
|
|
50
|
+
for (const block of blocks) {
|
|
51
|
+
const data = block
|
|
52
|
+
.split(/\r?\n/)
|
|
53
|
+
.filter((line) => line.startsWith("data:"))
|
|
54
|
+
.map((line) => line.slice("data:".length).trimStart())
|
|
55
|
+
.join("\n")
|
|
56
|
+
.trim();
|
|
57
|
+
if (!data)
|
|
58
|
+
continue;
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(data);
|
|
61
|
+
if (isRecord(parsed))
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(text);
|
|
72
|
+
return isRecord(parsed) ? parsed : null;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function parseToolTextJsonPayload(response) {
|
|
79
|
+
const message = parseJsonRpcResponse(response);
|
|
80
|
+
const result = isRecord(message?.result) ? message.result : null;
|
|
81
|
+
const content = Array.isArray(result?.["content"]) ? result["content"] : [];
|
|
82
|
+
const firstText = content.find((item) => isRecord(item) && item["type"] === "text" && typeof item["text"] === "string");
|
|
83
|
+
if (!isRecord(firstText) || typeof firstText["text"] !== "string")
|
|
84
|
+
return null;
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(firstText["text"]);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function parseResourceTextJsonPayload(response, uri) {
|
|
93
|
+
const message = parseJsonRpcResponse(response);
|
|
94
|
+
const result = isRecord(message?.result) ? message.result : null;
|
|
95
|
+
const contents = Array.isArray(result?.["contents"]) ? result["contents"] : [];
|
|
96
|
+
const resourceContent = contents.find((item) => isRecord(item) && item["uri"] === uri && typeof item["text"] === "string");
|
|
97
|
+
if (!isRecord(resourceContent) || typeof resourceContent["text"] !== "string")
|
|
98
|
+
return null;
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(resourceContent["text"]);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function parsePromptText(response) {
|
|
107
|
+
const message = parseJsonRpcResponse(response);
|
|
108
|
+
const result = isRecord(message?.result) ? message.result : null;
|
|
109
|
+
const messages = Array.isArray(result?.["messages"]) ? result["messages"] : [];
|
|
110
|
+
return messages
|
|
111
|
+
.map((item) => isRecord(item) && isRecord(item["content"]) ? item["content"]["text"] : "")
|
|
112
|
+
.filter((text) => typeof text === "string")
|
|
113
|
+
.join("\n");
|
|
114
|
+
}
|
|
115
|
+
function fail(code, message, httpStatus) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
status: "fail",
|
|
119
|
+
error: { code, message, httpStatus },
|
|
120
|
+
warnings: [],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export async function runMcpRuntimeProbe(options) {
|
|
124
|
+
const endpoint = "/mcp";
|
|
125
|
+
const protocolVersion = "2025-11-25";
|
|
126
|
+
const warnings = [];
|
|
127
|
+
let sessionId = null;
|
|
128
|
+
let negotiatedProtocolVersion = protocolVersion;
|
|
129
|
+
const finalResult = { current: null };
|
|
130
|
+
const done = (result) => {
|
|
131
|
+
finalResult.current = result;
|
|
132
|
+
return result;
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
const init = await fetchText(endpoint, {
|
|
136
|
+
...options,
|
|
137
|
+
method: "POST",
|
|
138
|
+
body: {
|
|
139
|
+
jsonrpc: "2.0",
|
|
140
|
+
id: 1,
|
|
141
|
+
method: "initialize",
|
|
142
|
+
params: {
|
|
143
|
+
protocolVersion,
|
|
144
|
+
capabilities: {},
|
|
145
|
+
clientInfo: {
|
|
146
|
+
name: "sxl-bridge-runtime-probe",
|
|
147
|
+
version: getBridgePackageVersion(),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
if (init.status === 401) {
|
|
153
|
+
return done(fail("MCP_STREAMABLE_HTTP_UNAUTHORIZED", "POST /mcp initialize requires Authorization. Pass --auth-token or set BRIDGE_AUTH_TOKEN.", init.status));
|
|
154
|
+
}
|
|
155
|
+
if (init.status < 200 || init.status >= 300) {
|
|
156
|
+
return done(fail("MCP_STREAMABLE_HTTP_ERROR", `POST /mcp initialize returned HTTP ${init.status}.`, init.status));
|
|
157
|
+
}
|
|
158
|
+
sessionId = init.headers.get("mcp-session-id") ?? init.headers.get("Mcp-Session-Id");
|
|
159
|
+
const initMessage = parseJsonRpcResponse(init);
|
|
160
|
+
const initResult = isRecord(initMessage?.result) ? initMessage.result : null;
|
|
161
|
+
const serverInfo = isRecord(initResult?.["serverInfo"]) ? initResult["serverInfo"] : null;
|
|
162
|
+
const serverName = typeof serverInfo?.["name"] === "string" ? serverInfo["name"] : null;
|
|
163
|
+
const negotiated = typeof initResult?.["protocolVersion"] === "string" ? initResult["protocolVersion"] : null;
|
|
164
|
+
if (negotiated)
|
|
165
|
+
negotiatedProtocolVersion = negotiated;
|
|
166
|
+
if (!sessionId || !initMessage || !initResult || serverName !== "@sxl-studio/bridge") {
|
|
167
|
+
return done(fail("MCP_STREAMABLE_HTTP_INIT_INVALID", "POST /mcp initialize did not return the expected Bridge MCP session envelope.", init.status));
|
|
168
|
+
}
|
|
169
|
+
const initialized = await fetchText(endpoint, {
|
|
170
|
+
...options,
|
|
171
|
+
method: "POST",
|
|
172
|
+
sessionId,
|
|
173
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
174
|
+
body: {
|
|
175
|
+
jsonrpc: "2.0",
|
|
176
|
+
method: "notifications/initialized",
|
|
177
|
+
params: {},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
if (initialized.status < 200 || initialized.status >= 300) {
|
|
181
|
+
return done(fail("MCP_STREAMABLE_HTTP_INITIALIZED_FAILED", `POST /mcp notifications/initialized returned HTTP ${initialized.status}.`, initialized.status));
|
|
182
|
+
}
|
|
183
|
+
const toolsList = await fetchText(endpoint, {
|
|
184
|
+
...options,
|
|
185
|
+
method: "POST",
|
|
186
|
+
sessionId,
|
|
187
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
188
|
+
body: {
|
|
189
|
+
jsonrpc: "2.0",
|
|
190
|
+
id: 2,
|
|
191
|
+
method: "tools/list",
|
|
192
|
+
params: {},
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
if (toolsList.status < 200 || toolsList.status >= 300) {
|
|
196
|
+
return done(fail("MCP_STREAMABLE_HTTP_TOOLS_LIST_FAILED", `POST /mcp tools/list returned HTTP ${toolsList.status}.`, toolsList.status));
|
|
197
|
+
}
|
|
198
|
+
const toolsMessage = parseJsonRpcResponse(toolsList);
|
|
199
|
+
const toolsResult = isRecord(toolsMessage?.result) ? toolsMessage.result : null;
|
|
200
|
+
const tools = Array.isArray(toolsResult?.["tools"]) ? toolsResult["tools"] : [];
|
|
201
|
+
const toolNames = new Set(tools
|
|
202
|
+
.filter(isRecord)
|
|
203
|
+
.map((tool) => typeof tool["name"] === "string" ? tool["name"] : "")
|
|
204
|
+
.filter(Boolean));
|
|
205
|
+
const missingRequiredMcpTools = ["list_tools", "get_operator_runbook", "get_libraries", "search_design_system"]
|
|
206
|
+
.filter((name) => !toolNames.has(name));
|
|
207
|
+
if (missingRequiredMcpTools.length > 0) {
|
|
208
|
+
return done(fail("MCP_STREAMABLE_HTTP_FIGMA_MCP_COMPAT_TOOLS_MISSING", `POST /mcp tools/list did not return required Bridge/Figma MCP-compatible tools: ${missingRequiredMcpTools.join(", ")}.`, toolsList.status));
|
|
209
|
+
}
|
|
210
|
+
const getLibrariesCall = await fetchText(endpoint, {
|
|
211
|
+
...options,
|
|
212
|
+
method: "POST",
|
|
213
|
+
sessionId,
|
|
214
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
215
|
+
body: {
|
|
216
|
+
jsonrpc: "2.0",
|
|
217
|
+
id: 31,
|
|
218
|
+
method: "tools/call",
|
|
219
|
+
params: {
|
|
220
|
+
name: "get_libraries",
|
|
221
|
+
arguments: {
|
|
222
|
+
source: "rest-known",
|
|
223
|
+
bridgeResponseMode: "full",
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
if (getLibrariesCall.status < 200 || getLibrariesCall.status >= 300) {
|
|
229
|
+
return done(fail("MCP_STREAMABLE_HTTP_GET_LIBRARIES_FAILED", `POST /mcp tools/call get_libraries returned HTTP ${getLibrariesCall.status}.`, getLibrariesCall.status));
|
|
230
|
+
}
|
|
231
|
+
const getLibrariesPayload = parseToolTextJsonPayload(getLibrariesCall);
|
|
232
|
+
const getLibrariesOk = isRecord(getLibrariesPayload) &&
|
|
233
|
+
getLibrariesPayload["ok"] === true &&
|
|
234
|
+
getLibrariesPayload["tool"] === "get_libraries" &&
|
|
235
|
+
getLibrariesPayload["compatibility"] === "figma-mcp-design" &&
|
|
236
|
+
Array.isArray(getLibrariesPayload["warnings"]);
|
|
237
|
+
if (!getLibrariesOk) {
|
|
238
|
+
return done(fail("MCP_STREAMABLE_HTTP_GET_LIBRARIES_INVALID", "POST /mcp tools/call get_libraries did not return the expected Figma MCP-compatible zero-config envelope.", getLibrariesCall.status));
|
|
239
|
+
}
|
|
240
|
+
const searchDesignSystemCall = await fetchText(endpoint, {
|
|
241
|
+
...options,
|
|
242
|
+
method: "POST",
|
|
243
|
+
sessionId,
|
|
244
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
245
|
+
body: {
|
|
246
|
+
jsonrpc: "2.0",
|
|
247
|
+
id: 32,
|
|
248
|
+
method: "tools/call",
|
|
249
|
+
params: {
|
|
250
|
+
name: "search_design_system",
|
|
251
|
+
arguments: {
|
|
252
|
+
source: "rest-known",
|
|
253
|
+
query: "button",
|
|
254
|
+
bridgeResponseMode: "full",
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
if (searchDesignSystemCall.status < 200 || searchDesignSystemCall.status >= 300) {
|
|
260
|
+
return done(fail("MCP_STREAMABLE_HTTP_SEARCH_DESIGN_SYSTEM_FAILED", `POST /mcp tools/call search_design_system returned HTTP ${searchDesignSystemCall.status}.`, searchDesignSystemCall.status));
|
|
261
|
+
}
|
|
262
|
+
const searchDesignSystemPayload = parseToolTextJsonPayload(searchDesignSystemCall);
|
|
263
|
+
const searchDesignSystemOk = isRecord(searchDesignSystemPayload) &&
|
|
264
|
+
searchDesignSystemPayload["ok"] === true &&
|
|
265
|
+
searchDesignSystemPayload["tool"] === "search_design_system" &&
|
|
266
|
+
searchDesignSystemPayload["compatibility"] === "figma-mcp-design" &&
|
|
267
|
+
typeof searchDesignSystemPayload["count"] === "number" &&
|
|
268
|
+
Array.isArray(searchDesignSystemPayload["warnings"]);
|
|
269
|
+
if (!searchDesignSystemOk) {
|
|
270
|
+
return done(fail("MCP_STREAMABLE_HTTP_SEARCH_DESIGN_SYSTEM_INVALID", "POST /mcp tools/call search_design_system did not return the expected Figma MCP-compatible zero-config envelope.", searchDesignSystemCall.status));
|
|
271
|
+
}
|
|
272
|
+
const listToolsCall = await fetchText(endpoint, {
|
|
273
|
+
...options,
|
|
274
|
+
method: "POST",
|
|
275
|
+
sessionId,
|
|
276
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
277
|
+
body: {
|
|
278
|
+
jsonrpc: "2.0",
|
|
279
|
+
id: 3,
|
|
280
|
+
method: "tools/call",
|
|
281
|
+
params: {
|
|
282
|
+
name: "list_tools",
|
|
283
|
+
arguments: { category: "diagnostics" },
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
if (listToolsCall.status < 200 || listToolsCall.status >= 300) {
|
|
288
|
+
return done(fail("MCP_STREAMABLE_HTTP_TOOL_CALL_FAILED", `POST /mcp tools/call list_tools returned HTTP ${listToolsCall.status}.`, listToolsCall.status));
|
|
289
|
+
}
|
|
290
|
+
const callMessage = parseJsonRpcResponse(listToolsCall);
|
|
291
|
+
const callResult = isRecord(callMessage?.result) ? callMessage.result : null;
|
|
292
|
+
const content = Array.isArray(callResult?.["content"]) ? callResult["content"] : [];
|
|
293
|
+
const firstText = content.find((item) => isRecord(item) && item["type"] === "text" && typeof item["text"] === "string");
|
|
294
|
+
let cataloguePayload = null;
|
|
295
|
+
if (isRecord(firstText) && typeof firstText["text"] === "string") {
|
|
296
|
+
try {
|
|
297
|
+
cataloguePayload = JSON.parse(firstText["text"]);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
cataloguePayload = null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const catalogueTools = isRecord(cataloguePayload) && Array.isArray(cataloguePayload["tools"])
|
|
304
|
+
? cataloguePayload["tools"]
|
|
305
|
+
: [];
|
|
306
|
+
const hasPluginStatus = catalogueTools.some((tool) => isRecord(tool) && tool["name"] === "get_plugin_status");
|
|
307
|
+
if (!hasPluginStatus) {
|
|
308
|
+
return done(fail("MCP_STREAMABLE_HTTP_TOOL_CALL_INVALID", "POST /mcp tools/call list_tools did not return the expected normalized Bridge tool response.", listToolsCall.status));
|
|
309
|
+
}
|
|
310
|
+
const validateScreenSpecCall = await fetchText(endpoint, {
|
|
311
|
+
...options,
|
|
312
|
+
method: "POST",
|
|
313
|
+
sessionId,
|
|
314
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
315
|
+
body: {
|
|
316
|
+
jsonrpc: "2.0",
|
|
317
|
+
id: 4,
|
|
318
|
+
method: "tools/call",
|
|
319
|
+
params: {
|
|
320
|
+
name: "validate_screen_spec",
|
|
321
|
+
arguments: {
|
|
322
|
+
spec: {
|
|
323
|
+
name: "SXL Bridge MCP Probe",
|
|
324
|
+
dryRun: true,
|
|
325
|
+
items: [{ kind: "text", value: "MCP probe" }],
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
if (validateScreenSpecCall.status < 200 || validateScreenSpecCall.status >= 300) {
|
|
332
|
+
return done(fail("MCP_STREAMABLE_HTTP_VALIDATE_SCREEN_SPEC_FAILED", `POST /mcp tools/call validate_screen_spec returned HTTP ${validateScreenSpecCall.status}.`, validateScreenSpecCall.status));
|
|
333
|
+
}
|
|
334
|
+
const validateSpecMessage = parseJsonRpcResponse(validateScreenSpecCall);
|
|
335
|
+
const validateSpecResult = isRecord(validateSpecMessage?.result) ? validateSpecMessage.result : null;
|
|
336
|
+
const validateSpecContent = Array.isArray(validateSpecResult?.["content"]) ? validateSpecResult["content"] : [];
|
|
337
|
+
const validateSpecFirstText = validateSpecContent.find((item) => isRecord(item) && item["type"] === "text" && typeof item["text"] === "string");
|
|
338
|
+
let validateSpecPayload = null;
|
|
339
|
+
if (isRecord(validateSpecFirstText) && typeof validateSpecFirstText["text"] === "string") {
|
|
340
|
+
try {
|
|
341
|
+
validateSpecPayload = JSON.parse(validateSpecFirstText["text"]);
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
validateSpecPayload = null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (!isRecord(validateSpecPayload) || validateSpecPayload["ok"] !== true || validateSpecPayload["dryRun"] !== true) {
|
|
348
|
+
return done(fail("MCP_STREAMABLE_HTTP_VALIDATE_SCREEN_SPEC_INVALID", "POST /mcp tools/call validate_screen_spec did not return the expected dry-run validation payload.", validateScreenSpecCall.status));
|
|
349
|
+
}
|
|
350
|
+
const previewExportCall = await fetchText(endpoint, {
|
|
351
|
+
...options,
|
|
352
|
+
method: "POST",
|
|
353
|
+
sessionId,
|
|
354
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
355
|
+
body: {
|
|
356
|
+
jsonrpc: "2.0",
|
|
357
|
+
id: 5,
|
|
358
|
+
method: "tools/call",
|
|
359
|
+
params: {
|
|
360
|
+
name: "preview_export_variables",
|
|
361
|
+
arguments: {
|
|
362
|
+
collection: "SXL Bridge MCP Probe",
|
|
363
|
+
deleteOrphans: false,
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
if (previewExportCall.status < 200 || previewExportCall.status >= 300) {
|
|
369
|
+
return done(fail("MCP_STREAMABLE_HTTP_PREVIEW_EXPORT_VARIABLES_FAILED", `POST /mcp tools/call preview_export_variables returned HTTP ${previewExportCall.status}.`, previewExportCall.status));
|
|
370
|
+
}
|
|
371
|
+
const previewExportMessage = parseJsonRpcResponse(previewExportCall);
|
|
372
|
+
const previewExportResult = isRecord(previewExportMessage?.result) ? previewExportMessage.result : null;
|
|
373
|
+
const previewExportContent = Array.isArray(previewExportResult?.["content"]) ? previewExportResult["content"] : [];
|
|
374
|
+
const previewExportFirstText = previewExportContent.find((item) => isRecord(item) && item["type"] === "text" && typeof item["text"] === "string");
|
|
375
|
+
let previewExportPayload = null;
|
|
376
|
+
if (isRecord(previewExportFirstText) && typeof previewExportFirstText["text"] === "string") {
|
|
377
|
+
try {
|
|
378
|
+
previewExportPayload = JSON.parse(previewExportFirstText["text"]);
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
previewExportPayload = null;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const previewExportOk = isRecord(previewExportPayload) &&
|
|
385
|
+
previewExportPayload["ok"] === true &&
|
|
386
|
+
previewExportPayload["dryRun"] === true &&
|
|
387
|
+
previewExportPayload["commandType"] === "export_variables" &&
|
|
388
|
+
isRecord(previewExportPayload["commandPayload"]);
|
|
389
|
+
if (!previewExportOk) {
|
|
390
|
+
return done(fail("MCP_STREAMABLE_HTTP_PREVIEW_EXPORT_VARIABLES_INVALID", "POST /mcp tools/call preview_export_variables did not return the expected export preflight payload.", previewExportCall.status));
|
|
391
|
+
}
|
|
392
|
+
const skillsParityCall = await fetchText(endpoint, {
|
|
393
|
+
...options,
|
|
394
|
+
method: "POST",
|
|
395
|
+
sessionId,
|
|
396
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
397
|
+
body: {
|
|
398
|
+
jsonrpc: "2.0",
|
|
399
|
+
id: 6,
|
|
400
|
+
method: "tools/call",
|
|
401
|
+
params: {
|
|
402
|
+
name: "get_figma_mcp_skills_parity",
|
|
403
|
+
arguments: {},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
if (skillsParityCall.status < 200 || skillsParityCall.status >= 300) {
|
|
408
|
+
return done(fail("MCP_STREAMABLE_HTTP_SKILLS_PARITY_FAILED", `POST /mcp tools/call get_figma_mcp_skills_parity returned HTTP ${skillsParityCall.status}.`, skillsParityCall.status));
|
|
409
|
+
}
|
|
410
|
+
const skillsParityMessage = parseJsonRpcResponse(skillsParityCall);
|
|
411
|
+
const skillsParityResult = isRecord(skillsParityMessage?.result) ? skillsParityMessage.result : null;
|
|
412
|
+
const skillsParityContent = Array.isArray(skillsParityResult?.["content"]) ? skillsParityResult["content"] : [];
|
|
413
|
+
const skillsParityFirstText = skillsParityContent.find((item) => isRecord(item) && item["type"] === "text" && typeof item["text"] === "string");
|
|
414
|
+
let skillsParityPayload = null;
|
|
415
|
+
if (isRecord(skillsParityFirstText) && typeof skillsParityFirstText["text"] === "string") {
|
|
416
|
+
try {
|
|
417
|
+
skillsParityPayload = JSON.parse(skillsParityFirstText["text"]);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
skillsParityPayload = null;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const skillsParityValidation = validateFigmaMcpSkillsParityPayload(skillsParityPayload);
|
|
424
|
+
if (!skillsParityValidation.ok) {
|
|
425
|
+
return done(fail("MCP_STREAMABLE_HTTP_SKILLS_PARITY_INVALID", `POST /mcp tools/call get_figma_mcp_skills_parity did not return the expected current skills parity matrix: ${skillsParityValidation.errors.join(" ")}`, skillsParityCall.status));
|
|
426
|
+
}
|
|
427
|
+
const operatorRunbookToolCall = await fetchText(endpoint, {
|
|
428
|
+
...options,
|
|
429
|
+
method: "POST",
|
|
430
|
+
sessionId,
|
|
431
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
432
|
+
body: {
|
|
433
|
+
jsonrpc: "2.0",
|
|
434
|
+
id: 61,
|
|
435
|
+
method: "tools/call",
|
|
436
|
+
params: {
|
|
437
|
+
name: "get_operator_runbook",
|
|
438
|
+
arguments: {},
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
if (operatorRunbookToolCall.status < 200 || operatorRunbookToolCall.status >= 300) {
|
|
443
|
+
return done(fail("MCP_STREAMABLE_HTTP_OPERATOR_RUNBOOK_TOOL_FAILED", `POST /mcp tools/call get_operator_runbook returned HTTP ${operatorRunbookToolCall.status}.`, operatorRunbookToolCall.status));
|
|
444
|
+
}
|
|
445
|
+
const operatorRunbookToolPayload = parseToolTextJsonPayload(operatorRunbookToolCall);
|
|
446
|
+
const operatorRunbookToolWorkflows = isRecord(operatorRunbookToolPayload) && Array.isArray(operatorRunbookToolPayload["workflows"])
|
|
447
|
+
? operatorRunbookToolPayload["workflows"]
|
|
448
|
+
: [];
|
|
449
|
+
const operatorRunbookToolWorkflowIds = new Set(operatorRunbookToolWorkflows
|
|
450
|
+
.filter(isRecord)
|
|
451
|
+
.map((workflow) => typeof workflow["id"] === "string" ? workflow["id"] : "")
|
|
452
|
+
.filter(Boolean));
|
|
453
|
+
const operatorRunbookToolOk = isRecord(operatorRunbookToolPayload) &&
|
|
454
|
+
operatorRunbookToolPayload["id"] === "sxl-operator-runbook" &&
|
|
455
|
+
operatorRunbookToolWorkflowIds.has("draw-or-update-screen") &&
|
|
456
|
+
operatorRunbookToolWorkflowIds.has("official-figma-mcp-companion");
|
|
457
|
+
if (!operatorRunbookToolOk) {
|
|
458
|
+
return done(fail("MCP_STREAMABLE_HTTP_OPERATOR_RUNBOOK_TOOL_INVALID", "POST /mcp tools/call get_operator_runbook did not return the expected SXL operator workflow map.", operatorRunbookToolCall.status));
|
|
459
|
+
}
|
|
460
|
+
const libraryWorkflowCall = await fetchText(endpoint, {
|
|
461
|
+
...options,
|
|
462
|
+
method: "POST",
|
|
463
|
+
sessionId,
|
|
464
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
465
|
+
body: {
|
|
466
|
+
jsonrpc: "2.0",
|
|
467
|
+
id: 7,
|
|
468
|
+
method: "tools/call",
|
|
469
|
+
params: {
|
|
470
|
+
name: "plan_workflow",
|
|
471
|
+
arguments: {
|
|
472
|
+
request: "get_libraries and search_design_system for a button component",
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
if (libraryWorkflowCall.status < 200 || libraryWorkflowCall.status >= 300) {
|
|
478
|
+
return done(fail("MCP_STREAMABLE_HTTP_LIBRARY_WORKFLOW_FAILED", `POST /mcp tools/call plan_workflow returned HTTP ${libraryWorkflowCall.status}.`, libraryWorkflowCall.status));
|
|
479
|
+
}
|
|
480
|
+
const libraryWorkflowPayload = parseToolTextJsonPayload(libraryWorkflowCall);
|
|
481
|
+
const libraryWorkflowRoute = isRecord(libraryWorkflowPayload) && isRecord(libraryWorkflowPayload["route"])
|
|
482
|
+
? libraryWorkflowPayload["route"]
|
|
483
|
+
: null;
|
|
484
|
+
const libraryWorkflowSteps = isRecord(libraryWorkflowPayload) && Array.isArray(libraryWorkflowPayload["steps"])
|
|
485
|
+
? libraryWorkflowPayload["steps"]
|
|
486
|
+
: [];
|
|
487
|
+
const libraryWorkflowToolNames = new Set(libraryWorkflowSteps
|
|
488
|
+
.filter(isRecord)
|
|
489
|
+
.map((step) => typeof step["toolName"] === "string" ? step["toolName"] : "")
|
|
490
|
+
.filter(Boolean));
|
|
491
|
+
const usesCompanionGate = libraryWorkflowSteps.some((step) => isRecord(step) && step["title"] === "Use official Figma MCP companion");
|
|
492
|
+
const libraryWorkflowOk = isRecord(libraryWorkflowRoute) &&
|
|
493
|
+
libraryWorkflowRoute["recipeId"] === "sxl-use" &&
|
|
494
|
+
libraryWorkflowToolNames.has("get_libraries") &&
|
|
495
|
+
libraryWorkflowToolNames.has("search_design_system") &&
|
|
496
|
+
!usesCompanionGate;
|
|
497
|
+
if (!libraryWorkflowOk) {
|
|
498
|
+
return done(fail("MCP_STREAMABLE_HTTP_LIBRARY_WORKFLOW_INVALID", "POST /mcp tools/call plan_workflow did not route Figma Design library discovery through Bridge get_libraries/search_design_system.", libraryWorkflowCall.status));
|
|
499
|
+
}
|
|
500
|
+
const resourcesList = await fetchText(endpoint, {
|
|
501
|
+
...options,
|
|
502
|
+
method: "POST",
|
|
503
|
+
sessionId,
|
|
504
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
505
|
+
body: {
|
|
506
|
+
jsonrpc: "2.0",
|
|
507
|
+
id: 8,
|
|
508
|
+
method: "resources/list",
|
|
509
|
+
params: {},
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
if (resourcesList.status < 200 || resourcesList.status >= 300) {
|
|
513
|
+
return done(fail("MCP_STREAMABLE_HTTP_RESOURCES_LIST_FAILED", `POST /mcp resources/list returned HTTP ${resourcesList.status}.`, resourcesList.status));
|
|
514
|
+
}
|
|
515
|
+
const resourcesMessage = parseJsonRpcResponse(resourcesList);
|
|
516
|
+
const resourcesResult = isRecord(resourcesMessage?.result) ? resourcesMessage.result : null;
|
|
517
|
+
const resources = Array.isArray(resourcesResult?.["resources"]) ? resourcesResult["resources"] : [];
|
|
518
|
+
const hasRuntimeSummary = resources.some((resource) => isRecord(resource) && resource["uri"] === "sxl://agent/runtime-summary");
|
|
519
|
+
const hasOperatorRunbookResource = resources.some((resource) => isRecord(resource) && resource["uri"] === "sxl://agent/operator-runbook");
|
|
520
|
+
const hasSkillsParityResource = resources.some((resource) => isRecord(resource) && resource["uri"] === "sxl://agent/figma-mcp-skills-parity");
|
|
521
|
+
if (!hasRuntimeSummary || !hasOperatorRunbookResource || !hasSkillsParityResource) {
|
|
522
|
+
return done(fail("MCP_STREAMABLE_HTTP_RESOURCES_INVALID", "POST /mcp resources/list did not return required SXL agent resources.", resourcesList.status));
|
|
523
|
+
}
|
|
524
|
+
const runtimeSummaryRead = await fetchText(endpoint, {
|
|
525
|
+
...options,
|
|
526
|
+
method: "POST",
|
|
527
|
+
sessionId,
|
|
528
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
529
|
+
body: {
|
|
530
|
+
jsonrpc: "2.0",
|
|
531
|
+
id: 9,
|
|
532
|
+
method: "resources/read",
|
|
533
|
+
params: {
|
|
534
|
+
uri: "sxl://agent/runtime-summary",
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
if (runtimeSummaryRead.status < 200 || runtimeSummaryRead.status >= 300) {
|
|
539
|
+
return done(fail("MCP_STREAMABLE_HTTP_RESOURCE_READ_FAILED", `POST /mcp resources/read runtime-summary returned HTTP ${runtimeSummaryRead.status}.`, runtimeSummaryRead.status));
|
|
540
|
+
}
|
|
541
|
+
const runtimeSummaryMessage = parseJsonRpcResponse(runtimeSummaryRead);
|
|
542
|
+
const runtimeSummaryResult = isRecord(runtimeSummaryMessage?.result) ? runtimeSummaryMessage.result : null;
|
|
543
|
+
const resourceContents = Array.isArray(runtimeSummaryResult?.["contents"]) ? runtimeSummaryResult["contents"] : [];
|
|
544
|
+
const runtimeSummaryContent = resourceContents.find((item) => isRecord(item) && item["uri"] === "sxl://agent/runtime-summary" && typeof item["text"] === "string");
|
|
545
|
+
let runtimeSummaryPayload = null;
|
|
546
|
+
if (isRecord(runtimeSummaryContent) && typeof runtimeSummaryContent["text"] === "string") {
|
|
547
|
+
try {
|
|
548
|
+
runtimeSummaryPayload = JSON.parse(runtimeSummaryContent["text"]);
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
runtimeSummaryPayload = null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const runtimeSummaryOk = isRecord(runtimeSummaryPayload) &&
|
|
555
|
+
isRecord(runtimeSummaryPayload["transport"]) &&
|
|
556
|
+
typeof runtimeSummaryPayload["transport"]["mcpStreamableHttp"] === "string" &&
|
|
557
|
+
isRecord(runtimeSummaryPayload["plugin"]) &&
|
|
558
|
+
isRecord(runtimeSummaryPayload["queue"]);
|
|
559
|
+
if (!runtimeSummaryOk) {
|
|
560
|
+
return done(fail("MCP_STREAMABLE_HTTP_RESOURCE_READ_INVALID", "POST /mcp resources/read runtime-summary did not return the expected Bridge runtime resource payload.", runtimeSummaryRead.status));
|
|
561
|
+
}
|
|
562
|
+
const operatorRunbookResourceRead = await fetchText(endpoint, {
|
|
563
|
+
...options,
|
|
564
|
+
method: "POST",
|
|
565
|
+
sessionId,
|
|
566
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
567
|
+
body: {
|
|
568
|
+
jsonrpc: "2.0",
|
|
569
|
+
id: 10,
|
|
570
|
+
method: "resources/read",
|
|
571
|
+
params: {
|
|
572
|
+
uri: "sxl://agent/operator-runbook",
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
if (operatorRunbookResourceRead.status < 200 || operatorRunbookResourceRead.status >= 300) {
|
|
577
|
+
return done(fail("MCP_STREAMABLE_HTTP_OPERATOR_RUNBOOK_RESOURCE_READ_FAILED", `POST /mcp resources/read operator-runbook returned HTTP ${operatorRunbookResourceRead.status}.`, operatorRunbookResourceRead.status));
|
|
578
|
+
}
|
|
579
|
+
const operatorRunbookPayload = parseResourceTextJsonPayload(operatorRunbookResourceRead, "sxl://agent/operator-runbook");
|
|
580
|
+
const operatorRunbookWorkflows = isRecord(operatorRunbookPayload) && Array.isArray(operatorRunbookPayload["workflows"])
|
|
581
|
+
? operatorRunbookPayload["workflows"]
|
|
582
|
+
: [];
|
|
583
|
+
const operatorWorkflowIds = new Set(operatorRunbookWorkflows
|
|
584
|
+
.filter(isRecord)
|
|
585
|
+
.map((workflow) => typeof workflow["id"] === "string" ? workflow["id"] : "")
|
|
586
|
+
.filter(Boolean));
|
|
587
|
+
const operatorRunbookOk = isRecord(operatorRunbookPayload) &&
|
|
588
|
+
operatorRunbookPayload["id"] === "sxl-operator-runbook" &&
|
|
589
|
+
operatorWorkflowIds.has("draw-or-update-screen") &&
|
|
590
|
+
operatorWorkflowIds.has("official-figma-mcp-companion");
|
|
591
|
+
if (!operatorRunbookOk) {
|
|
592
|
+
return done(fail("MCP_STREAMABLE_HTTP_OPERATOR_RUNBOOK_RESOURCE_READ_INVALID", "POST /mcp resources/read operator-runbook did not return the expected SXL operator workflow map.", operatorRunbookResourceRead.status));
|
|
593
|
+
}
|
|
594
|
+
const skillsParityResourceRead = await fetchText(endpoint, {
|
|
595
|
+
...options,
|
|
596
|
+
method: "POST",
|
|
597
|
+
sessionId,
|
|
598
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
599
|
+
body: {
|
|
600
|
+
jsonrpc: "2.0",
|
|
601
|
+
id: 11,
|
|
602
|
+
method: "resources/read",
|
|
603
|
+
params: {
|
|
604
|
+
uri: "sxl://agent/figma-mcp-skills-parity",
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
if (skillsParityResourceRead.status < 200 || skillsParityResourceRead.status >= 300) {
|
|
609
|
+
return done(fail("MCP_STREAMABLE_HTTP_SKILLS_PARITY_RESOURCE_READ_FAILED", `POST /mcp resources/read figma-mcp-skills-parity returned HTTP ${skillsParityResourceRead.status}.`, skillsParityResourceRead.status));
|
|
610
|
+
}
|
|
611
|
+
const skillsParityResourceMessage = parseJsonRpcResponse(skillsParityResourceRead);
|
|
612
|
+
const skillsParityResourceResult = isRecord(skillsParityResourceMessage?.result) ? skillsParityResourceMessage.result : null;
|
|
613
|
+
const skillsParityResourceContents = Array.isArray(skillsParityResourceResult?.["contents"]) ? skillsParityResourceResult["contents"] : [];
|
|
614
|
+
const skillsParityResourceContent = skillsParityResourceContents.find((item) => isRecord(item) && item["uri"] === "sxl://agent/figma-mcp-skills-parity" && typeof item["text"] === "string");
|
|
615
|
+
let skillsParityResourcePayload = null;
|
|
616
|
+
if (isRecord(skillsParityResourceContent) && typeof skillsParityResourceContent["text"] === "string") {
|
|
617
|
+
try {
|
|
618
|
+
skillsParityResourcePayload = JSON.parse(skillsParityResourceContent["text"]);
|
|
619
|
+
}
|
|
620
|
+
catch {
|
|
621
|
+
skillsParityResourcePayload = null;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const skillsParityResourceValidation = validateFigmaMcpSkillsParityPayload(skillsParityResourcePayload);
|
|
625
|
+
if (!skillsParityResourceValidation.ok) {
|
|
626
|
+
return done(fail("MCP_STREAMABLE_HTTP_SKILLS_PARITY_RESOURCE_READ_INVALID", `POST /mcp resources/read figma-mcp-skills-parity did not return the expected current skills parity resource payload: ${skillsParityResourceValidation.errors.join(" ")}`, skillsParityResourceRead.status));
|
|
627
|
+
}
|
|
628
|
+
const promptsList = await fetchText(endpoint, {
|
|
629
|
+
...options,
|
|
630
|
+
method: "POST",
|
|
631
|
+
sessionId,
|
|
632
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
633
|
+
body: {
|
|
634
|
+
jsonrpc: "2.0",
|
|
635
|
+
id: 12,
|
|
636
|
+
method: "prompts/list",
|
|
637
|
+
params: {},
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
if (promptsList.status < 200 || promptsList.status >= 300) {
|
|
641
|
+
return done(fail("MCP_STREAMABLE_HTTP_PROMPTS_LIST_FAILED", `POST /mcp prompts/list returned HTTP ${promptsList.status}.`, promptsList.status));
|
|
642
|
+
}
|
|
643
|
+
const promptsMessage = parseJsonRpcResponse(promptsList);
|
|
644
|
+
const promptsResult = isRecord(promptsMessage?.result) ? promptsMessage.result : null;
|
|
645
|
+
const prompts = Array.isArray(promptsResult?.["prompts"]) ? promptsResult["prompts"] : [];
|
|
646
|
+
const promptNames = new Set(prompts
|
|
647
|
+
.filter(isRecord)
|
|
648
|
+
.map((prompt) => typeof prompt["name"] === "string" ? prompt["name"] : "")
|
|
649
|
+
.filter(Boolean));
|
|
650
|
+
const missingPrompts = ["sxl-operator", "sxl-generate-design", "figma-mcp-companion"]
|
|
651
|
+
.filter((name) => !promptNames.has(name));
|
|
652
|
+
if (missingPrompts.length > 0) {
|
|
653
|
+
return done(fail("MCP_STREAMABLE_HTTP_PROMPTS_INVALID", `POST /mcp prompts/list did not return required SXL skill prompts: ${missingPrompts.join(", ")}.`, promptsList.status));
|
|
654
|
+
}
|
|
655
|
+
const generateDesignPrompt = await fetchText(endpoint, {
|
|
656
|
+
...options,
|
|
657
|
+
method: "POST",
|
|
658
|
+
sessionId,
|
|
659
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
660
|
+
body: {
|
|
661
|
+
jsonrpc: "2.0",
|
|
662
|
+
id: 13,
|
|
663
|
+
method: "prompts/get",
|
|
664
|
+
params: {
|
|
665
|
+
name: "sxl-generate-design",
|
|
666
|
+
arguments: {
|
|
667
|
+
request: "Build a settings screen in Figma",
|
|
668
|
+
focus: "runtime probe",
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
if (generateDesignPrompt.status < 200 || generateDesignPrompt.status >= 300) {
|
|
674
|
+
return done(fail("MCP_STREAMABLE_HTTP_PROMPT_GET_FAILED", `POST /mcp prompts/get sxl-generate-design returned HTTP ${generateDesignPrompt.status}.`, generateDesignPrompt.status));
|
|
675
|
+
}
|
|
676
|
+
const promptText = parsePromptText(generateDesignPrompt);
|
|
677
|
+
if (!promptText.includes("sxl://agent/recipes/sxl-generate-design") || !promptText.includes("validate_screen_spec")) {
|
|
678
|
+
return done(fail("MCP_STREAMABLE_HTTP_PROMPT_GET_INVALID", "POST /mcp prompts/get sxl-generate-design did not return the expected SXL skill prompt content.", generateDesignPrompt.status));
|
|
679
|
+
}
|
|
680
|
+
const operatorPrompt = await fetchText(endpoint, {
|
|
681
|
+
...options,
|
|
682
|
+
method: "POST",
|
|
683
|
+
sessionId,
|
|
684
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
685
|
+
body: {
|
|
686
|
+
jsonrpc: "2.0",
|
|
687
|
+
id: 14,
|
|
688
|
+
method: "prompts/get",
|
|
689
|
+
params: {
|
|
690
|
+
name: "sxl-operator",
|
|
691
|
+
arguments: {
|
|
692
|
+
request: "Audit variables and draw documentation in Figma",
|
|
693
|
+
focus: "runtime probe",
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
if (operatorPrompt.status < 200 || operatorPrompt.status >= 300) {
|
|
699
|
+
return done(fail("MCP_STREAMABLE_HTTP_OPERATOR_PROMPT_GET_FAILED", `POST /mcp prompts/get sxl-operator returned HTTP ${operatorPrompt.status}.`, operatorPrompt.status));
|
|
700
|
+
}
|
|
701
|
+
const operatorPromptText = parsePromptText(operatorPrompt);
|
|
702
|
+
const operatorPromptOk = operatorPromptText.includes("sxl://agent/operator-runbook") &&
|
|
703
|
+
operatorPromptText.includes("draw-or-update-screen") &&
|
|
704
|
+
operatorPromptText.includes("npm run bridge:readiness:live");
|
|
705
|
+
if (!operatorPromptOk) {
|
|
706
|
+
return done(fail("MCP_STREAMABLE_HTTP_OPERATOR_PROMPT_GET_INVALID", "POST /mcp prompts/get sxl-operator did not return the expected SXL operator runbook prompt content.", operatorPrompt.status));
|
|
707
|
+
}
|
|
708
|
+
return done({
|
|
709
|
+
ok: true,
|
|
710
|
+
status: warnings.length > 0 ? "warn" : "pass",
|
|
711
|
+
details: {
|
|
712
|
+
session: "stateful",
|
|
713
|
+
toolCount: tools.length,
|
|
714
|
+
diagnosticsToolCount: catalogueTools.length,
|
|
715
|
+
toolCalls: [
|
|
716
|
+
"list_tools",
|
|
717
|
+
"get_operator_runbook",
|
|
718
|
+
"validate_screen_spec",
|
|
719
|
+
"preview_export_variables",
|
|
720
|
+
"get_figma_mcp_skills_parity",
|
|
721
|
+
"plan_workflow",
|
|
722
|
+
"get_libraries",
|
|
723
|
+
"search_design_system",
|
|
724
|
+
],
|
|
725
|
+
localValidationTool: "validate_screen_spec",
|
|
726
|
+
resourceCount: resources.length,
|
|
727
|
+
resourceRead: [
|
|
728
|
+
"sxl://agent/runtime-summary",
|
|
729
|
+
"sxl://agent/operator-runbook",
|
|
730
|
+
"sxl://agent/figma-mcp-skills-parity",
|
|
731
|
+
],
|
|
732
|
+
promptCount: prompts.length,
|
|
733
|
+
promptRead: ["sxl-operator", "sxl-generate-design"],
|
|
734
|
+
figmaMcpSkillsParity: {
|
|
735
|
+
officialSkillCount: skillsParityValidation.officialSkillCount,
|
|
736
|
+
compatibilityAliases: skillsParityValidation.compatibilityAliases,
|
|
737
|
+
},
|
|
738
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
739
|
+
},
|
|
740
|
+
warnings,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
catch (error) {
|
|
744
|
+
return done(fail("MCP_STREAMABLE_HTTP_UNREACHABLE", `MCP /mcp probe failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
745
|
+
}
|
|
746
|
+
finally {
|
|
747
|
+
if (sessionId) {
|
|
748
|
+
try {
|
|
749
|
+
const closed = await fetchText(endpoint, {
|
|
750
|
+
...options,
|
|
751
|
+
method: "DELETE",
|
|
752
|
+
sessionId,
|
|
753
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
754
|
+
});
|
|
755
|
+
if (closed.status < 200 || closed.status >= 300) {
|
|
756
|
+
warnings.push({
|
|
757
|
+
code: "MCP_STREAMABLE_HTTP_TERMINATE_WARN",
|
|
758
|
+
message: `DELETE /mcp returned HTTP ${closed.status}; runtime probe succeeded but session cleanup may have failed.`,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
warnings.push({
|
|
764
|
+
code: "MCP_STREAMABLE_HTTP_TERMINATE_WARN",
|
|
765
|
+
message: `DELETE /mcp failed after runtime probe: ${error instanceof Error ? error.message : String(error)}`,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (finalResult.current) {
|
|
770
|
+
finalResult.current.warnings = warnings;
|
|
771
|
+
if (finalResult.current.ok && warnings.length > 0) {
|
|
772
|
+
finalResult.current.status = "warn";
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
//# sourceMappingURL=mcp-runtime-probe.js.map
|