@zaai-dev/mcp
Advanced tools
+408
-2
@@ -13,3 +13,3 @@ #!/usr/bin/env node | ||
| // src/version.ts | ||
| var PKG_VERSION = true ? "0.1.0" : "0.0.0-dev"; | ||
| var PKG_VERSION = true ? "0.2.0" : "0.0.0-dev"; | ||
@@ -223,2 +223,208 @@ // src/tools/health.ts | ||
| // src/tools/get-brand-brief.ts | ||
| import { z as z7 } from "zod"; | ||
| var getBrandBriefInputSchema = z7.object({ | ||
| project_id: z7.string().uuid().describe( | ||
| "Zaai Dev project UUID. Find via the workspace at zaaistudio.com/dev/projects." | ||
| ) | ||
| }); | ||
| var getBrandBriefOutputSchema = z7.object({ | ||
| project_id: z7.string(), | ||
| project_name: z7.string(), | ||
| version: z7.string().describe("Brief version number, or '0' if no brief has been generated yet."), | ||
| status: z7.enum(["draft", "published", "archived"]), | ||
| positioning: z7.string(), | ||
| values: z7.array(z7.string()), | ||
| voice: z7.object({ | ||
| one_liner: z7.string(), | ||
| do_say: z7.array(z7.string()), | ||
| dont_say: z7.array(z7.string()) | ||
| }), | ||
| audience: z7.object({ | ||
| primary: z7.string(), | ||
| secondary: z7.string().nullable(), | ||
| needs: z7.array(z7.string()) | ||
| }), | ||
| design_intent: z7.object({ | ||
| descriptors: z7.array(z7.string()), | ||
| anti_descriptors: z7.array(z7.string()) | ||
| }), | ||
| decisions: z7.array( | ||
| z7.object({ | ||
| id: z7.string(), | ||
| title: z7.string(), | ||
| decided_at: z7.string(), | ||
| rationale: z7.string() | ||
| }) | ||
| ), | ||
| updated_at: z7.string() | ||
| }).passthrough(); | ||
| async function getBrandBrief(store, args) { | ||
| return store.getBrandBrief(args.project_id); | ||
| } | ||
| // src/tools/get-voice.ts | ||
| import { z as z8 } from "zod"; | ||
| var getVoiceInputSchema = z8.object({ | ||
| project_id: z8.string().uuid().describe("Zaai Dev project UUID.") | ||
| }); | ||
| var getVoiceOutputSchema = z8.object({ | ||
| project_id: z8.string(), | ||
| one_liner: z8.string().describe("One-sentence summary of how the brand sounds."), | ||
| tone_descriptors: z8.array(z8.string()).describe("Adjectives the brand voice aims for."), | ||
| do_say: z8.array(z8.string()).describe("Phrasings / patterns the brand actively uses."), | ||
| dont_say: z8.array(z8.string()).describe("Phrasings / patterns the brand explicitly avoids."), | ||
| example_phrases: z8.array(z8.string()).describe("Concrete phrases that exemplify the voice.") | ||
| }).passthrough(); | ||
| async function getVoice(store, args) { | ||
| return store.getVoice(args.project_id); | ||
| } | ||
| // src/tools/get-audience.ts | ||
| import { z as z9 } from "zod"; | ||
| var getAudienceInputSchema = z9.object({ | ||
| project_id: z9.string().uuid().describe("Zaai Dev project UUID.") | ||
| }); | ||
| var getAudienceOutputSchema = z9.object({ | ||
| project_id: z9.string(), | ||
| primary: z9.string().describe("Primary audience segment."), | ||
| secondary: z9.string().nullable().describe("Secondary audience segment, or null."), | ||
| needs: z9.array(z9.string()).describe("Jobs-to-be-done / pain points the audience has."), | ||
| channels: z9.array(z9.string()).describe("Where this audience consumes content.") | ||
| }).passthrough(); | ||
| async function getAudience(store, args) { | ||
| return store.getAudience(args.project_id); | ||
| } | ||
| // src/tools/get-design-intent.ts | ||
| import { z as z10 } from "zod"; | ||
| var getDesignIntentInputSchema = z10.object({ | ||
| project_id: z10.string().uuid().describe("Zaai Dev project UUID.") | ||
| }); | ||
| var getDesignIntentOutputSchema = z10.object({ | ||
| project_id: z10.string(), | ||
| descriptors: z10.array(z10.string()).describe( | ||
| "Visual adjectives the brand aims for (e.g. 'editorial', 'spacious', 'monochrome')." | ||
| ), | ||
| anti_descriptors: z10.array(z10.string()).describe("Visual adjectives the brand explicitly avoids."), | ||
| inspiration_summary: z10.string().describe("Free-form summary of the inspiration the brief points at.") | ||
| }).passthrough(); | ||
| async function getDesignIntent(store, args) { | ||
| return store.getDesignIntent(args.project_id); | ||
| } | ||
| // src/tools/get-brand-tokens.ts | ||
| import { z as z11 } from "zod"; | ||
| var getBrandTokensInputSchema = z11.object({ | ||
| project_id: z11.string().uuid().describe("Zaai Dev project UUID.") | ||
| }); | ||
| var getBrandTokensOutputSchema = z11.object({ | ||
| project_id: z11.string(), | ||
| colors: z11.array( | ||
| z11.object({ | ||
| name: z11.string(), | ||
| value: z11.string().describe("Hex / rgba / hsl value as authored."), | ||
| role: z11.string().optional().describe("Role like 'primary' / 'accent' / 'surface'.") | ||
| }) | ||
| ), | ||
| fonts: z11.array( | ||
| z11.object({ | ||
| role: z11.string().describe("'display', 'body', 'mono', etc."), | ||
| family: z11.string(), | ||
| weights: z11.array(z11.number().int()) | ||
| }) | ||
| ), | ||
| radius: z11.array(z11.object({ name: z11.string(), value: z11.string() })), | ||
| shadows: z11.array(z11.object({ name: z11.string(), value: z11.string() })) | ||
| }).passthrough(); | ||
| async function getBrandTokens(store, args) { | ||
| return store.getBrandTokens(args.project_id); | ||
| } | ||
| // src/tools/get-decisions.ts | ||
| import { z as z12 } from "zod"; | ||
| var getDecisionsInputSchema = z12.object({ | ||
| project_id: z12.string().uuid().describe("Zaai Dev project UUID."), | ||
| limit: z12.number().int().min(1).max(200).optional().describe("Max decisions to return. Default 50, max 200, newest first.") | ||
| }); | ||
| var getDecisionsOutputSchema = z12.object({ | ||
| project_id: z12.string(), | ||
| items: z12.array( | ||
| z12.object({ | ||
| id: z12.string(), | ||
| title: z12.string(), | ||
| rationale: z12.string(), | ||
| attribution: z12.string().describe( | ||
| "Who decided. 'mcp:<token_id>' for AI-logged decisions; user email / id for workspace-authored ones." | ||
| ), | ||
| brief_field: z12.string().nullable().describe("Which brief slice this decision concerns, if any (e.g. 'voice.tone')."), | ||
| reference_capture_id: z12.string().nullable(), | ||
| created_at: z12.string() | ||
| }).passthrough() | ||
| ) | ||
| }).passthrough(); | ||
| async function getDecisions(store, args) { | ||
| return store.getDecisions(args.project_id, args.limit); | ||
| } | ||
| // src/tools/get-references.ts | ||
| import { z as z13 } from "zod"; | ||
| var getReferencesInputSchema = z13.object({ | ||
| project_id: z13.string().uuid().describe("Zaai Dev project UUID."), | ||
| limit: z13.number().int().min(1).max(100).optional().describe("Max references to return. Default 20, max 100, newest first."), | ||
| cursor: z13.string().optional().describe("Opaque pagination cursor from the previous response's next_cursor.") | ||
| }); | ||
| var referenceItem = z13.object({ | ||
| id: z13.string(), | ||
| type: z13.enum(["page", "element", "composite"]), | ||
| source_url: z13.string(), | ||
| source_title: z13.string(), | ||
| note: z13.string().nullable(), | ||
| captured_at: z13.string(), | ||
| thumbnail_url: z13.string().nullable(), | ||
| screenshot_url: z13.string().nullable() | ||
| }).passthrough(); | ||
| var getReferencesOutputSchema = z13.object({ | ||
| project_id: z13.string(), | ||
| items: z13.array(referenceItem), | ||
| next_cursor: z13.string().nullable() | ||
| }).passthrough(); | ||
| async function getReferences(store, args) { | ||
| return store.getReferences(args.project_id, { | ||
| limit: args.limit, | ||
| cursor: args.cursor | ||
| }); | ||
| } | ||
| // src/tools/search-references.ts | ||
| import { z as z14 } from "zod"; | ||
| var searchReferencesInputSchema = z14.object({ | ||
| project_id: z14.string().uuid().describe("Zaai Dev project UUID."), | ||
| q: z14.string().min(1).max(200).describe("Keyword query \u2014 matched against title / url / note (case-insensitive substring)."), | ||
| limit: z14.number().int().min(1).max(50).optional().describe("Max results to return. Default 10, max 50.") | ||
| }); | ||
| var searchReferenceItem = z14.object({ | ||
| id: z14.string(), | ||
| type: z14.enum(["page", "element", "composite"]), | ||
| source_url: z14.string(), | ||
| source_title: z14.string(), | ||
| note: z14.string().nullable(), | ||
| captured_at: z14.string(), | ||
| thumbnail_url: z14.string().nullable(), | ||
| screenshot_url: z14.string().nullable(), | ||
| score: z14.number().describe("Relevance score 0..1. v1 keyword scoring: 1.0 title / 0.7 url / 0.5 note."), | ||
| matched_field: z14.enum(["title", "url", "note", "tag"]) | ||
| }).passthrough(); | ||
| var searchReferencesOutputSchema = z14.object({ | ||
| project_id: z14.string(), | ||
| query: z14.string(), | ||
| items: z14.array(searchReferenceItem) | ||
| }).passthrough(); | ||
| async function searchReferences(store, args) { | ||
| return store.searchReferences(args.project_id, { | ||
| q: args.q, | ||
| limit: args.limit | ||
| }); | ||
| } | ||
| // src/store/http-store.ts | ||
@@ -255,2 +461,68 @@ var HttpCaptureStore = class { | ||
| // src/store/project-store.ts | ||
| var HttpProjectStore = class { | ||
| constructor(config) { | ||
| this.config = config; | ||
| } | ||
| config; | ||
| async getBrandBrief(projectId) { | ||
| return mcpApiFetch( | ||
| this.config, | ||
| `/api/mcp/projects/${encodeURIComponent(projectId)}/brief` | ||
| ); | ||
| } | ||
| async getVoice(projectId) { | ||
| return mcpApiFetch( | ||
| this.config, | ||
| `/api/mcp/projects/${encodeURIComponent(projectId)}/voice` | ||
| ); | ||
| } | ||
| async getAudience(projectId) { | ||
| return mcpApiFetch( | ||
| this.config, | ||
| `/api/mcp/projects/${encodeURIComponent(projectId)}/audience` | ||
| ); | ||
| } | ||
| async getDesignIntent(projectId) { | ||
| return mcpApiFetch( | ||
| this.config, | ||
| `/api/mcp/projects/${encodeURIComponent(projectId)}/design-intent` | ||
| ); | ||
| } | ||
| async getBrandTokens(projectId) { | ||
| return mcpApiFetch( | ||
| this.config, | ||
| `/api/mcp/projects/${encodeURIComponent(projectId)}/brand-tokens` | ||
| ); | ||
| } | ||
| async getDecisions(projectId, limit) { | ||
| const sp = new URLSearchParams(); | ||
| if (limit !== void 0) sp.set("limit", String(limit)); | ||
| const qs = sp.toString(); | ||
| return mcpApiFetch( | ||
| this.config, | ||
| `/api/mcp/projects/${encodeURIComponent(projectId)}/decisions${qs ? `?${qs}` : ""}` | ||
| ); | ||
| } | ||
| async getReferences(projectId, args) { | ||
| const sp = new URLSearchParams(); | ||
| if (args.limit !== void 0) sp.set("limit", String(args.limit)); | ||
| if (args.cursor) sp.set("cursor", args.cursor); | ||
| const qs = sp.toString(); | ||
| return mcpApiFetch( | ||
| this.config, | ||
| `/api/mcp/projects/${encodeURIComponent(projectId)}/references${qs ? `?${qs}` : ""}` | ||
| ); | ||
| } | ||
| async searchReferences(projectId, args) { | ||
| const sp = new URLSearchParams(); | ||
| sp.set("q", args.q); | ||
| if (args.limit !== void 0) sp.set("limit", String(args.limit)); | ||
| return mcpApiFetch( | ||
| this.config, | ||
| `/api/mcp/projects/${encodeURIComponent(projectId)}/references/search?${sp.toString()}` | ||
| ); | ||
| } | ||
| }; | ||
| // src/resources/capture-resource.ts | ||
@@ -321,2 +593,3 @@ import { | ||
| const captureStore = new HttpCaptureStore(config); | ||
| const projectStore = new HttpProjectStore(config); | ||
| server.registerTool( | ||
@@ -488,2 +761,114 @@ "health", | ||
| ); | ||
| server.registerTool( | ||
| "get_brand_brief", | ||
| { | ||
| title: "Get brand brief", | ||
| 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.", | ||
| inputSchema: getBrandBriefInputSchema.shape, | ||
| outputSchema: getBrandBriefOutputSchema.shape | ||
| }, | ||
| makeProjectToolHandler( | ||
| projectStore, | ||
| getBrandBrief, | ||
| (r) => `Brief v${r.version} (${r.status}) for "${r.project_name}". Voice: ${r.voice.one_liner || "\u2014"}.` | ||
| ) | ||
| ); | ||
| server.registerTool( | ||
| "get_voice", | ||
| { | ||
| title: "Get voice guidelines", | ||
| 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.", | ||
| inputSchema: getVoiceInputSchema.shape, | ||
| outputSchema: getVoiceOutputSchema.shape | ||
| }, | ||
| makeProjectToolHandler( | ||
| projectStore, | ||
| getVoice, | ||
| (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"}.` | ||
| ) | ||
| ); | ||
| server.registerTool( | ||
| "get_audience", | ||
| { | ||
| title: "Get audience profile", | ||
| 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.", | ||
| inputSchema: getAudienceInputSchema.shape, | ||
| outputSchema: getAudienceOutputSchema.shape | ||
| }, | ||
| makeProjectToolHandler( | ||
| projectStore, | ||
| getAudience, | ||
| (r) => `Audience: ${r.primary || "\u2014"}` + (r.secondary ? ` (secondary: ${r.secondary})` : "") + `. ${r.needs.length} stated need${r.needs.length === 1 ? "" : "s"}.` | ||
| ) | ||
| ); | ||
| server.registerTool( | ||
| "get_design_intent", | ||
| { | ||
| title: "Get design intent", | ||
| 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.", | ||
| inputSchema: getDesignIntentInputSchema.shape, | ||
| outputSchema: getDesignIntentOutputSchema.shape | ||
| }, | ||
| makeProjectToolHandler( | ||
| projectStore, | ||
| getDesignIntent, | ||
| (r) => `${r.descriptors.length} descriptor${r.descriptors.length === 1 ? "" : "s"}, ${r.anti_descriptors.length} anti-descriptor${r.anti_descriptors.length === 1 ? "" : "s"}.` | ||
| ) | ||
| ); | ||
| server.registerTool( | ||
| "get_brand_tokens", | ||
| { | ||
| title: "Get brand tokens", | ||
| 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.", | ||
| inputSchema: getBrandTokensInputSchema.shape, | ||
| outputSchema: getBrandTokensOutputSchema.shape | ||
| }, | ||
| makeProjectToolHandler( | ||
| projectStore, | ||
| getBrandTokens, | ||
| (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"}.` | ||
| ) | ||
| ); | ||
| server.registerTool( | ||
| "get_decisions", | ||
| { | ||
| title: "Get decisions log", | ||
| 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.", | ||
| inputSchema: getDecisionsInputSchema.shape, | ||
| outputSchema: getDecisionsOutputSchema.shape | ||
| }, | ||
| makeProjectToolHandler( | ||
| projectStore, | ||
| getDecisions, | ||
| (r) => `${r.items.length} decision${r.items.length === 1 ? "" : "s"} returned (newest first).` | ||
| ) | ||
| ); | ||
| server.registerTool( | ||
| "get_references", | ||
| { | ||
| title: "Get project references", | ||
| 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.", | ||
| inputSchema: getReferencesInputSchema.shape, | ||
| outputSchema: getReferencesOutputSchema.shape | ||
| }, | ||
| makeProjectToolHandler( | ||
| projectStore, | ||
| getReferences, | ||
| (r) => `${r.items.length} reference${r.items.length === 1 ? "" : "s"} returned` + (r.next_cursor ? " (more available \u2014 pass next_cursor for the next page)." : ".") | ||
| ) | ||
| ); | ||
| server.registerTool( | ||
| "search_references", | ||
| { | ||
| title: "Search project references", | ||
| 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.", | ||
| inputSchema: searchReferencesInputSchema.shape, | ||
| outputSchema: searchReferencesOutputSchema.shape | ||
| }, | ||
| makeProjectToolHandler( | ||
| projectStore, | ||
| searchReferences, | ||
| (r) => r.items.length === 0 ? `No references match "${r.query}".` : `${r.items.length} reference${r.items.length === 1 ? "" : "s"} matching "${r.query}".` | ||
| ) | ||
| ); | ||
| registerCaptureResource(server, captureStore); | ||
@@ -501,3 +886,11 @@ info("server initialized", { | ||
| "get_animation", | ||
| "get_media" | ||
| "get_media", | ||
| "get_brand_brief", | ||
| "get_voice", | ||
| "get_audience", | ||
| "get_design_intent", | ||
| "get_brand_tokens", | ||
| "get_decisions", | ||
| "get_references", | ||
| "search_references" | ||
| ], | ||
@@ -508,2 +901,15 @@ resources: ["zaai-capture://{id}"] | ||
| } | ||
| function makeProjectToolHandler(store, fn, summary) { | ||
| return async (args) => { | ||
| try { | ||
| const result = await fn(store, args); | ||
| return { | ||
| structuredContent: result, | ||
| content: [{ type: "text", text: summary(result) }] | ||
| }; | ||
| } catch (err) { | ||
| return toolErrorResponse(err); | ||
| } | ||
| }; | ||
| } | ||
| function makeFocusedGetterHandler(store, fn, label) { | ||
@@ -510,0 +916,0 @@ return async (args) => { |
+1
-1
| { | ||
| "name": "@zaai-dev/mcp", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "Zaai Dev MCP server — exposes your captures (and, in v1.5, your brand brief) to MCP-compatible AI tools.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
131049
58.89%989
67.34%