@just-every/design 0.1.31 → 0.1.33
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 +10 -45
- package/dist/cli.js +113 -653
- package/dist/cli.js.map +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +36 -53
- package/dist/install.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +69 -0
- package/dist/instructions.js.map +1 -0
- package/dist/progress.d.ts +9 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +88 -0
- package/dist/progress.js.map +1 -0
- package/dist/response-guidance.d.ts +3 -0
- package/dist/response-guidance.d.ts.map +1 -1
- package/dist/response-guidance.js +428 -92
- package/dist/response-guidance.js.map +1 -1
- package/dist/screenshot.d.ts +17 -0
- package/dist/screenshot.d.ts.map +1 -0
- package/dist/screenshot.js +222 -0
- package/dist/screenshot.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +130 -465
- package/dist/server.js.map +1 -1
- package/dist/tool-logic.d.ts +1 -0
- package/dist/tool-logic.d.ts.map +1 -1
- package/dist/tool-logic.js +19 -96
- package/dist/tool-logic.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
6
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import { statSync } from 'node:fs';
|
|
7
8
|
import { readFile } from 'node:fs/promises';
|
|
8
|
-
import
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
12
|
import { DesignAppClient } from './design-client.js';
|
|
13
|
+
import { INSTRUCTIONS_MD } from './instructions.js';
|
|
14
|
+
import { runScreenshotCommand } from './screenshot.js';
|
|
10
15
|
import { buildCreateRunRequest, NOT_AUTHENTICATED_HELP, watchRun } from './tool-logic.js';
|
|
11
|
-
import { wrapToolResponse } from './response-guidance.js';
|
|
16
|
+
import { formatToolResponseMarkdown, wrapToolResponse } from './response-guidance.js';
|
|
17
|
+
import { createSyncProgressTracker, getProgressPct } from './progress.js';
|
|
12
18
|
export async function startMcpServer(options) {
|
|
13
19
|
const config = options.config;
|
|
14
20
|
const sessionToken = config.sessionToken ?? '';
|
|
@@ -34,155 +40,51 @@ export async function startMcpServer(options) {
|
|
|
34
40
|
tools: [
|
|
35
41
|
{
|
|
36
42
|
name: 'design.create',
|
|
37
|
-
description: 'Create a design run in
|
|
38
|
-
'web/landing page designs (HTML/CSS + assets), logos, icons, marketing graphics/illustrations, ' +
|
|
39
|
-
'photorealistic images, backgrounds/patterns, and even 3D assets (GLB). ' +
|
|
40
|
-
'Attach 1+ source images via `sourceImages` (local path, URL, or base64) or pass web URLs via `referenceUrls`/`references`. ' +
|
|
41
|
-
'Note: when using this MCP server/CLI, HTML output is opt-in (use output.designKind=interface and enable progression.autoGenerateHtml/autoExtractAssets or call `design.generateHtml`). If output.designKind is omitted, this server defaults to output.designKind=interface to avoid expensive code builds while still producing a UI design. For interface runs, asset extraction and HTML generation are deferred by default; use `design.extractAssets` and `design.generateHtml` when you explicitly want those outputs. To allow backend inference, explicitly set output.designKind=auto. ' +
|
|
42
|
-
'Returns a run id; follow up with `design.watch`, `design.get`, and `design.artifacts.*`.',
|
|
43
|
+
description: 'Create a new design. Returns run id. You must use design.create -> design.sync -> design.check in that order for interface creation or visual tasks (UI, Web, Mobile, Logos, Icons, 3D, Photos).',
|
|
43
44
|
inputSchema: {
|
|
44
45
|
type: 'object',
|
|
45
46
|
properties: {
|
|
46
47
|
prompt: { type: 'string', description: 'Prompt for the design run.' },
|
|
47
48
|
parentRunId: {
|
|
48
49
|
type: 'string',
|
|
49
|
-
description: 'Optional
|
|
50
|
-
},
|
|
51
|
-
output: {
|
|
52
|
-
type: 'object',
|
|
53
|
-
description: 'Optional output controls. If omitted, the system tries to infer the best kind from the prompt.',
|
|
54
|
-
properties: {
|
|
55
|
-
designKind: {
|
|
56
|
-
type: 'string',
|
|
57
|
-
description: 'What to generate. Use interface for UI designs (image target by default; HTML/code is opt-in via progression flags); use 3dasset for GLB output.',
|
|
58
|
-
enum: ['auto', 'logo', 'icon', 'interface', 'graphic', 'photo', 'background', '3dasset'],
|
|
59
|
-
},
|
|
60
|
-
format: {
|
|
61
|
-
type: 'string',
|
|
62
|
-
description: 'Optional format hint for image outputs.',
|
|
63
|
-
enum: ['png', 'webp'],
|
|
64
|
-
},
|
|
65
|
-
assetFormats: {
|
|
66
|
-
type: 'array',
|
|
67
|
-
description: 'Optional formats for 3d assets (glb is always produced).',
|
|
68
|
-
items: { type: 'string', enum: ['glb', 'fbx'] },
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
additionalProperties: true,
|
|
72
|
-
},
|
|
73
|
-
style: {
|
|
74
|
-
type: 'string',
|
|
75
|
-
description: 'Optional style string appended to the prompt (e.g. “minimal, Swiss, high-contrast”).',
|
|
50
|
+
description: 'Optional request changes to a target design or new designs based on a previous run.',
|
|
76
51
|
},
|
|
77
52
|
referenceUrls: {
|
|
78
53
|
type: 'array',
|
|
79
|
-
description: 'Optional web-accessible URLs to use as visual reference/inspiration.',
|
|
54
|
+
description: 'Optional public web-accessible URLs to use as visual reference/inspiration.',
|
|
80
55
|
items: { type: 'string' },
|
|
81
56
|
maxItems: 8,
|
|
82
57
|
},
|
|
83
|
-
references: {
|
|
84
|
-
type: 'array',
|
|
85
|
-
description: 'Optional structured references (advanced). Most users should prefer `sourceImages` or `referenceUrls`.',
|
|
86
|
-
items: {
|
|
87
|
-
type: 'object',
|
|
88
|
-
properties: {
|
|
89
|
-
type: { type: 'string', enum: ['url', 'path'] },
|
|
90
|
-
url: { type: 'string' },
|
|
91
|
-
path: { type: 'string' },
|
|
92
|
-
filename: { type: 'string' },
|
|
93
|
-
userProvided: { type: 'boolean' },
|
|
94
|
-
},
|
|
95
|
-
required: ['type'],
|
|
96
|
-
additionalProperties: true,
|
|
97
|
-
},
|
|
98
|
-
maxItems: 8,
|
|
99
|
-
},
|
|
100
58
|
sourceImages: {
|
|
101
59
|
type: 'array',
|
|
102
|
-
description: 'Optional images to upload to Every Design and use as references. Supports local file paths
|
|
60
|
+
description: 'Optional local images to upload to Every Design and use as references. Supports local file paths and URLs.',
|
|
103
61
|
items: {
|
|
104
62
|
type: 'object',
|
|
105
63
|
properties: {
|
|
106
|
-
type: { type: 'string', enum: ['path', 'url'
|
|
107
|
-
path: { type: 'string', description: 'Local file path (png/jpg).'
|
|
108
|
-
url: { type: 'string', description: 'Public URL of an image.'
|
|
109
|
-
data: { type: 'string', description: 'Base64 payload (optionally a data: URL).', },
|
|
110
|
-
mime: { type: 'string', description: 'MIME type for base64 data (e.g. image/png).', },
|
|
111
|
-
filename: { type: 'string', description: 'Optional filename override.', },
|
|
64
|
+
type: { type: 'string', enum: ['path', 'url'] },
|
|
65
|
+
path: { type: 'string', description: 'Local file path (png/jpg).' },
|
|
66
|
+
url: { type: 'string', description: 'Public URL of an image.' },
|
|
112
67
|
},
|
|
113
68
|
required: ['type'],
|
|
114
69
|
additionalProperties: false,
|
|
115
70
|
},
|
|
116
71
|
maxItems: 8,
|
|
117
72
|
},
|
|
118
|
-
config: {
|
|
119
|
-
type: 'object',
|
|
120
|
-
description: 'Advanced pass-through config merged with the convenience fields above. Useful keys include: ' +
|
|
121
|
-
'`output` (designKind/format/assetFormats), `references` (url/path refs), `imageLoop`, `codeLoop`, and `refinementLoop`.',
|
|
122
|
-
additionalProperties: true,
|
|
123
|
-
},
|
|
124
73
|
},
|
|
125
74
|
required: ['prompt'],
|
|
126
75
|
additionalProperties: false,
|
|
127
76
|
},
|
|
128
77
|
},
|
|
129
78
|
{
|
|
130
|
-
name: 'design.
|
|
131
|
-
description: '
|
|
132
|
-
inputSchema: {
|
|
133
|
-
type: 'object',
|
|
134
|
-
properties: {
|
|
135
|
-
page: { type: 'number', description: 'Page number (default 1).' },
|
|
136
|
-
limit: { type: 'number', description: 'Page size (default 20, max 50).' },
|
|
137
|
-
},
|
|
138
|
-
additionalProperties: false,
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
name: 'design.get',
|
|
143
|
-
description: 'Fetch a single design run by id (status, prompt, timestamps, etc).',
|
|
79
|
+
name: 'design.sync',
|
|
80
|
+
description: 'Wait for a design to complete, extracts assets and sync to a folder. Takes up to 30 mins for a fresh design. MCP clients should call with a long timeout (>=30 minutes).',
|
|
144
81
|
inputSchema: {
|
|
145
82
|
type: 'object',
|
|
146
83
|
properties: {
|
|
147
84
|
runId: { type: 'string' },
|
|
148
|
-
|
|
149
|
-
required: ['runId'],
|
|
150
|
-
additionalProperties: false,
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
name: 'design.watch',
|
|
155
|
-
description: 'Watch a run until it finishes by polling status. Optionally sync useful artifacts to a local folder (downloads missing files and can trigger asset extraction when needed).',
|
|
156
|
-
inputSchema: {
|
|
157
|
-
type: 'object',
|
|
158
|
-
properties: {
|
|
159
|
-
runId: { type: 'string' },
|
|
160
|
-
timeoutSeconds: { type: 'number', description: 'Max wait time (default 3600).' },
|
|
161
|
-
intervalSeconds: { type: 'number', description: 'Polling interval (default 5).' },
|
|
162
|
-
syncDir: { type: 'string', description: 'Optional local directory to sync artifacts into.' },
|
|
163
|
-
ensureAssets: { type: 'boolean', description: 'Whether to trigger extract-assets if assets are missing when syncDir is set (default true).' },
|
|
164
|
-
targetArtifactId: { type: 'string', description: 'Optional: select this artifact as the run target before extracting/syncing.' },
|
|
165
|
-
},
|
|
166
|
-
required: ['runId'],
|
|
167
|
-
additionalProperties: false,
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
name: 'design.extractAssets',
|
|
172
|
-
description: 'Trigger the asset extraction phase for an existing interface run (useful when runs are configured to stop after the target image by default). ' +
|
|
173
|
-
'If you pass targetArtifactId, this tool will first set the run\'s explicit target selection to that artifact (so subsequent progress actions use the same target unless cleared).',
|
|
174
|
-
inputSchema: {
|
|
175
|
-
type: 'object',
|
|
176
|
-
properties: {
|
|
177
|
-
runId: { type: 'string' },
|
|
178
|
-
targetArtifactId: {
|
|
179
|
-
type: 'string',
|
|
180
|
-
description: 'Optional artifact id to explicitly set as the run\'s target image before extracting assets (must be an image draft artifact from the same run).',
|
|
181
|
-
},
|
|
182
|
-
designKind: {
|
|
85
|
+
syncDir: {
|
|
183
86
|
type: 'string',
|
|
184
|
-
description: 'Optional
|
|
185
|
-
enum: ['interface'],
|
|
87
|
+
description: 'Optional local directory to sync artifacts into (default: ./every-design/<run-id>).',
|
|
186
88
|
},
|
|
187
89
|
},
|
|
188
90
|
required: ['runId'],
|
|
@@ -190,187 +92,25 @@ export async function startMcpServer(options) {
|
|
|
190
92
|
},
|
|
191
93
|
},
|
|
192
94
|
{
|
|
193
|
-
name: 'design.
|
|
194
|
-
description: '
|
|
195
|
-
'If you pass targetArtifactId, this tool will first set the run\'s explicit target selection to that artifact.',
|
|
95
|
+
name: 'design.check',
|
|
96
|
+
description: 'Returns a screenshot and guidance on how close we are to matching a design run. Accepts either URL or path to html file. MCP clients should call with a long timeout (>=15 minutes).',
|
|
196
97
|
inputSchema: {
|
|
197
98
|
type: 'object',
|
|
198
99
|
properties: {
|
|
199
100
|
runId: { type: 'string' },
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
description: 'Optional artifact id to explicitly set as the run\'s target image before generating HTML (must be an image draft artifact from the same run).',
|
|
203
|
-
},
|
|
101
|
+
url: { type: 'string', description: 'HTTP(S) URL to screenshot (local URL is allowed).' },
|
|
102
|
+
path: { type: 'string', description: 'Absolute or relative path to a local HTML file.' },
|
|
204
103
|
},
|
|
205
104
|
required: ['runId'],
|
|
206
105
|
additionalProperties: false,
|
|
207
106
|
},
|
|
208
107
|
},
|
|
209
108
|
{
|
|
210
|
-
name: 'design.
|
|
211
|
-
description: '
|
|
212
|
-
'Provide 1+ render screenshots via `render`. Optionally include a `target` image; if omitted, the backend will use the run\'s latest target image.',
|
|
213
|
-
inputSchema: {
|
|
214
|
-
type: 'object',
|
|
215
|
-
properties: {
|
|
216
|
-
runId: { type: 'string' },
|
|
217
|
-
concerns: { type: 'string', description: 'Optional: what to focus on (spacing, hierarchy, etc).' },
|
|
218
|
-
viewport: {
|
|
219
|
-
type: 'object',
|
|
220
|
-
description: 'Optional viewport hint used for iteration context.',
|
|
221
|
-
properties: {
|
|
222
|
-
width: { type: 'number' },
|
|
223
|
-
height: { type: 'number' },
|
|
224
|
-
},
|
|
225
|
-
additionalProperties: false,
|
|
226
|
-
},
|
|
227
|
-
target: {
|
|
228
|
-
type: 'array',
|
|
229
|
-
description: 'Optional target/reference images (path/url/base64). If omitted, the run\'s latest target-image is used.',
|
|
230
|
-
items: {
|
|
231
|
-
type: 'object',
|
|
232
|
-
properties: {
|
|
233
|
-
type: { type: 'string', enum: ['path', 'url', 'base64'] },
|
|
234
|
-
path: { type: 'string' },
|
|
235
|
-
url: { type: 'string' },
|
|
236
|
-
data: { type: 'string' },
|
|
237
|
-
mime: { type: 'string' },
|
|
238
|
-
filename: { type: 'string' },
|
|
239
|
-
},
|
|
240
|
-
required: ['type'],
|
|
241
|
-
additionalProperties: false,
|
|
242
|
-
},
|
|
243
|
-
maxItems: 4,
|
|
244
|
-
},
|
|
245
|
-
render: {
|
|
246
|
-
type: 'array',
|
|
247
|
-
description: 'Render/current screenshots to iterate on (path/url/base64).',
|
|
248
|
-
items: {
|
|
249
|
-
type: 'object',
|
|
250
|
-
properties: {
|
|
251
|
-
type: { type: 'string', enum: ['path', 'url', 'base64'] },
|
|
252
|
-
path: { type: 'string' },
|
|
253
|
-
url: { type: 'string' },
|
|
254
|
-
data: { type: 'string' },
|
|
255
|
-
mime: { type: 'string' },
|
|
256
|
-
filename: { type: 'string' },
|
|
257
|
-
},
|
|
258
|
-
required: ['type'],
|
|
259
|
-
additionalProperties: false,
|
|
260
|
-
},
|
|
261
|
-
maxItems: 4,
|
|
262
|
-
},
|
|
263
|
-
timeoutSeconds: { type: 'number', description: 'Max wait time for the iterate operation (default 900).' },
|
|
264
|
-
intervalSeconds: { type: 'number', description: 'Polling interval (default 5).' },
|
|
265
|
-
},
|
|
266
|
-
required: ['runId', 'render'],
|
|
267
|
-
additionalProperties: false,
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
name: 'design.refineDraft',
|
|
272
|
-
description: 'Refine a specific draft image within the same run by generating a new refined draft (non-fork). ' +
|
|
273
|
-
'Use this for quick, single-pass iteration without forking the run thread.',
|
|
109
|
+
name: 'design.instructions',
|
|
110
|
+
description: 'Return the Every Design MCP workflow instructions for gudiance on how to use these tools.',
|
|
274
111
|
inputSchema: {
|
|
275
112
|
type: 'object',
|
|
276
|
-
properties: {
|
|
277
|
-
runId: { type: 'string' },
|
|
278
|
-
artifactId: { type: 'string', description: 'Draft/refined draft (or target) artifact id to refine.' },
|
|
279
|
-
prompt: { type: 'string', description: 'Refinement instructions (what to change).', minLength: 1 },
|
|
280
|
-
},
|
|
281
|
-
required: ['runId', 'artifactId', 'prompt'],
|
|
282
|
-
additionalProperties: false,
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
name: 'design.uploadTarget',
|
|
287
|
-
description: 'Upload an image and set it as the run\'s explicit target selection (used by extract-assets and generate-html).',
|
|
288
|
-
inputSchema: {
|
|
289
|
-
type: 'object',
|
|
290
|
-
properties: {
|
|
291
|
-
runId: { type: 'string' },
|
|
292
|
-
sourceImage: {
|
|
293
|
-
type: 'object',
|
|
294
|
-
description: 'Image to upload (path/url/base64).',
|
|
295
|
-
properties: {
|
|
296
|
-
type: { type: 'string', enum: ['path', 'url', 'base64'] },
|
|
297
|
-
path: { type: 'string' },
|
|
298
|
-
url: { type: 'string' },
|
|
299
|
-
data: { type: 'string' },
|
|
300
|
-
mime: { type: 'string' },
|
|
301
|
-
filename: { type: 'string' },
|
|
302
|
-
},
|
|
303
|
-
required: ['type'],
|
|
304
|
-
additionalProperties: false,
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
required: ['runId', 'sourceImage'],
|
|
308
|
-
additionalProperties: false,
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
{
|
|
312
|
-
name: 'design.setTarget',
|
|
313
|
-
description: 'Select which draft image should be treated as the canonical target for this run (used by extract-assets and generate-html). ' +
|
|
314
|
-
'This persists in the run config until changed or cleared.',
|
|
315
|
-
inputSchema: {
|
|
316
|
-
type: 'object',
|
|
317
|
-
properties: {
|
|
318
|
-
runId: { type: 'string' },
|
|
319
|
-
artifactId: {
|
|
320
|
-
type: 'string',
|
|
321
|
-
description: 'Artifact id of a draft/refined draft image in this run.',
|
|
322
|
-
},
|
|
323
|
-
},
|
|
324
|
-
required: ['runId', 'artifactId'],
|
|
325
|
-
additionalProperties: false,
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
name: 'design.clearTarget',
|
|
330
|
-
description: 'Clear any explicit target selection for this run (reverts to default target selection behavior on subsequent progress actions).',
|
|
331
|
-
inputSchema: {
|
|
332
|
-
type: 'object',
|
|
333
|
-
properties: {
|
|
334
|
-
runId: { type: 'string' },
|
|
335
|
-
},
|
|
336
|
-
required: ['runId'],
|
|
337
|
-
additionalProperties: false,
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
name: 'design.events',
|
|
342
|
-
description: 'Fetch structured events/logs for a run (useful for debugging failures).',
|
|
343
|
-
inputSchema: {
|
|
344
|
-
type: 'object',
|
|
345
|
-
properties: {
|
|
346
|
-
runId: { type: 'string' },
|
|
347
|
-
},
|
|
348
|
-
required: ['runId'],
|
|
349
|
-
additionalProperties: false,
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
name: 'design.artifacts.list',
|
|
354
|
-
description: 'List artifacts for a run (e.g. png/webp/html/css/glb, previews, logs).',
|
|
355
|
-
inputSchema: {
|
|
356
|
-
type: 'object',
|
|
357
|
-
properties: {
|
|
358
|
-
runId: { type: 'string' },
|
|
359
|
-
},
|
|
360
|
-
required: ['runId'],
|
|
361
|
-
additionalProperties: false,
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
name: 'design.artifacts.download',
|
|
366
|
-
description: 'Download an artifact to a local cache directory and return the file path (for opening/viewing locally).',
|
|
367
|
-
inputSchema: {
|
|
368
|
-
type: 'object',
|
|
369
|
-
properties: {
|
|
370
|
-
runId: { type: 'string' },
|
|
371
|
-
artifactId: { type: 'string' },
|
|
372
|
-
},
|
|
373
|
-
required: ['runId', 'artifactId'],
|
|
113
|
+
properties: {},
|
|
374
114
|
additionalProperties: false,
|
|
375
115
|
},
|
|
376
116
|
},
|
|
@@ -385,117 +125,122 @@ export async function startMcpServer(options) {
|
|
|
385
125
|
const { prompt, config, parentRunId } = await buildCreateRunRequest(client, input ?? {});
|
|
386
126
|
const created = await client.createRun({ prompt, config, ...(parentRunId ? { parentRunId } : {}) });
|
|
387
127
|
const wrapped = wrap('design.create', created);
|
|
388
|
-
return { content: [{ type: 'text', text:
|
|
389
|
-
}
|
|
390
|
-
case 'design.list': {
|
|
391
|
-
const page = typeof input?.page === 'number' ? input.page : undefined;
|
|
392
|
-
const limit = typeof input?.limit === 'number' ? input.limit : undefined;
|
|
393
|
-
const runs = await client.listRuns({ page, limit });
|
|
394
|
-
const wrapped = wrap('design.list', runs);
|
|
395
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
396
|
-
}
|
|
397
|
-
case 'design.get': {
|
|
398
|
-
const runId = String(input?.runId ?? '').trim();
|
|
399
|
-
if (!runId)
|
|
400
|
-
throw new Error('Missing required argument: runId');
|
|
401
|
-
const run = await client.getRun(runId);
|
|
402
|
-
const wrapped = wrap('design.get', run, { runId });
|
|
403
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
128
|
+
return { content: [{ type: 'text', text: formatToolResponseMarkdown(wrapped) }] };
|
|
404
129
|
}
|
|
405
|
-
case 'design.
|
|
130
|
+
case 'design.sync': {
|
|
406
131
|
const runId = String(input?.runId ?? '').trim();
|
|
407
132
|
if (!runId)
|
|
408
133
|
throw new Error('Missing required argument: runId');
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
const
|
|
134
|
+
const syncDirInput = typeof input?.syncDir === 'string' ? input.syncDir.trim() : '';
|
|
135
|
+
const syncDir = syncDirInput || `./every-design/${runId}`;
|
|
136
|
+
const progressToken = request.params?.progressToken;
|
|
137
|
+
const hasProgressToken = progressToken !== null && progressToken !== undefined;
|
|
138
|
+
const progressLines = [];
|
|
139
|
+
const tracker = createSyncProgressTracker();
|
|
140
|
+
const emitProgress = (pct) => {
|
|
141
|
+
progressLines.push(`Progress: ${pct}%`);
|
|
142
|
+
if (!hasProgressToken)
|
|
143
|
+
return;
|
|
144
|
+
void server.notification({
|
|
145
|
+
method: 'notifications/progress',
|
|
146
|
+
params: {
|
|
147
|
+
progressToken,
|
|
148
|
+
progress: pct,
|
|
149
|
+
total: 100,
|
|
150
|
+
message: `Sync ${pct}%`,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
const initialPct = tracker.next(0);
|
|
155
|
+
if (initialPct !== null) {
|
|
156
|
+
emitProgress(initialPct);
|
|
157
|
+
}
|
|
414
158
|
const run = await watchRun(client, runId, {
|
|
415
|
-
timeoutSeconds,
|
|
416
|
-
intervalSeconds,
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
159
|
+
timeoutSeconds: 1800,
|
|
160
|
+
intervalSeconds: 5,
|
|
161
|
+
onUpdate: (snapshot) => {
|
|
162
|
+
const pct = getProgressPct(snapshot.run);
|
|
163
|
+
const mapped = tracker.next(pct);
|
|
164
|
+
if (mapped !== null) {
|
|
165
|
+
emitProgress(mapped);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
onExtractStart: () => {
|
|
169
|
+
const mapped = tracker.markExtractStart();
|
|
170
|
+
if (mapped !== null) {
|
|
171
|
+
emitProgress(mapped);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
syncDir,
|
|
175
|
+
ensureAssets: true,
|
|
421
176
|
});
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const runId = String(input?.runId ?? '').trim();
|
|
427
|
-
if (!runId)
|
|
428
|
-
throw new Error('Missing required argument: runId');
|
|
429
|
-
const targetArtifactId = typeof input?.targetArtifactId === 'string' ? input.targetArtifactId.trim() : '';
|
|
430
|
-
const designKind = typeof input?.designKind === 'string' ? input.designKind.trim().toLowerCase() : '';
|
|
431
|
-
const normalizedKind = designKind === 'html' ? 'interface' : designKind;
|
|
432
|
-
if (targetArtifactId) {
|
|
433
|
-
await client.setRunTarget(runId, targetArtifactId);
|
|
177
|
+
const finalPct = getProgressPct(run);
|
|
178
|
+
const mappedFinal = tracker.finalize(finalPct);
|
|
179
|
+
if (mappedFinal !== null) {
|
|
180
|
+
emitProgress(mappedFinal);
|
|
434
181
|
}
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
});
|
|
439
|
-
const wrapped = wrap('design.extractAssets', res, {
|
|
440
|
-
runId,
|
|
441
|
-
...(targetArtifactId ? { artifactId: targetArtifactId } : {}),
|
|
442
|
-
});
|
|
443
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
182
|
+
const payload = typeof run === 'object' && run ? { ...run, progress: progressLines } : { run, progress: progressLines };
|
|
183
|
+
const wrapped = wrap('design.sync', payload, { runId, syncDir });
|
|
184
|
+
return { content: [{ type: 'text', text: formatToolResponseMarkdown(wrapped) }] };
|
|
444
185
|
}
|
|
445
|
-
case 'design.
|
|
186
|
+
case 'design.check': {
|
|
446
187
|
const runId = String(input?.runId ?? '').trim();
|
|
447
188
|
if (!runId)
|
|
448
189
|
throw new Error('Missing required argument: runId');
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
190
|
+
const urlInput = typeof input?.url === 'string' ? input.url.trim() : '';
|
|
191
|
+
const pathInput = typeof input?.path === 'string' ? input.path.trim() : '';
|
|
192
|
+
if (!urlInput && !pathInput)
|
|
193
|
+
throw new Error('Missing required argument: url or path');
|
|
194
|
+
if (urlInput && pathInput)
|
|
195
|
+
throw new Error('Provide only one of url or path');
|
|
196
|
+
let targetUrl = urlInput;
|
|
197
|
+
if (pathInput) {
|
|
198
|
+
const resolved = path.resolve(pathInput);
|
|
199
|
+
let stat;
|
|
200
|
+
try {
|
|
201
|
+
stat = statSync(resolved);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
throw new Error(`File not found: ${resolved}`);
|
|
205
|
+
}
|
|
206
|
+
if (!stat.isFile()) {
|
|
207
|
+
throw new Error(`Path must be a file: ${resolved}`);
|
|
208
|
+
}
|
|
209
|
+
targetUrl = pathToFileURL(resolved).toString();
|
|
452
210
|
}
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
211
|
+
const screenshotOut = path.join(os.tmpdir(), `every-design-check-${runId}-${Date.now()}.png`);
|
|
212
|
+
const screenshot = await runScreenshotCommand({
|
|
213
|
+
url: targetUrl,
|
|
214
|
+
outPath: screenshotOut,
|
|
215
|
+
width: 1696,
|
|
216
|
+
height: 2528,
|
|
217
|
+
waitMs: 4000,
|
|
457
218
|
});
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
case 'design.iterate': {
|
|
461
|
-
const runId = String(input?.runId ?? '').trim();
|
|
462
|
-
if (!runId)
|
|
463
|
-
throw new Error('Missing required argument: runId');
|
|
464
|
-
const concerns = typeof input?.concerns === 'string' ? input.concerns.trim() : '';
|
|
465
|
-
const viewportInput = input?.viewport && typeof input.viewport === 'object' ? input.viewport : null;
|
|
466
|
-
const viewport = {
|
|
467
|
-
width: Number(viewportInput?.width) || 1696,
|
|
468
|
-
height: Number(viewportInput?.height) || 2528,
|
|
469
|
-
};
|
|
470
|
-
const render = Array.isArray(input?.render) ? input?.render : [];
|
|
471
|
-
if (render.length === 0)
|
|
472
|
-
throw new Error('Missing required argument: render (array of images)');
|
|
473
|
-
const target = Array.isArray(input?.target) ? input?.target : [];
|
|
474
|
-
const renderRefs = await client.uploadSourceImages(render);
|
|
475
|
-
const targetRefs = target.length > 0 ? await client.uploadSourceImages(target) : [];
|
|
219
|
+
const renderRefs = await client.uploadSourceImages([{ type: 'path', path: screenshot.screenshotPath }]);
|
|
476
220
|
const created = await client.createRunOperation(runId, {
|
|
477
221
|
type: 'iterate',
|
|
478
|
-
|
|
479
|
-
viewport,
|
|
222
|
+
viewport: { width: screenshot.width, height: screenshot.height },
|
|
480
223
|
render: renderRefs,
|
|
481
|
-
...(targetRefs.length > 0 ? { target: targetRefs } : {}),
|
|
482
224
|
});
|
|
483
225
|
const operationId = String(created?.operation?.id ?? '').trim();
|
|
484
226
|
if (!operationId)
|
|
485
227
|
throw new Error('Iterate operation created but missing operation id');
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
const deadlineMs = Date.now() + Math.max(1, timeoutSeconds) * 1000;
|
|
228
|
+
const deadlineMs = Date.now() + 900 * 1000;
|
|
229
|
+
let terminalStatus = '';
|
|
489
230
|
while (true) {
|
|
490
231
|
const op = await client.getOperation(operationId);
|
|
491
232
|
const status = String(op?.operation?.status ?? '').trim().toLowerCase();
|
|
492
233
|
if (status === 'completed' || status === 'failed' || status === 'cancelled') {
|
|
234
|
+
terminalStatus = status;
|
|
493
235
|
break;
|
|
494
236
|
}
|
|
495
237
|
if (Date.now() > deadlineMs) {
|
|
496
238
|
throw new Error(`Timed out waiting for iterate operation (${operationId})`);
|
|
497
239
|
}
|
|
498
|
-
await new Promise((r) => setTimeout(r,
|
|
240
|
+
await new Promise((r) => setTimeout(r, 5_000));
|
|
241
|
+
}
|
|
242
|
+
if (terminalStatus !== 'completed') {
|
|
243
|
+
throw new Error(`Iterate operation ${operationId} ended with status ${terminalStatus || 'unknown'}`);
|
|
499
244
|
}
|
|
500
245
|
const detail = await client.getRun(runId);
|
|
501
246
|
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
@@ -518,12 +263,17 @@ export async function startMcpServer(options) {
|
|
|
518
263
|
runId,
|
|
519
264
|
operationId,
|
|
520
265
|
status: 'completed',
|
|
266
|
+
screenshot: {
|
|
267
|
+
filePath: screenshot.screenshotPath,
|
|
268
|
+
width: screenshot.width,
|
|
269
|
+
height: screenshot.height,
|
|
270
|
+
},
|
|
521
271
|
iterate: null,
|
|
522
272
|
generatedAssets,
|
|
523
273
|
error: 'Missing critique artifact for iterate operation',
|
|
524
274
|
};
|
|
525
|
-
const wrapped = wrap('design.
|
|
526
|
-
return { content: [{ type: 'text', text:
|
|
275
|
+
const wrapped = wrap('design.check', payload, { runId });
|
|
276
|
+
return { content: [{ type: 'text', text: formatToolResponseMarkdown(wrapped) }] };
|
|
527
277
|
}
|
|
528
278
|
const url = typeof critiqueArtifact?.url === 'string' ? String(critiqueArtifact.url) : '';
|
|
529
279
|
if (!url)
|
|
@@ -539,103 +289,21 @@ export async function startMcpServer(options) {
|
|
|
539
289
|
}
|
|
540
290
|
const payload = {
|
|
541
291
|
runId,
|
|
542
|
-
|
|
292
|
+
screenshot: {
|
|
293
|
+
filePath: screenshot.screenshotPath,
|
|
294
|
+
width: screenshot.width,
|
|
295
|
+
height: screenshot.height,
|
|
296
|
+
},
|
|
543
297
|
iterate: parsed,
|
|
544
298
|
artifact: { id: critiqueArtifact.id, filePath: downloaded.filePath },
|
|
545
299
|
generatedAssets,
|
|
546
300
|
};
|
|
547
|
-
const wrapped = wrap('design.
|
|
548
|
-
return { content: [{ type: 'text', text:
|
|
549
|
-
}
|
|
550
|
-
case 'design.uploadTarget': {
|
|
551
|
-
const runId = String(input?.runId ?? '').trim();
|
|
552
|
-
if (!runId)
|
|
553
|
-
throw new Error('Missing required argument: runId');
|
|
554
|
-
const sourceImage = input?.sourceImage;
|
|
555
|
-
if (!sourceImage || typeof sourceImage !== 'object') {
|
|
556
|
-
throw new Error('Missing required argument: sourceImage');
|
|
557
|
-
}
|
|
558
|
-
const res = await client.uploadRunTarget(runId, sourceImage);
|
|
559
|
-
const wrapped = wrap('design.uploadTarget', res, { runId });
|
|
560
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
561
|
-
}
|
|
562
|
-
case 'design.refineDraft': {
|
|
563
|
-
const runId = String(input?.runId ?? '').trim();
|
|
564
|
-
const artifactId = String(input?.artifactId ?? '').trim();
|
|
565
|
-
const prompt = String(input?.prompt ?? '').trim();
|
|
566
|
-
if (!runId)
|
|
567
|
-
throw new Error('Missing required argument: runId');
|
|
568
|
-
if (!artifactId)
|
|
569
|
-
throw new Error('Missing required argument: artifactId');
|
|
570
|
-
if (!prompt)
|
|
571
|
-
throw new Error('Missing required argument: prompt');
|
|
572
|
-
const res = await client.refineDraft(runId, artifactId, prompt);
|
|
573
|
-
const wrapped = wrap('design.refineDraft', res, { runId, artifactId });
|
|
574
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
575
|
-
}
|
|
576
|
-
case 'design.setTarget': {
|
|
577
|
-
const runId = String(input?.runId ?? '').trim();
|
|
578
|
-
const artifactId = String(input?.artifactId ?? '').trim();
|
|
579
|
-
if (!runId)
|
|
580
|
-
throw new Error('Missing required argument: runId');
|
|
581
|
-
if (!artifactId)
|
|
582
|
-
throw new Error('Missing required argument: artifactId');
|
|
583
|
-
const res = await client.setRunTarget(runId, artifactId);
|
|
584
|
-
const wrapped = wrap('design.setTarget', res, { runId, artifactId });
|
|
585
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
586
|
-
}
|
|
587
|
-
case 'design.clearTarget': {
|
|
588
|
-
const runId = String(input?.runId ?? '').trim();
|
|
589
|
-
if (!runId)
|
|
590
|
-
throw new Error('Missing required argument: runId');
|
|
591
|
-
const res = await client.clearRunTarget(runId);
|
|
592
|
-
const wrapped = wrap('design.clearTarget', res, { runId });
|
|
593
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
594
|
-
}
|
|
595
|
-
case 'design.events': {
|
|
596
|
-
const runId = String(input?.runId ?? '').trim();
|
|
597
|
-
if (!runId)
|
|
598
|
-
throw new Error('Missing required argument: runId');
|
|
599
|
-
const events = await client.getRunEvents(runId);
|
|
600
|
-
const wrapped = wrap('design.events', events, { runId });
|
|
601
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
602
|
-
}
|
|
603
|
-
case 'design.artifacts.list': {
|
|
604
|
-
const runId = String(input?.runId ?? '').trim();
|
|
605
|
-
if (!runId)
|
|
606
|
-
throw new Error('Missing required argument: runId');
|
|
607
|
-
const detail = await client.getRun(runId);
|
|
608
|
-
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
609
|
-
const wrapped = wrap('design.artifacts.list', { artifacts: outputs }, { runId });
|
|
610
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
301
|
+
const wrapped = wrap('design.check', payload, { runId });
|
|
302
|
+
return { content: [{ type: 'text', text: formatToolResponseMarkdown(wrapped) }] };
|
|
611
303
|
}
|
|
612
|
-
case 'design.
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
if (!runId)
|
|
616
|
-
throw new Error('Missing required argument: runId');
|
|
617
|
-
if (!artifactId)
|
|
618
|
-
throw new Error('Missing required argument: artifactId');
|
|
619
|
-
const detail = await client.getRun(runId);
|
|
620
|
-
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
621
|
-
const found = outputs.find((entry) => String(entry?.id ?? '') === artifactId) || null;
|
|
622
|
-
const url = typeof found?.url === 'string' ? found.url : '';
|
|
623
|
-
if (!url)
|
|
624
|
-
throw new Error(`Artifact not found in standard outputs: ${artifactId}`);
|
|
625
|
-
const hint = (() => {
|
|
626
|
-
try {
|
|
627
|
-
const u = url.startsWith('http://') || url.startsWith('https://')
|
|
628
|
-
? new URL(url)
|
|
629
|
-
: new URL(url, 'https://example.invalid');
|
|
630
|
-
return u.pathname.split('/').filter(Boolean).pop() || undefined;
|
|
631
|
-
}
|
|
632
|
-
catch {
|
|
633
|
-
return url.split('/').filter(Boolean).pop() || undefined;
|
|
634
|
-
}
|
|
635
|
-
})();
|
|
636
|
-
const artifact = await client.downloadUrlToCache(runId, artifactId, url, { fileNameHint: hint });
|
|
637
|
-
const wrapped = wrap('design.artifacts.download', artifact, { runId, artifactId });
|
|
638
|
-
return { content: [{ type: 'text', text: pretty(wrapped) }] };
|
|
304
|
+
case 'design.instructions': {
|
|
305
|
+
const wrapped = wrap('design.instructions', { instructions: INSTRUCTIONS_MD });
|
|
306
|
+
return { content: [{ type: 'text', text: formatToolResponseMarkdown(wrapped) }] };
|
|
639
307
|
}
|
|
640
308
|
default:
|
|
641
309
|
throw new Error(`Unknown tool: ${tool}`);
|
|
@@ -649,9 +317,6 @@ export async function startMcpServer(options) {
|
|
|
649
317
|
const transport = new StdioServerTransport();
|
|
650
318
|
await server.connect(transport);
|
|
651
319
|
}
|
|
652
|
-
function pretty(value) {
|
|
653
|
-
return JSON.stringify(value, null, 2);
|
|
654
|
-
}
|
|
655
320
|
async function resolvePackageVersion() {
|
|
656
321
|
try {
|
|
657
322
|
const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
|