@zaai-dev/mcp 0.3.1 → 0.6.1
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/LICENSE +21 -21
- package/README.md +28 -13
- package/dist/bin/http.js +449 -37
- package/dist/bin/http.js.map +1 -1
- package/dist/bin/stdio.js +449 -37
- package/dist/bin/stdio.js.map +1 -1
- package/package.json +56 -56
package/dist/bin/stdio.js
CHANGED
|
@@ -10,7 +10,7 @@ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
// src/version.ts
|
|
13
|
-
var PKG_VERSION = true ? "0.
|
|
13
|
+
var PKG_VERSION = true ? "0.6.1" : "0.0.0-dev";
|
|
14
14
|
|
|
15
15
|
// src/tools/health.ts
|
|
16
16
|
var healthInputSchema = z.object({});
|
|
@@ -82,7 +82,8 @@ async function mcpApiFetch(config, path, init = {}) {
|
|
|
82
82
|
if (RETRYABLE_STATUSES.has(res.status) || res.status >= 502 && res.status <= 504) {
|
|
83
83
|
lastErr = err;
|
|
84
84
|
if (attempt < MAX_ATTEMPTS) {
|
|
85
|
-
|
|
85
|
+
const serverWait = res.status === 429 ? rateLimitWaitMs(res.headers) : null;
|
|
86
|
+
await sleep(serverWait ?? BASE_BACKOFF_MS * 2 ** (attempt - 1));
|
|
86
87
|
continue;
|
|
87
88
|
}
|
|
88
89
|
}
|
|
@@ -90,6 +91,25 @@ async function mcpApiFetch(config, path, init = {}) {
|
|
|
90
91
|
}
|
|
91
92
|
throw lastErr ?? new WorkspaceApiError(0, { message: "no attempts" }, path);
|
|
92
93
|
}
|
|
94
|
+
var MAX_RATE_LIMIT_WAIT_MS = 1e4;
|
|
95
|
+
function rateLimitWaitMs(headers) {
|
|
96
|
+
const retryAfter = headers.get("retry-after");
|
|
97
|
+
if (retryAfter) {
|
|
98
|
+
const secs = Number(retryAfter);
|
|
99
|
+
if (Number.isFinite(secs) && secs >= 0) {
|
|
100
|
+
return Math.min(secs * 1e3, MAX_RATE_LIMIT_WAIT_MS);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const reset = headers.get("x-ratelimit-reset");
|
|
104
|
+
if (reset) {
|
|
105
|
+
const resetEpoch = Number(reset);
|
|
106
|
+
if (Number.isFinite(resetEpoch)) {
|
|
107
|
+
const ms = resetEpoch * 1e3 - Date.now();
|
|
108
|
+
if (ms > 0) return Math.min(ms, MAX_RATE_LIMIT_WAIT_MS);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
93
113
|
function sleep(ms) {
|
|
94
114
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
115
|
}
|
|
@@ -128,7 +148,7 @@ var listCapturesOutputSchema = z3.object({
|
|
|
128
148
|
captures: z3.array(
|
|
129
149
|
z3.object({
|
|
130
150
|
id: z3.string().describe("Capture UUID. Pass to get_capture for the full payload."),
|
|
131
|
-
type: z3.enum(["page", "element", "composite"]).describe(
|
|
151
|
+
type: z3.enum(["page", "element", "composite", "recording"]).describe(
|
|
132
152
|
"page = full-page screenshot + DOM context; element = a single picked element; composite = multiple elements stacked."
|
|
133
153
|
),
|
|
134
154
|
sourceTitle: z3.string(),
|
|
@@ -160,7 +180,7 @@ var getCaptureInputSchema = z4.object({
|
|
|
160
180
|
});
|
|
161
181
|
var getCaptureOutputSchema = z4.object({
|
|
162
182
|
id: z4.string(),
|
|
163
|
-
type: z4.enum(["page", "element", "composite"]),
|
|
183
|
+
type: z4.enum(["page", "element", "composite", "recording"]),
|
|
164
184
|
projectId: z4.string(),
|
|
165
185
|
sourceTitle: z4.string(),
|
|
166
186
|
sourceUrl: z4.string(),
|
|
@@ -193,7 +213,7 @@ var searchCapturesOutputSchema = z5.object({
|
|
|
193
213
|
captures: z5.array(
|
|
194
214
|
z5.object({
|
|
195
215
|
id: z5.string(),
|
|
196
|
-
type: z5.enum(["page", "element", "composite"]),
|
|
216
|
+
type: z5.enum(["page", "element", "composite", "recording"]),
|
|
197
217
|
sourceTitle: z5.string(),
|
|
198
218
|
sourceUrl: z5.string(),
|
|
199
219
|
capturedAt: z5.string(),
|
|
@@ -217,7 +237,7 @@ var focusedGetterInputSchema = z6.object({
|
|
|
217
237
|
});
|
|
218
238
|
var focusedGetterOutputSchema = z6.object({
|
|
219
239
|
id: z6.string(),
|
|
220
|
-
type: z6.enum(["page", "element", "composite"]),
|
|
240
|
+
type: z6.enum(["page", "element", "composite", "recording"]),
|
|
221
241
|
field: z6.string(),
|
|
222
242
|
data: z6.unknown()
|
|
223
243
|
}).passthrough();
|
|
@@ -242,11 +262,19 @@ async function getMedia(store, args) {
|
|
|
242
262
|
return store.getField(args.id, "media");
|
|
243
263
|
}
|
|
244
264
|
|
|
265
|
+
// src/tools/get-structure.ts
|
|
266
|
+
async function getStructure(store, args) {
|
|
267
|
+
return store.getField(args.id, "structure");
|
|
268
|
+
}
|
|
269
|
+
|
|
245
270
|
// src/tools/get-brand-brief.ts
|
|
246
271
|
import { z as z7 } from "zod";
|
|
247
272
|
var getBrandBriefInputSchema = z7.object({
|
|
248
273
|
project_id: z7.string().uuid().describe(
|
|
249
|
-
"Zaai Dev project UUID. Find via the workspace at
|
|
274
|
+
"Zaai Dev project UUID. Find via the workspace at zaaidev.com/dev/projects."
|
|
275
|
+
),
|
|
276
|
+
brief_id: z7.string().uuid().optional().describe(
|
|
277
|
+
"Target a specific brief by id (from list_briefs). Omit to read the project's brand-identity brief."
|
|
250
278
|
)
|
|
251
279
|
});
|
|
252
280
|
var getBrandBriefOutputSchema = z7.object({
|
|
@@ -281,13 +309,16 @@ var getBrandBriefOutputSchema = z7.object({
|
|
|
281
309
|
updated_at: z7.string()
|
|
282
310
|
}).passthrough();
|
|
283
311
|
async function getBrandBrief(store, args) {
|
|
284
|
-
return store.getBrandBrief(args.project_id);
|
|
312
|
+
return store.getBrandBrief(args.project_id, args.brief_id);
|
|
285
313
|
}
|
|
286
314
|
|
|
287
315
|
// src/tools/get-voice.ts
|
|
288
316
|
import { z as z8 } from "zod";
|
|
289
317
|
var getVoiceInputSchema = z8.object({
|
|
290
|
-
project_id: z8.string().uuid().describe("Zaai Dev project UUID.")
|
|
318
|
+
project_id: z8.string().uuid().describe("Zaai Dev project UUID."),
|
|
319
|
+
brief_id: z8.string().uuid().optional().describe(
|
|
320
|
+
"Target a specific brief by id (from list_briefs). Omit to read the project's brand-identity brief."
|
|
321
|
+
)
|
|
291
322
|
});
|
|
292
323
|
var getVoiceOutputSchema = z8.object({
|
|
293
324
|
project_id: z8.string(),
|
|
@@ -298,13 +329,16 @@ var getVoiceOutputSchema = z8.object({
|
|
|
298
329
|
example_phrases: z8.array(z8.string()).describe("Concrete phrases that exemplify the voice.")
|
|
299
330
|
}).passthrough();
|
|
300
331
|
async function getVoice(store, args) {
|
|
301
|
-
return store.getVoice(args.project_id);
|
|
332
|
+
return store.getVoice(args.project_id, args.brief_id);
|
|
302
333
|
}
|
|
303
334
|
|
|
304
335
|
// src/tools/get-audience.ts
|
|
305
336
|
import { z as z9 } from "zod";
|
|
306
337
|
var getAudienceInputSchema = z9.object({
|
|
307
|
-
project_id: z9.string().uuid().describe("Zaai Dev project UUID.")
|
|
338
|
+
project_id: z9.string().uuid().describe("Zaai Dev project UUID."),
|
|
339
|
+
brief_id: z9.string().uuid().optional().describe(
|
|
340
|
+
"Target a specific brief by id (from list_briefs). Omit to read the project's brand-identity brief."
|
|
341
|
+
)
|
|
308
342
|
});
|
|
309
343
|
var getAudienceOutputSchema = z9.object({
|
|
310
344
|
project_id: z9.string(),
|
|
@@ -314,13 +348,16 @@ var getAudienceOutputSchema = z9.object({
|
|
|
314
348
|
channels: z9.array(z9.string()).describe("Where this audience consumes content.")
|
|
315
349
|
}).passthrough();
|
|
316
350
|
async function getAudience(store, args) {
|
|
317
|
-
return store.getAudience(args.project_id);
|
|
351
|
+
return store.getAudience(args.project_id, args.brief_id);
|
|
318
352
|
}
|
|
319
353
|
|
|
320
354
|
// src/tools/get-design-intent.ts
|
|
321
355
|
import { z as z10 } from "zod";
|
|
322
356
|
var getDesignIntentInputSchema = z10.object({
|
|
323
|
-
project_id: z10.string().uuid().describe("Zaai Dev project UUID.")
|
|
357
|
+
project_id: z10.string().uuid().describe("Zaai Dev project UUID."),
|
|
358
|
+
brief_id: z10.string().uuid().optional().describe(
|
|
359
|
+
"Target a specific brief by id (from list_briefs). Omit to read the project's brand-identity brief."
|
|
360
|
+
)
|
|
324
361
|
});
|
|
325
362
|
var getDesignIntentOutputSchema = z10.object({
|
|
326
363
|
project_id: z10.string(),
|
|
@@ -331,13 +368,16 @@ var getDesignIntentOutputSchema = z10.object({
|
|
|
331
368
|
inspiration_summary: z10.string().describe("Free-form summary of the inspiration the brief points at.")
|
|
332
369
|
}).passthrough();
|
|
333
370
|
async function getDesignIntent(store, args) {
|
|
334
|
-
return store.getDesignIntent(args.project_id);
|
|
371
|
+
return store.getDesignIntent(args.project_id, args.brief_id);
|
|
335
372
|
}
|
|
336
373
|
|
|
337
374
|
// src/tools/get-brand-tokens.ts
|
|
338
375
|
import { z as z11 } from "zod";
|
|
339
376
|
var getBrandTokensInputSchema = z11.object({
|
|
340
|
-
project_id: z11.string().uuid().describe("Zaai Dev project UUID.")
|
|
377
|
+
project_id: z11.string().uuid().describe("Zaai Dev project UUID."),
|
|
378
|
+
brief_id: z11.string().uuid().optional().describe(
|
|
379
|
+
"Target a specific brief by id (from list_briefs). Omit to read the project's brand-identity brief."
|
|
380
|
+
)
|
|
341
381
|
});
|
|
342
382
|
var getBrandTokensOutputSchema = z11.object({
|
|
343
383
|
project_id: z11.string(),
|
|
@@ -359,7 +399,7 @@ var getBrandTokensOutputSchema = z11.object({
|
|
|
359
399
|
shadows: z11.array(z11.object({ name: z11.string(), value: z11.string() }))
|
|
360
400
|
}).passthrough();
|
|
361
401
|
async function getBrandTokens(store, args) {
|
|
362
|
-
return store.getBrandTokens(args.project_id);
|
|
402
|
+
return store.getBrandTokens(args.project_id, args.brief_id);
|
|
363
403
|
}
|
|
364
404
|
|
|
365
405
|
// src/tools/get-decisions.ts
|
|
@@ -397,7 +437,7 @@ var getReferencesInputSchema = z13.object({
|
|
|
397
437
|
});
|
|
398
438
|
var referenceItem = z13.object({
|
|
399
439
|
id: z13.string(),
|
|
400
|
-
type: z13.enum(["page", "element", "composite"]),
|
|
440
|
+
type: z13.enum(["page", "element", "composite", "recording"]),
|
|
401
441
|
source_url: z13.string(),
|
|
402
442
|
source_title: z13.string(),
|
|
403
443
|
note: z13.string().nullable(),
|
|
@@ -426,7 +466,7 @@ var searchReferencesInputSchema = z14.object({
|
|
|
426
466
|
});
|
|
427
467
|
var searchReferenceItem = z14.object({
|
|
428
468
|
id: z14.string(),
|
|
429
|
-
type: z14.enum(["page", "element", "composite"]),
|
|
469
|
+
type: z14.enum(["page", "element", "composite", "recording"]),
|
|
430
470
|
source_url: z14.string(),
|
|
431
471
|
source_title: z14.string(),
|
|
432
472
|
note: z14.string().nullable(),
|
|
@@ -448,6 +488,164 @@ async function searchReferences(store, args) {
|
|
|
448
488
|
});
|
|
449
489
|
}
|
|
450
490
|
|
|
491
|
+
// src/tools/list-briefs.ts
|
|
492
|
+
import { z as z15 } from "zod";
|
|
493
|
+
var listBriefsInputSchema = z15.object({
|
|
494
|
+
project_id: z15.string().uuid().describe("Zaai Dev project UUID.")
|
|
495
|
+
});
|
|
496
|
+
var briefTypeEnum = z15.enum([
|
|
497
|
+
"brand_identity",
|
|
498
|
+
"web_site",
|
|
499
|
+
"layout_page",
|
|
500
|
+
"component",
|
|
501
|
+
"campaign",
|
|
502
|
+
"naming"
|
|
503
|
+
]);
|
|
504
|
+
var briefStatusEnum = z15.enum([
|
|
505
|
+
"to_draft",
|
|
506
|
+
"drafting",
|
|
507
|
+
"synthesised",
|
|
508
|
+
"in_review",
|
|
509
|
+
"changes_requested",
|
|
510
|
+
"approved"
|
|
511
|
+
]);
|
|
512
|
+
var listBriefsItem = z15.object({
|
|
513
|
+
id: z15.string(),
|
|
514
|
+
brief_type: briefTypeEnum,
|
|
515
|
+
name: z15.string().nullable(),
|
|
516
|
+
display_name: z15.string(),
|
|
517
|
+
status: briefStatusEnum,
|
|
518
|
+
current_version: z15.object({ version: z15.number().int(), created_at: z15.string() }).nullable(),
|
|
519
|
+
created_at: z15.string(),
|
|
520
|
+
updated_at: z15.string()
|
|
521
|
+
}).passthrough();
|
|
522
|
+
var listBriefsOutputSchema = z15.object({
|
|
523
|
+
project_id: z15.string(),
|
|
524
|
+
items: z15.array(listBriefsItem)
|
|
525
|
+
}).passthrough();
|
|
526
|
+
async function listBriefs(store, args) {
|
|
527
|
+
return store.listBriefs(args.project_id);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/tools/list-docs.ts
|
|
531
|
+
var listDocsInputSchema = listBriefsInputSchema;
|
|
532
|
+
var listDocsOutputSchema = listBriefsOutputSchema;
|
|
533
|
+
async function listDocs(store, args) {
|
|
534
|
+
return store.listDocs(args.project_id);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/tools/get-doc.ts
|
|
538
|
+
import { z as z16 } from "zod";
|
|
539
|
+
var getDocInputSchema = z16.object({
|
|
540
|
+
project_id: z16.string().uuid().describe("Zaai Dev project UUID."),
|
|
541
|
+
doc_id: z16.string().uuid().optional().describe(
|
|
542
|
+
"Target a specific doc by id (from list_docs). Omit to read the project's brand-identity doc."
|
|
543
|
+
)
|
|
544
|
+
});
|
|
545
|
+
var briefStatusEnum2 = z16.enum([
|
|
546
|
+
"to_draft",
|
|
547
|
+
"drafting",
|
|
548
|
+
"synthesised",
|
|
549
|
+
"in_review",
|
|
550
|
+
"changes_requested",
|
|
551
|
+
"approved"
|
|
552
|
+
]);
|
|
553
|
+
var docBlock = z16.object({
|
|
554
|
+
type: z16.enum(["text", "capture", "image", "divider"]),
|
|
555
|
+
markdown: z16.string().optional(),
|
|
556
|
+
capture_id: z16.string().nullable().optional(),
|
|
557
|
+
role: z16.string().optional(),
|
|
558
|
+
treatment: z16.string().optional(),
|
|
559
|
+
note: z16.string().nullable().optional(),
|
|
560
|
+
src: z16.string().optional()
|
|
561
|
+
}).passthrough();
|
|
562
|
+
var getDocOutputSchema = z16.object({
|
|
563
|
+
project_id: z16.string(),
|
|
564
|
+
doc_id: z16.string(),
|
|
565
|
+
kind: z16.literal("block-doc"),
|
|
566
|
+
name: z16.string(),
|
|
567
|
+
version: z16.number().int(),
|
|
568
|
+
status: briefStatusEnum2,
|
|
569
|
+
approval: z16.object({ state: z16.string(), version: z16.number().int().nullable() }).nullable().describe("Sign-off signal: whether the doc is an approved spec or a draft."),
|
|
570
|
+
blocks: z16.array(docBlock),
|
|
571
|
+
references: z16.array(z16.object({}).passthrough())
|
|
572
|
+
}).passthrough();
|
|
573
|
+
async function getDoc(store, args) {
|
|
574
|
+
return store.getDoc(args.project_id, args.doc_id);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/tools/get-project-bundle.ts
|
|
578
|
+
import { z as z17 } from "zod";
|
|
579
|
+
var getProjectBundleInputSchema = z17.object({
|
|
580
|
+
project_id: z17.string().uuid().describe("Zaai Dev project UUID.")
|
|
581
|
+
});
|
|
582
|
+
var getProjectBundleOutputSchema = z17.object({
|
|
583
|
+
project_id: z17.string(),
|
|
584
|
+
project_name: z17.string(),
|
|
585
|
+
docs: z17.array(getDocOutputSchema),
|
|
586
|
+
captures: z17.array(
|
|
587
|
+
z17.object({
|
|
588
|
+
id: z17.string(),
|
|
589
|
+
title: z17.string(),
|
|
590
|
+
type: z17.string(),
|
|
591
|
+
palette: z17.array(z17.string()),
|
|
592
|
+
vibe: z17.array(z17.string())
|
|
593
|
+
}).passthrough()
|
|
594
|
+
)
|
|
595
|
+
}).passthrough();
|
|
596
|
+
async function getProjectBundle(store, args) {
|
|
597
|
+
return store.getProjectBundle(args.project_id);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/tools/add-reference.ts
|
|
601
|
+
import { z as z18 } from "zod";
|
|
602
|
+
var addReferenceInputSchema = z18.object({
|
|
603
|
+
project_id: z18.string().uuid().describe("Zaai Dev project UUID."),
|
|
604
|
+
source_url: z18.string().url().describe("URL of the page/element being referenced."),
|
|
605
|
+
source_title: z18.string().min(1).max(500).describe("Human-readable title for the reference."),
|
|
606
|
+
note: z18.string().max(5e3).nullable().optional().describe("Optional note explaining why this reference matters."),
|
|
607
|
+
tags: z18.array(z18.string().min(1).max(60)).max(50).optional().describe("Optional tags to organise the reference.")
|
|
608
|
+
});
|
|
609
|
+
var addReferenceOutputSchema = z18.object({
|
|
610
|
+
id: z18.string(),
|
|
611
|
+
project_id: z18.string(),
|
|
612
|
+
source_url: z18.string(),
|
|
613
|
+
source_title: z18.string(),
|
|
614
|
+
source_kind: z18.enum(["extension", "mcp"]).describe("Origin of the reference. MCP-added references are 'mcp'."),
|
|
615
|
+
captured_at: z18.string()
|
|
616
|
+
}).passthrough();
|
|
617
|
+
async function addReference(store, args) {
|
|
618
|
+
return store.addReference(args.project_id, {
|
|
619
|
+
source_url: args.source_url,
|
|
620
|
+
source_title: args.source_title,
|
|
621
|
+
note: args.note,
|
|
622
|
+
tags: args.tags
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/tools/log-decision.ts
|
|
627
|
+
import { z as z19 } from "zod";
|
|
628
|
+
var logDecisionInputSchema = z19.object({
|
|
629
|
+
project_id: z19.string().uuid().describe("Zaai Dev project UUID."),
|
|
630
|
+
title: z19.string().min(1).max(200).describe("What was decided (one line)."),
|
|
631
|
+
rationale: z19.string().max(5e3).nullable().optional().describe("Optional reasoning behind the decision."),
|
|
632
|
+
brief_field: z19.string().max(100).nullable().optional().describe("Optional brief slice this decision concerns (e.g. 'voice.tone').")
|
|
633
|
+
});
|
|
634
|
+
var logDecisionOutputSchema = z19.object({
|
|
635
|
+
id: z19.string(),
|
|
636
|
+
project_id: z19.string(),
|
|
637
|
+
title: z19.string(),
|
|
638
|
+
brief_field: z19.string().nullable(),
|
|
639
|
+
created_at: z19.string()
|
|
640
|
+
}).passthrough();
|
|
641
|
+
async function logDecision(store, args) {
|
|
642
|
+
return store.logDecision(args.project_id, {
|
|
643
|
+
title: args.title,
|
|
644
|
+
rationale: args.rationale,
|
|
645
|
+
brief_field: args.brief_field
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
451
649
|
// src/store/http-store.ts
|
|
452
650
|
var HttpCaptureStore = class {
|
|
453
651
|
constructor(config) {
|
|
@@ -480,39 +678,45 @@ var HttpCaptureStore = class {
|
|
|
480
678
|
};
|
|
481
679
|
|
|
482
680
|
// src/store/project-store.ts
|
|
681
|
+
function briefIdQuery(briefId) {
|
|
682
|
+
if (!briefId) return "";
|
|
683
|
+
const sp = new URLSearchParams();
|
|
684
|
+
sp.set("brief_id", briefId);
|
|
685
|
+
return `?${sp.toString()}`;
|
|
686
|
+
}
|
|
483
687
|
var HttpProjectStore = class {
|
|
484
688
|
constructor(config) {
|
|
485
689
|
this.config = config;
|
|
486
690
|
}
|
|
487
691
|
config;
|
|
488
|
-
async getBrandBrief(projectId) {
|
|
692
|
+
async getBrandBrief(projectId, briefId) {
|
|
489
693
|
return mcpApiFetch(
|
|
490
694
|
this.config,
|
|
491
|
-
`/api/mcp/projects/${encodeURIComponent(projectId)}/brief`
|
|
695
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/brief${briefIdQuery(briefId)}`
|
|
492
696
|
);
|
|
493
697
|
}
|
|
494
|
-
async getVoice(projectId) {
|
|
698
|
+
async getVoice(projectId, briefId) {
|
|
495
699
|
return mcpApiFetch(
|
|
496
700
|
this.config,
|
|
497
|
-
`/api/mcp/projects/${encodeURIComponent(projectId)}/voice`
|
|
701
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/voice${briefIdQuery(briefId)}`
|
|
498
702
|
);
|
|
499
703
|
}
|
|
500
|
-
async getAudience(projectId) {
|
|
704
|
+
async getAudience(projectId, briefId) {
|
|
501
705
|
return mcpApiFetch(
|
|
502
706
|
this.config,
|
|
503
|
-
`/api/mcp/projects/${encodeURIComponent(projectId)}/audience`
|
|
707
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/audience${briefIdQuery(briefId)}`
|
|
504
708
|
);
|
|
505
709
|
}
|
|
506
|
-
async getDesignIntent(projectId) {
|
|
710
|
+
async getDesignIntent(projectId, briefId) {
|
|
507
711
|
return mcpApiFetch(
|
|
508
712
|
this.config,
|
|
509
|
-
`/api/mcp/projects/${encodeURIComponent(projectId)}/design-intent`
|
|
713
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/design-intent${briefIdQuery(briefId)}`
|
|
510
714
|
);
|
|
511
715
|
}
|
|
512
|
-
async getBrandTokens(projectId) {
|
|
716
|
+
async getBrandTokens(projectId, briefId) {
|
|
513
717
|
return mcpApiFetch(
|
|
514
718
|
this.config,
|
|
515
|
-
`/api/mcp/projects/${encodeURIComponent(projectId)}/brand-tokens`
|
|
719
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/brand-tokens${briefIdQuery(briefId)}`
|
|
516
720
|
);
|
|
517
721
|
}
|
|
518
722
|
async getDecisions(projectId, limit) {
|
|
@@ -543,6 +747,54 @@ var HttpProjectStore = class {
|
|
|
543
747
|
`/api/mcp/projects/${encodeURIComponent(projectId)}/references/search?${sp.toString()}`
|
|
544
748
|
);
|
|
545
749
|
}
|
|
750
|
+
// ── v1.2 / v1.3 / v1.4 ──────────────────────────────────────
|
|
751
|
+
async listBriefs(projectId) {
|
|
752
|
+
return mcpApiFetch(
|
|
753
|
+
this.config,
|
|
754
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/briefs`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
async listDocs(projectId) {
|
|
758
|
+
return mcpApiFetch(
|
|
759
|
+
this.config,
|
|
760
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/docs`
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
async getDoc(projectId, docId) {
|
|
764
|
+
const seg = docId ? encodeURIComponent(docId) : "default";
|
|
765
|
+
return mcpApiFetch(
|
|
766
|
+
this.config,
|
|
767
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/docs/${seg}`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
async getProjectBundle(projectId) {
|
|
771
|
+
return mcpApiFetch(
|
|
772
|
+
this.config,
|
|
773
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/bundle`
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
async addReference(projectId, args) {
|
|
777
|
+
return mcpApiFetch(
|
|
778
|
+
this.config,
|
|
779
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/references`,
|
|
780
|
+
{
|
|
781
|
+
method: "POST",
|
|
782
|
+
headers: { "Content-Type": "application/json" },
|
|
783
|
+
body: JSON.stringify(args)
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
async logDecision(projectId, args) {
|
|
788
|
+
return mcpApiFetch(
|
|
789
|
+
this.config,
|
|
790
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/decisions`,
|
|
791
|
+
{
|
|
792
|
+
method: "POST",
|
|
793
|
+
headers: { "Content-Type": "application/json" },
|
|
794
|
+
body: JSON.stringify(args)
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
}
|
|
546
798
|
};
|
|
547
799
|
|
|
548
800
|
// src/resources/capture-resource.ts
|
|
@@ -777,6 +1029,16 @@ function createServer(config) {
|
|
|
777
1029
|
},
|
|
778
1030
|
makeFocusedGetterHandler(captureStore, getMedia, "media")
|
|
779
1031
|
);
|
|
1032
|
+
server.registerTool(
|
|
1033
|
+
"get_structure",
|
|
1034
|
+
{
|
|
1035
|
+
title: "Get capture structure map",
|
|
1036
|
+
description: "Returns just the Layer-Explorer teardown \u2014 the 'structure map' (BUILD-PLAN \xA76.6): a flat list of the captured subtree's nodes, each with depth, a root-relative rect, short selector, inferred component, headline computed style (display/background/color/radius/font), and flags (flex/grid, animated, brandToken, a11yIssue, hidden). Page \u2192 main-content subtree; element \u2192 the captured element's subtree; composite \u2192 one map per picked element. Use this to understand HOW a reference is built before an on-brand rebuild, without loading full HTML + computed style. Returns structure:null on pre-\xA76.6 captures. Costs 1 credit.",
|
|
1037
|
+
inputSchema: focusedGetterInputSchema.shape,
|
|
1038
|
+
outputSchema: focusedGetterOutputSchema.shape
|
|
1039
|
+
},
|
|
1040
|
+
makeFocusedGetterHandler(captureStore, getStructure, "structure")
|
|
1041
|
+
);
|
|
780
1042
|
server.registerTool(
|
|
781
1043
|
"get_brand_brief",
|
|
782
1044
|
{
|
|
@@ -889,6 +1151,90 @@ function createServer(config) {
|
|
|
889
1151
|
(r) => r.items.length === 0 ? `No references match "${r.query}".` : `${r.items.length} reference${r.items.length === 1 ? "" : "s"} matching "${r.query}".`
|
|
890
1152
|
)
|
|
891
1153
|
);
|
|
1154
|
+
server.registerTool(
|
|
1155
|
+
"list_briefs",
|
|
1156
|
+
{
|
|
1157
|
+
title: "List briefs",
|
|
1158
|
+
description: "List every brief on a project \u2014 id, type, name, status, and current-version metadata. Call this first when working with a project so you know what briefs exist; then pass `brief_id` into the targeted read tools (get_brand_brief / get_voice / get_audience / get_design_intent / get_brand_tokens) to target a non-default brief. Returns oldest-first by created_at. Costs 1 credit.",
|
|
1159
|
+
inputSchema: listBriefsInputSchema.shape,
|
|
1160
|
+
outputSchema: listBriefsOutputSchema.shape
|
|
1161
|
+
},
|
|
1162
|
+
makeProjectToolHandler(
|
|
1163
|
+
projectStore,
|
|
1164
|
+
listBriefs,
|
|
1165
|
+
(r) => `${r.items.length} brief${r.items.length === 1 ? "" : "s"} on project ${r.project_id.slice(0, 8)}\u2026 (oldest first).`
|
|
1166
|
+
)
|
|
1167
|
+
);
|
|
1168
|
+
server.registerTool(
|
|
1169
|
+
"list_docs",
|
|
1170
|
+
{
|
|
1171
|
+
title: "List docs",
|
|
1172
|
+
description: "List every doc on a project \u2014 id, type, name, status, and current-version metadata. Friendly alias of list_briefs. Call first, then pass `doc_id` into get_doc. Returns oldest-first. Costs 1 credit.",
|
|
1173
|
+
inputSchema: listDocsInputSchema.shape,
|
|
1174
|
+
outputSchema: listDocsOutputSchema.shape
|
|
1175
|
+
},
|
|
1176
|
+
makeProjectToolHandler(
|
|
1177
|
+
projectStore,
|
|
1178
|
+
listDocs,
|
|
1179
|
+
(r) => `${r.items.length} doc${r.items.length === 1 ? "" : "s"} on project ${r.project_id.slice(0, 8)}\u2026 (oldest first).`
|
|
1180
|
+
)
|
|
1181
|
+
);
|
|
1182
|
+
server.registerTool(
|
|
1183
|
+
"get_doc",
|
|
1184
|
+
{
|
|
1185
|
+
title: "Get doc",
|
|
1186
|
+
description: "Read a doc as an agent-ready block list: text blocks as markdown, capture blocks as structured references with role / treatment / note and full capture metadata (palette, typography, vibe, inferred component) plus the captured `code` (element outerHTML + computed CSS + build tokens like bg/color/radius/padding/font/shadow/border) so you build from real values, not a screenshot guess. Includes the sign-off signal (approval state + version) so you know whether you're building from an approved spec or a draft. Omit doc_id for the project's brand-identity doc. Costs 1 credit.",
|
|
1187
|
+
inputSchema: getDocInputSchema.shape,
|
|
1188
|
+
outputSchema: getDocOutputSchema.shape
|
|
1189
|
+
},
|
|
1190
|
+
makeProjectToolHandler(
|
|
1191
|
+
projectStore,
|
|
1192
|
+
getDoc,
|
|
1193
|
+
(r) => `Doc "${r.name}" v${r.version} (${r.status})` + (r.approval ? `, approval: ${r.approval.state}` : "") + `, ${r.blocks.length} block${r.blocks.length === 1 ? "" : "s"}.`
|
|
1194
|
+
)
|
|
1195
|
+
);
|
|
1196
|
+
server.registerTool(
|
|
1197
|
+
"get_project_bundle",
|
|
1198
|
+
{
|
|
1199
|
+
title: "Get project bundle",
|
|
1200
|
+
description: "Fetch the whole project as one agent-ready bundle: every doc's block list (text + roled capture references + approval state) plus the project's capture library. Use this to load full project context up front in a single call. Can be large. Costs 1 credit.",
|
|
1201
|
+
inputSchema: getProjectBundleInputSchema.shape,
|
|
1202
|
+
outputSchema: getProjectBundleOutputSchema.shape
|
|
1203
|
+
},
|
|
1204
|
+
makeProjectToolHandler(
|
|
1205
|
+
projectStore,
|
|
1206
|
+
getProjectBundle,
|
|
1207
|
+
(r) => `Bundle for "${r.project_name}": ${r.docs.length} doc${r.docs.length === 1 ? "" : "s"}, ${r.captures.length} capture${r.captures.length === 1 ? "" : "s"}.`
|
|
1208
|
+
)
|
|
1209
|
+
);
|
|
1210
|
+
server.registerTool(
|
|
1211
|
+
"add_reference",
|
|
1212
|
+
{
|
|
1213
|
+
title: "Add reference",
|
|
1214
|
+
description: "Add a URL reference to a project's library \u2014 for when you've found a relevant page and want to save it. No screenshot required; stored with source_kind='mcp' so the UI distinguishes it from extension-pushed captures. Use when discovering inspiration, never to bulk-import URLs the user already has. Costs 5 credits.",
|
|
1215
|
+
inputSchema: addReferenceInputSchema.shape,
|
|
1216
|
+
outputSchema: addReferenceOutputSchema.shape
|
|
1217
|
+
},
|
|
1218
|
+
makeProjectToolHandler(
|
|
1219
|
+
projectStore,
|
|
1220
|
+
addReference,
|
|
1221
|
+
(r) => `Added reference "${r.source_title}" (${r.source_kind}) to project ${r.project_id.slice(0, 8)}\u2026.`
|
|
1222
|
+
)
|
|
1223
|
+
);
|
|
1224
|
+
server.registerTool(
|
|
1225
|
+
"log_decision",
|
|
1226
|
+
{
|
|
1227
|
+
title: "Log decision",
|
|
1228
|
+
description: "Append a decision to a project's decisions log: what was decided, optionally why, optionally which brief slice it concerns (e.g. 'voice.tone'). Use this when you make or recommend a brand- or design-relevant choice that future you should trace back. Costs 1 credit.",
|
|
1229
|
+
inputSchema: logDecisionInputSchema.shape,
|
|
1230
|
+
outputSchema: logDecisionOutputSchema.shape
|
|
1231
|
+
},
|
|
1232
|
+
makeProjectToolHandler(
|
|
1233
|
+
projectStore,
|
|
1234
|
+
logDecision,
|
|
1235
|
+
(r) => `Logged decision "${r.title}"` + (r.brief_field ? ` (${r.brief_field})` : "") + `.`
|
|
1236
|
+
)
|
|
1237
|
+
);
|
|
892
1238
|
registerCaptureResource(server, captureStore);
|
|
893
1239
|
info("server initialized", {
|
|
894
1240
|
version: PKG_VERSION,
|
|
@@ -902,6 +1248,7 @@ function createServer(config) {
|
|
|
902
1248
|
"get_html",
|
|
903
1249
|
"get_animation",
|
|
904
1250
|
"get_media",
|
|
1251
|
+
"get_structure",
|
|
905
1252
|
"get_brand_brief",
|
|
906
1253
|
"get_voice",
|
|
907
1254
|
"get_audience",
|
|
@@ -909,7 +1256,13 @@ function createServer(config) {
|
|
|
909
1256
|
"get_brand_tokens",
|
|
910
1257
|
"get_decisions",
|
|
911
1258
|
"get_references",
|
|
912
|
-
"search_references"
|
|
1259
|
+
"search_references",
|
|
1260
|
+
"list_briefs",
|
|
1261
|
+
"list_docs",
|
|
1262
|
+
"get_doc",
|
|
1263
|
+
"get_project_bundle",
|
|
1264
|
+
"add_reference",
|
|
1265
|
+
"log_decision"
|
|
913
1266
|
],
|
|
914
1267
|
resources: ["zaai-capture://{id}"]
|
|
915
1268
|
});
|
|
@@ -952,27 +1305,61 @@ function summariseWhoami(r) {
|
|
|
952
1305
|
}
|
|
953
1306
|
function toolErrorResponse(err) {
|
|
954
1307
|
if (err instanceof WorkspaceApiError) {
|
|
1308
|
+
const cls = errorClassOf(err.body);
|
|
1309
|
+
const detail = describe(err.body);
|
|
955
1310
|
switch (err.status) {
|
|
956
1311
|
case 401:
|
|
957
1312
|
return errorContent(
|
|
958
|
-
"Authentication failed. Your ZAAI_API_TOKEN is invalid, expired, or revoked. Mint a new MCP token at https://
|
|
1313
|
+
"Authentication failed. Your ZAAI_API_TOKEN is invalid, expired, or revoked. Mint a new MCP token at https://zaaidev.com/dev/settings/tokens and update your MCP client config."
|
|
959
1314
|
);
|
|
960
1315
|
case 402:
|
|
1316
|
+
if (cls === "SubscriptionRequired") {
|
|
1317
|
+
return errorContent(
|
|
1318
|
+
"Zaai Dev needs an active subscription or trial to use the MCP. Start one (14-day free trial, no card required) at https://zaaidev.com/dev/settings/billing."
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
961
1321
|
return errorContent(
|
|
962
|
-
"Out of credits. Top up at https://
|
|
1322
|
+
"Out of credits for MCP calls. Top up at https://zaaidev.com/dev/settings/billing."
|
|
1323
|
+
);
|
|
1324
|
+
case 403:
|
|
1325
|
+
return errorContent(
|
|
1326
|
+
"This token is out of scope for that project (OutOfScope). The MCP token is restricted to specific projects; pick a project the token can access, or mint a token with broader scope at https://zaaidev.com/dev/settings/tokens."
|
|
1327
|
+
);
|
|
1328
|
+
case 404:
|
|
1329
|
+
return errorContent(
|
|
1330
|
+
`Not found (${cls ?? "not_found"}): ${detail}. The project, brief, or doc id doesn't exist or isn't visible to this token. List first (list_briefs / list_docs / list_captures) to get valid ids.`
|
|
1331
|
+
);
|
|
1332
|
+
case 410:
|
|
1333
|
+
return errorContent(
|
|
1334
|
+
"That project is archived (ProjectArchived) and can no longer be read over MCP. Unarchive it in the workspace to access it again."
|
|
1335
|
+
);
|
|
1336
|
+
case 422:
|
|
1337
|
+
return errorContent(
|
|
1338
|
+
`Unprocessable (${cls ?? "invalid"}): ${detail}. Likely a wrong brief type for this tool or an invalid doc id \u2014 check the tool's expected input against list_briefs / list_docs.`
|
|
1339
|
+
);
|
|
1340
|
+
case 429:
|
|
1341
|
+
return errorContent(
|
|
1342
|
+
"Rate limited (RateLimited). The MCP server retried with backoff and still hit the limit \u2014 wait a moment before the next call."
|
|
963
1343
|
);
|
|
964
1344
|
case 0:
|
|
965
1345
|
return errorContent(
|
|
966
|
-
`Network failure reaching the Zaai Dev workspace: ${
|
|
1346
|
+
`Network failure reaching the Zaai Dev workspace: ${detail}. Check your connection or ZAAI_API_URL setting.`
|
|
967
1347
|
);
|
|
968
1348
|
default:
|
|
969
1349
|
return errorContent(
|
|
970
|
-
`Workspace returned ${err.status} for ${err.path}: ${
|
|
1350
|
+
`Workspace returned ${err.status}${cls ? ` (${cls})` : ""} for ${err.path}: ${detail}.`
|
|
971
1351
|
);
|
|
972
1352
|
}
|
|
973
1353
|
}
|
|
974
1354
|
return errorContent(`Unexpected MCP tool failure: ${describe(err)}`);
|
|
975
1355
|
}
|
|
1356
|
+
function errorClassOf(body) {
|
|
1357
|
+
if (body && typeof body === "object" && "errorClass" in body) {
|
|
1358
|
+
const v = body.errorClass;
|
|
1359
|
+
return typeof v === "string" ? v : null;
|
|
1360
|
+
}
|
|
1361
|
+
return null;
|
|
1362
|
+
}
|
|
976
1363
|
function errorContent(text) {
|
|
977
1364
|
return { isError: true, content: [{ type: "text", text }] };
|
|
978
1365
|
}
|
|
@@ -987,7 +1374,7 @@ function describe(value) {
|
|
|
987
1374
|
}
|
|
988
1375
|
|
|
989
1376
|
// src/config.ts
|
|
990
|
-
var DEFAULT_API_URL = "https://
|
|
1377
|
+
var DEFAULT_API_URL = "https://zaaidev.com";
|
|
991
1378
|
var ConfigError = class extends Error {
|
|
992
1379
|
constructor(message) {
|
|
993
1380
|
super(message);
|
|
@@ -998,19 +1385,44 @@ function loadConfig() {
|
|
|
998
1385
|
const apiToken = process.env.ZAAI_API_TOKEN;
|
|
999
1386
|
if (!apiToken) {
|
|
1000
1387
|
throw new ConfigError(
|
|
1001
|
-
"ZAAI_API_TOKEN is not set. Mint a token at https://
|
|
1388
|
+
"ZAAI_API_TOKEN is not set. Mint a token at https://zaaidev.com/dev/settings/tokens (kind = mcp) and pass it via the env in your MCP client config."
|
|
1002
1389
|
);
|
|
1003
1390
|
}
|
|
1004
1391
|
if (!apiToken.startsWith("zaai_mcp_")) {
|
|
1005
1392
|
throw new ConfigError(
|
|
1006
|
-
"ZAAI_API_TOKEN must start with 'zaai_mcp_'. Did you paste an extension token (zaai_ext_*) by accident? Issue an AI tool token at https://
|
|
1393
|
+
"ZAAI_API_TOKEN must start with 'zaai_mcp_'. Did you paste an extension token (zaai_ext_*) by accident? Issue an AI tool token at https://zaaidev.com/dev/settings/tokens."
|
|
1007
1394
|
);
|
|
1008
1395
|
}
|
|
1009
1396
|
const rawUrl = process.env.ZAAI_API_URL ?? DEFAULT_API_URL;
|
|
1010
|
-
const apiUrl = rawUrl.replace(/\/+$/, "");
|
|
1397
|
+
const apiUrl = validateApiUrl(rawUrl.replace(/\/+$/, ""));
|
|
1011
1398
|
info("config loaded", { apiUrl });
|
|
1012
1399
|
return { apiToken, apiUrl };
|
|
1013
1400
|
}
|
|
1401
|
+
function validateApiUrl(value) {
|
|
1402
|
+
let url;
|
|
1403
|
+
try {
|
|
1404
|
+
url = new URL(value);
|
|
1405
|
+
} catch {
|
|
1406
|
+
throw new ConfigError(
|
|
1407
|
+
`ZAAI_API_URL is not a valid URL: ${JSON.stringify(value)}.`
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
const host = url.hostname.toLowerCase();
|
|
1411
|
+
const isLocal = host === "localhost" || host === "127.0.0.1";
|
|
1412
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLocal)) {
|
|
1413
|
+
throw new ConfigError(
|
|
1414
|
+
`ZAAI_API_URL must use https:// (got ${url.protocol}). The MCP token is sent on every request and must never travel in cleartext.`
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
const allowAny = process.env.ZAAI_API_URL_ALLOW_ANY === "1";
|
|
1418
|
+
const isZaai = host === "zaaidev.com" || host.endsWith(".zaaidev.com") || host === "zaaistudio.com" || host.endsWith(".zaaistudio.com");
|
|
1419
|
+
if (!isZaai && !isLocal && !allowAny) {
|
|
1420
|
+
throw new ConfigError(
|
|
1421
|
+
`ZAAI_API_URL host ${JSON.stringify(host)} is not a zaaidev.com domain. The MCP token would be sent to it. If this is intentional (self-hosted/staging workspace), set ZAAI_API_URL_ALLOW_ANY=1.`
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
return value;
|
|
1425
|
+
}
|
|
1014
1426
|
|
|
1015
1427
|
// src/bin/stdio.ts
|
|
1016
1428
|
async function main() {
|