@zaai-dev/mcp 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +53 -14
- package/dist/bin/http.js +1097 -0
- package/dist/bin/http.js.map +1 -0
- package/dist/bin/stdio.js +445 -16
- package/dist/bin/stdio.js.map +1 -1
- package/package.json +4 -2
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.3.0" : "0.0.0-dev";
|
|
14
14
|
|
|
15
15
|
// src/tools/health.ts
|
|
16
16
|
var healthInputSchema = z.object({});
|
|
@@ -50,25 +50,48 @@ var WorkspaceApiError = class extends Error {
|
|
|
50
50
|
body;
|
|
51
51
|
path;
|
|
52
52
|
};
|
|
53
|
+
var MAX_ATTEMPTS = 3;
|
|
54
|
+
var BASE_BACKOFF_MS = 250;
|
|
55
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
53
56
|
async function mcpApiFetch(config, path, init = {}) {
|
|
54
57
|
const url = `${config.apiUrl}${path}`;
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
res
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...
|
|
61
|
-
|
|
58
|
+
let lastErr = null;
|
|
59
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
60
|
+
let res;
|
|
61
|
+
try {
|
|
62
|
+
res = await fetch(url, {
|
|
63
|
+
...init,
|
|
64
|
+
headers: {
|
|
65
|
+
...bearerHeader(config.apiToken),
|
|
66
|
+
...init.headers ?? {}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
} catch (err2) {
|
|
70
|
+
lastErr = new WorkspaceApiError(0, { message: String(err2) }, path);
|
|
71
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
72
|
+
await sleep(BASE_BACKOFF_MS * 2 ** (attempt - 1));
|
|
73
|
+
continue;
|
|
62
74
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
throw lastErr;
|
|
76
|
+
}
|
|
77
|
+
if (res.ok) {
|
|
78
|
+
return await res.json();
|
|
79
|
+
}
|
|
68
80
|
const body = await res.json().catch(() => ({ error: `Non-JSON ${res.status} response` }));
|
|
69
|
-
|
|
81
|
+
const err = new WorkspaceApiError(res.status, body, path);
|
|
82
|
+
if (RETRYABLE_STATUSES.has(res.status) || res.status >= 502 && res.status <= 504) {
|
|
83
|
+
lastErr = err;
|
|
84
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
85
|
+
await sleep(BASE_BACKOFF_MS * 2 ** (attempt - 1));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
70
90
|
}
|
|
71
|
-
|
|
91
|
+
throw lastErr ?? new WorkspaceApiError(0, { message: "no attempts" }, path);
|
|
92
|
+
}
|
|
93
|
+
function sleep(ms) {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
72
95
|
}
|
|
73
96
|
|
|
74
97
|
// src/tools/whoami.ts
|
|
@@ -219,6 +242,212 @@ async function getMedia(store, args) {
|
|
|
219
242
|
return store.getField(args.id, "media");
|
|
220
243
|
}
|
|
221
244
|
|
|
245
|
+
// src/tools/get-brand-brief.ts
|
|
246
|
+
import { z as z7 } from "zod";
|
|
247
|
+
var getBrandBriefInputSchema = z7.object({
|
|
248
|
+
project_id: z7.string().uuid().describe(
|
|
249
|
+
"Zaai Dev project UUID. Find via the workspace at zaaistudio.com/dev/projects."
|
|
250
|
+
)
|
|
251
|
+
});
|
|
252
|
+
var getBrandBriefOutputSchema = z7.object({
|
|
253
|
+
project_id: z7.string(),
|
|
254
|
+
project_name: z7.string(),
|
|
255
|
+
version: z7.string().describe("Brief version number, or '0' if no brief has been generated yet."),
|
|
256
|
+
status: z7.enum(["draft", "published", "archived"]),
|
|
257
|
+
positioning: z7.string(),
|
|
258
|
+
values: z7.array(z7.string()),
|
|
259
|
+
voice: z7.object({
|
|
260
|
+
one_liner: z7.string(),
|
|
261
|
+
do_say: z7.array(z7.string()),
|
|
262
|
+
dont_say: z7.array(z7.string())
|
|
263
|
+
}),
|
|
264
|
+
audience: z7.object({
|
|
265
|
+
primary: z7.string(),
|
|
266
|
+
secondary: z7.string().nullable(),
|
|
267
|
+
needs: z7.array(z7.string())
|
|
268
|
+
}),
|
|
269
|
+
design_intent: z7.object({
|
|
270
|
+
descriptors: z7.array(z7.string()),
|
|
271
|
+
anti_descriptors: z7.array(z7.string())
|
|
272
|
+
}),
|
|
273
|
+
decisions: z7.array(
|
|
274
|
+
z7.object({
|
|
275
|
+
id: z7.string(),
|
|
276
|
+
title: z7.string(),
|
|
277
|
+
decided_at: z7.string(),
|
|
278
|
+
rationale: z7.string()
|
|
279
|
+
})
|
|
280
|
+
),
|
|
281
|
+
updated_at: z7.string()
|
|
282
|
+
}).passthrough();
|
|
283
|
+
async function getBrandBrief(store, args) {
|
|
284
|
+
return store.getBrandBrief(args.project_id);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/tools/get-voice.ts
|
|
288
|
+
import { z as z8 } from "zod";
|
|
289
|
+
var getVoiceInputSchema = z8.object({
|
|
290
|
+
project_id: z8.string().uuid().describe("Zaai Dev project UUID.")
|
|
291
|
+
});
|
|
292
|
+
var getVoiceOutputSchema = z8.object({
|
|
293
|
+
project_id: z8.string(),
|
|
294
|
+
one_liner: z8.string().describe("One-sentence summary of how the brand sounds."),
|
|
295
|
+
tone_descriptors: z8.array(z8.string()).describe("Adjectives the brand voice aims for."),
|
|
296
|
+
do_say: z8.array(z8.string()).describe("Phrasings / patterns the brand actively uses."),
|
|
297
|
+
dont_say: z8.array(z8.string()).describe("Phrasings / patterns the brand explicitly avoids."),
|
|
298
|
+
example_phrases: z8.array(z8.string()).describe("Concrete phrases that exemplify the voice.")
|
|
299
|
+
}).passthrough();
|
|
300
|
+
async function getVoice(store, args) {
|
|
301
|
+
return store.getVoice(args.project_id);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/tools/get-audience.ts
|
|
305
|
+
import { z as z9 } from "zod";
|
|
306
|
+
var getAudienceInputSchema = z9.object({
|
|
307
|
+
project_id: z9.string().uuid().describe("Zaai Dev project UUID.")
|
|
308
|
+
});
|
|
309
|
+
var getAudienceOutputSchema = z9.object({
|
|
310
|
+
project_id: z9.string(),
|
|
311
|
+
primary: z9.string().describe("Primary audience segment."),
|
|
312
|
+
secondary: z9.string().nullable().describe("Secondary audience segment, or null."),
|
|
313
|
+
needs: z9.array(z9.string()).describe("Jobs-to-be-done / pain points the audience has."),
|
|
314
|
+
channels: z9.array(z9.string()).describe("Where this audience consumes content.")
|
|
315
|
+
}).passthrough();
|
|
316
|
+
async function getAudience(store, args) {
|
|
317
|
+
return store.getAudience(args.project_id);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/tools/get-design-intent.ts
|
|
321
|
+
import { z as z10 } from "zod";
|
|
322
|
+
var getDesignIntentInputSchema = z10.object({
|
|
323
|
+
project_id: z10.string().uuid().describe("Zaai Dev project UUID.")
|
|
324
|
+
});
|
|
325
|
+
var getDesignIntentOutputSchema = z10.object({
|
|
326
|
+
project_id: z10.string(),
|
|
327
|
+
descriptors: z10.array(z10.string()).describe(
|
|
328
|
+
"Visual adjectives the brand aims for (e.g. 'editorial', 'spacious', 'monochrome')."
|
|
329
|
+
),
|
|
330
|
+
anti_descriptors: z10.array(z10.string()).describe("Visual adjectives the brand explicitly avoids."),
|
|
331
|
+
inspiration_summary: z10.string().describe("Free-form summary of the inspiration the brief points at.")
|
|
332
|
+
}).passthrough();
|
|
333
|
+
async function getDesignIntent(store, args) {
|
|
334
|
+
return store.getDesignIntent(args.project_id);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/tools/get-brand-tokens.ts
|
|
338
|
+
import { z as z11 } from "zod";
|
|
339
|
+
var getBrandTokensInputSchema = z11.object({
|
|
340
|
+
project_id: z11.string().uuid().describe("Zaai Dev project UUID.")
|
|
341
|
+
});
|
|
342
|
+
var getBrandTokensOutputSchema = z11.object({
|
|
343
|
+
project_id: z11.string(),
|
|
344
|
+
colors: z11.array(
|
|
345
|
+
z11.object({
|
|
346
|
+
name: z11.string(),
|
|
347
|
+
value: z11.string().describe("Hex / rgba / hsl value as authored."),
|
|
348
|
+
role: z11.string().optional().describe("Role like 'primary' / 'accent' / 'surface'.")
|
|
349
|
+
})
|
|
350
|
+
),
|
|
351
|
+
fonts: z11.array(
|
|
352
|
+
z11.object({
|
|
353
|
+
role: z11.string().describe("'display', 'body', 'mono', etc."),
|
|
354
|
+
family: z11.string(),
|
|
355
|
+
weights: z11.array(z11.number().int())
|
|
356
|
+
})
|
|
357
|
+
),
|
|
358
|
+
radius: z11.array(z11.object({ name: z11.string(), value: z11.string() })),
|
|
359
|
+
shadows: z11.array(z11.object({ name: z11.string(), value: z11.string() }))
|
|
360
|
+
}).passthrough();
|
|
361
|
+
async function getBrandTokens(store, args) {
|
|
362
|
+
return store.getBrandTokens(args.project_id);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/tools/get-decisions.ts
|
|
366
|
+
import { z as z12 } from "zod";
|
|
367
|
+
var getDecisionsInputSchema = z12.object({
|
|
368
|
+
project_id: z12.string().uuid().describe("Zaai Dev project UUID."),
|
|
369
|
+
limit: z12.number().int().min(1).max(200).optional().describe("Max decisions to return. Default 50, max 200, newest first.")
|
|
370
|
+
});
|
|
371
|
+
var getDecisionsOutputSchema = z12.object({
|
|
372
|
+
project_id: z12.string(),
|
|
373
|
+
items: z12.array(
|
|
374
|
+
z12.object({
|
|
375
|
+
id: z12.string(),
|
|
376
|
+
title: z12.string(),
|
|
377
|
+
rationale: z12.string(),
|
|
378
|
+
attribution: z12.string().describe(
|
|
379
|
+
"Who decided. 'mcp:<token_id>' for AI-logged decisions; user email / id for workspace-authored ones."
|
|
380
|
+
),
|
|
381
|
+
brief_field: z12.string().nullable().describe("Which brief slice this decision concerns, if any (e.g. 'voice.tone')."),
|
|
382
|
+
reference_capture_id: z12.string().nullable(),
|
|
383
|
+
created_at: z12.string()
|
|
384
|
+
}).passthrough()
|
|
385
|
+
)
|
|
386
|
+
}).passthrough();
|
|
387
|
+
async function getDecisions(store, args) {
|
|
388
|
+
return store.getDecisions(args.project_id, args.limit);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/tools/get-references.ts
|
|
392
|
+
import { z as z13 } from "zod";
|
|
393
|
+
var getReferencesInputSchema = z13.object({
|
|
394
|
+
project_id: z13.string().uuid().describe("Zaai Dev project UUID."),
|
|
395
|
+
limit: z13.number().int().min(1).max(100).optional().describe("Max references to return. Default 20, max 100, newest first."),
|
|
396
|
+
cursor: z13.string().optional().describe("Opaque pagination cursor from the previous response's next_cursor.")
|
|
397
|
+
});
|
|
398
|
+
var referenceItem = z13.object({
|
|
399
|
+
id: z13.string(),
|
|
400
|
+
type: z13.enum(["page", "element", "composite"]),
|
|
401
|
+
source_url: z13.string(),
|
|
402
|
+
source_title: z13.string(),
|
|
403
|
+
note: z13.string().nullable(),
|
|
404
|
+
captured_at: z13.string(),
|
|
405
|
+
thumbnail_url: z13.string().nullable(),
|
|
406
|
+
screenshot_url: z13.string().nullable()
|
|
407
|
+
}).passthrough();
|
|
408
|
+
var getReferencesOutputSchema = z13.object({
|
|
409
|
+
project_id: z13.string(),
|
|
410
|
+
items: z13.array(referenceItem),
|
|
411
|
+
next_cursor: z13.string().nullable()
|
|
412
|
+
}).passthrough();
|
|
413
|
+
async function getReferences(store, args) {
|
|
414
|
+
return store.getReferences(args.project_id, {
|
|
415
|
+
limit: args.limit,
|
|
416
|
+
cursor: args.cursor
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/tools/search-references.ts
|
|
421
|
+
import { z as z14 } from "zod";
|
|
422
|
+
var searchReferencesInputSchema = z14.object({
|
|
423
|
+
project_id: z14.string().uuid().describe("Zaai Dev project UUID."),
|
|
424
|
+
q: z14.string().min(1).max(200).describe("Keyword query \u2014 matched against title / url / note (case-insensitive substring)."),
|
|
425
|
+
limit: z14.number().int().min(1).max(50).optional().describe("Max results to return. Default 10, max 50.")
|
|
426
|
+
});
|
|
427
|
+
var searchReferenceItem = z14.object({
|
|
428
|
+
id: z14.string(),
|
|
429
|
+
type: z14.enum(["page", "element", "composite"]),
|
|
430
|
+
source_url: z14.string(),
|
|
431
|
+
source_title: z14.string(),
|
|
432
|
+
note: z14.string().nullable(),
|
|
433
|
+
captured_at: z14.string(),
|
|
434
|
+
thumbnail_url: z14.string().nullable(),
|
|
435
|
+
screenshot_url: z14.string().nullable(),
|
|
436
|
+
score: z14.number().describe("Relevance score 0..1. v1 keyword scoring: 1.0 title / 0.7 url / 0.5 note."),
|
|
437
|
+
matched_field: z14.enum(["title", "url", "note", "tag"])
|
|
438
|
+
}).passthrough();
|
|
439
|
+
var searchReferencesOutputSchema = z14.object({
|
|
440
|
+
project_id: z14.string(),
|
|
441
|
+
query: z14.string(),
|
|
442
|
+
items: z14.array(searchReferenceItem)
|
|
443
|
+
}).passthrough();
|
|
444
|
+
async function searchReferences(store, args) {
|
|
445
|
+
return store.searchReferences(args.project_id, {
|
|
446
|
+
q: args.q,
|
|
447
|
+
limit: args.limit
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
222
451
|
// src/store/http-store.ts
|
|
223
452
|
var HttpCaptureStore = class {
|
|
224
453
|
constructor(config) {
|
|
@@ -250,6 +479,72 @@ var HttpCaptureStore = class {
|
|
|
250
479
|
}
|
|
251
480
|
};
|
|
252
481
|
|
|
482
|
+
// src/store/project-store.ts
|
|
483
|
+
var HttpProjectStore = class {
|
|
484
|
+
constructor(config) {
|
|
485
|
+
this.config = config;
|
|
486
|
+
}
|
|
487
|
+
config;
|
|
488
|
+
async getBrandBrief(projectId) {
|
|
489
|
+
return mcpApiFetch(
|
|
490
|
+
this.config,
|
|
491
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/brief`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
async getVoice(projectId) {
|
|
495
|
+
return mcpApiFetch(
|
|
496
|
+
this.config,
|
|
497
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/voice`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
async getAudience(projectId) {
|
|
501
|
+
return mcpApiFetch(
|
|
502
|
+
this.config,
|
|
503
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/audience`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
async getDesignIntent(projectId) {
|
|
507
|
+
return mcpApiFetch(
|
|
508
|
+
this.config,
|
|
509
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/design-intent`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
async getBrandTokens(projectId) {
|
|
513
|
+
return mcpApiFetch(
|
|
514
|
+
this.config,
|
|
515
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/brand-tokens`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
async getDecisions(projectId, limit) {
|
|
519
|
+
const sp = new URLSearchParams();
|
|
520
|
+
if (limit !== void 0) sp.set("limit", String(limit));
|
|
521
|
+
const qs = sp.toString();
|
|
522
|
+
return mcpApiFetch(
|
|
523
|
+
this.config,
|
|
524
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/decisions${qs ? `?${qs}` : ""}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
async getReferences(projectId, args) {
|
|
528
|
+
const sp = new URLSearchParams();
|
|
529
|
+
if (args.limit !== void 0) sp.set("limit", String(args.limit));
|
|
530
|
+
if (args.cursor) sp.set("cursor", args.cursor);
|
|
531
|
+
const qs = sp.toString();
|
|
532
|
+
return mcpApiFetch(
|
|
533
|
+
this.config,
|
|
534
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/references${qs ? `?${qs}` : ""}`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
async searchReferences(projectId, args) {
|
|
538
|
+
const sp = new URLSearchParams();
|
|
539
|
+
sp.set("q", args.q);
|
|
540
|
+
if (args.limit !== void 0) sp.set("limit", String(args.limit));
|
|
541
|
+
return mcpApiFetch(
|
|
542
|
+
this.config,
|
|
543
|
+
`/api/mcp/projects/${encodeURIComponent(projectId)}/references/search?${sp.toString()}`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
253
548
|
// src/resources/capture-resource.ts
|
|
254
549
|
import {
|
|
255
550
|
ResourceTemplate
|
|
@@ -315,6 +610,7 @@ function createServer(config) {
|
|
|
315
610
|
version: PKG_VERSION
|
|
316
611
|
});
|
|
317
612
|
const captureStore = new HttpCaptureStore(config);
|
|
613
|
+
const projectStore = new HttpProjectStore(config);
|
|
318
614
|
server.registerTool(
|
|
319
615
|
"health",
|
|
320
616
|
{
|
|
@@ -481,6 +777,118 @@ function createServer(config) {
|
|
|
481
777
|
},
|
|
482
778
|
makeFocusedGetterHandler(captureStore, getMedia, "media")
|
|
483
779
|
);
|
|
780
|
+
server.registerTool(
|
|
781
|
+
"get_brand_brief",
|
|
782
|
+
{
|
|
783
|
+
title: "Get brand brief",
|
|
784
|
+
description: "Returns the full published brand brief for a project: positioning, values, voice, audience, design intent, and decisions. Use this when the LLM needs the whole picture before writing copy or designing a component. If the project has no brief yet, the response still validates \u2014 most fields are empty strings or empty arrays.",
|
|
785
|
+
inputSchema: getBrandBriefInputSchema.shape,
|
|
786
|
+
outputSchema: getBrandBriefOutputSchema.shape
|
|
787
|
+
},
|
|
788
|
+
makeProjectToolHandler(
|
|
789
|
+
projectStore,
|
|
790
|
+
getBrandBrief,
|
|
791
|
+
(r) => `Brief v${r.version} (${r.status}) for "${r.project_name}". Voice: ${r.voice.one_liner || "\u2014"}.`
|
|
792
|
+
)
|
|
793
|
+
);
|
|
794
|
+
server.registerTool(
|
|
795
|
+
"get_voice",
|
|
796
|
+
{
|
|
797
|
+
title: "Get voice guidelines",
|
|
798
|
+
description: "Returns only the voice slice: one-liner, tone descriptors, do-say / don't-say lists, example phrases. Lighter than the full brief when the LLM is only writing copy. Costs 1 credit.",
|
|
799
|
+
inputSchema: getVoiceInputSchema.shape,
|
|
800
|
+
outputSchema: getVoiceOutputSchema.shape
|
|
801
|
+
},
|
|
802
|
+
makeProjectToolHandler(
|
|
803
|
+
projectStore,
|
|
804
|
+
getVoice,
|
|
805
|
+
(r) => `Voice for project ${r.project_id.slice(0, 8)}\u2026: "${r.one_liner || "\u2014"}". ${r.tone_descriptors.length} tone descriptor${r.tone_descriptors.length === 1 ? "" : "s"}.`
|
|
806
|
+
)
|
|
807
|
+
);
|
|
808
|
+
server.registerTool(
|
|
809
|
+
"get_audience",
|
|
810
|
+
{
|
|
811
|
+
title: "Get audience profile",
|
|
812
|
+
description: "Returns only the audience slice: primary segment, optional secondary, needs / pain points, channels. Use when targeting copy or design at a specific audience. Costs 1 credit.",
|
|
813
|
+
inputSchema: getAudienceInputSchema.shape,
|
|
814
|
+
outputSchema: getAudienceOutputSchema.shape
|
|
815
|
+
},
|
|
816
|
+
makeProjectToolHandler(
|
|
817
|
+
projectStore,
|
|
818
|
+
getAudience,
|
|
819
|
+
(r) => `Audience: ${r.primary || "\u2014"}` + (r.secondary ? ` (secondary: ${r.secondary})` : "") + `. ${r.needs.length} stated need${r.needs.length === 1 ? "" : "s"}.`
|
|
820
|
+
)
|
|
821
|
+
);
|
|
822
|
+
server.registerTool(
|
|
823
|
+
"get_design_intent",
|
|
824
|
+
{
|
|
825
|
+
title: "Get design intent",
|
|
826
|
+
description: "Returns the visual descriptors and anti-descriptors that constrain design exploration, plus the inspiration_summary. Use this before generating layouts, palettes, or component styles \u2014 it's the guardrail set the brief encodes. Costs 1 credit.",
|
|
827
|
+
inputSchema: getDesignIntentInputSchema.shape,
|
|
828
|
+
outputSchema: getDesignIntentOutputSchema.shape
|
|
829
|
+
},
|
|
830
|
+
makeProjectToolHandler(
|
|
831
|
+
projectStore,
|
|
832
|
+
getDesignIntent,
|
|
833
|
+
(r) => `${r.descriptors.length} descriptor${r.descriptors.length === 1 ? "" : "s"}, ${r.anti_descriptors.length} anti-descriptor${r.anti_descriptors.length === 1 ? "" : "s"}.`
|
|
834
|
+
)
|
|
835
|
+
);
|
|
836
|
+
server.registerTool(
|
|
837
|
+
"get_brand_tokens",
|
|
838
|
+
{
|
|
839
|
+
title: "Get brand tokens",
|
|
840
|
+
description: "Returns the brand's design tokens: colors (with optional role), fonts (role / family / weights), radius scale, shadow scale. Use this when generating CSS, Tailwind config, or component styles that should match the brand. Costs 1 credit.",
|
|
841
|
+
inputSchema: getBrandTokensInputSchema.shape,
|
|
842
|
+
outputSchema: getBrandTokensOutputSchema.shape
|
|
843
|
+
},
|
|
844
|
+
makeProjectToolHandler(
|
|
845
|
+
projectStore,
|
|
846
|
+
getBrandTokens,
|
|
847
|
+
(r) => `${r.colors.length} color${r.colors.length === 1 ? "" : "s"}, ${r.fonts.length} font${r.fonts.length === 1 ? "" : "s"}, ${r.radius.length} radius, ${r.shadows.length} shadow${r.shadows.length === 1 ? "" : "s"}.`
|
|
848
|
+
)
|
|
849
|
+
);
|
|
850
|
+
server.registerTool(
|
|
851
|
+
"get_decisions",
|
|
852
|
+
{
|
|
853
|
+
title: "Get decisions log",
|
|
854
|
+
description: "Returns the brand + design decisions log for a project: what was decided, why, who decided, when, and which brief field (if any) it concerns. Use this to avoid re-litigating settled questions. Costs 1 credit.",
|
|
855
|
+
inputSchema: getDecisionsInputSchema.shape,
|
|
856
|
+
outputSchema: getDecisionsOutputSchema.shape
|
|
857
|
+
},
|
|
858
|
+
makeProjectToolHandler(
|
|
859
|
+
projectStore,
|
|
860
|
+
getDecisions,
|
|
861
|
+
(r) => `${r.items.length} decision${r.items.length === 1 ? "" : "s"} returned (newest first).`
|
|
862
|
+
)
|
|
863
|
+
);
|
|
864
|
+
server.registerTool(
|
|
865
|
+
"get_references",
|
|
866
|
+
{
|
|
867
|
+
title: "Get project references",
|
|
868
|
+
description: "Paginated list of references (captures) for one project. Use this when the LLM needs to know what visual / interaction references the user has saved for a specific project \u2014 different from list_captures which is org-wide. Costs 1 credit per call.",
|
|
869
|
+
inputSchema: getReferencesInputSchema.shape,
|
|
870
|
+
outputSchema: getReferencesOutputSchema.shape
|
|
871
|
+
},
|
|
872
|
+
makeProjectToolHandler(
|
|
873
|
+
projectStore,
|
|
874
|
+
getReferences,
|
|
875
|
+
(r) => `${r.items.length} reference${r.items.length === 1 ? "" : "s"} returned` + (r.next_cursor ? " (more available \u2014 pass next_cursor for the next page)." : ".")
|
|
876
|
+
)
|
|
877
|
+
);
|
|
878
|
+
server.registerTool(
|
|
879
|
+
"search_references",
|
|
880
|
+
{
|
|
881
|
+
title: "Search project references",
|
|
882
|
+
description: "Keyword search over a project's references (title / url / note in v1; pgvector ranking in v1.5). Returns scored matches with matched_field signals. Use when the LLM has a specific concept in mind ('hero', 'pricing table'). Costs 1 credit.",
|
|
883
|
+
inputSchema: searchReferencesInputSchema.shape,
|
|
884
|
+
outputSchema: searchReferencesOutputSchema.shape
|
|
885
|
+
},
|
|
886
|
+
makeProjectToolHandler(
|
|
887
|
+
projectStore,
|
|
888
|
+
searchReferences,
|
|
889
|
+
(r) => r.items.length === 0 ? `No references match "${r.query}".` : `${r.items.length} reference${r.items.length === 1 ? "" : "s"} matching "${r.query}".`
|
|
890
|
+
)
|
|
891
|
+
);
|
|
484
892
|
registerCaptureResource(server, captureStore);
|
|
485
893
|
info("server initialized", {
|
|
486
894
|
version: PKG_VERSION,
|
|
@@ -493,12 +901,33 @@ function createServer(config) {
|
|
|
493
901
|
"get_palette",
|
|
494
902
|
"get_html",
|
|
495
903
|
"get_animation",
|
|
496
|
-
"get_media"
|
|
904
|
+
"get_media",
|
|
905
|
+
"get_brand_brief",
|
|
906
|
+
"get_voice",
|
|
907
|
+
"get_audience",
|
|
908
|
+
"get_design_intent",
|
|
909
|
+
"get_brand_tokens",
|
|
910
|
+
"get_decisions",
|
|
911
|
+
"get_references",
|
|
912
|
+
"search_references"
|
|
497
913
|
],
|
|
498
914
|
resources: ["zaai-capture://{id}"]
|
|
499
915
|
});
|
|
500
916
|
return server;
|
|
501
917
|
}
|
|
918
|
+
function makeProjectToolHandler(store, fn, summary) {
|
|
919
|
+
return async (args) => {
|
|
920
|
+
try {
|
|
921
|
+
const result = await fn(store, args);
|
|
922
|
+
return {
|
|
923
|
+
structuredContent: result,
|
|
924
|
+
content: [{ type: "text", text: summary(result) }]
|
|
925
|
+
};
|
|
926
|
+
} catch (err) {
|
|
927
|
+
return toolErrorResponse(err);
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
}
|
|
502
931
|
function makeFocusedGetterHandler(store, fn, label) {
|
|
503
932
|
return async (args) => {
|
|
504
933
|
try {
|