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