saglitzdesign-mcp 0.3.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 +29 -0
- package/README.md +191 -0
- package/dist/examples.js +67 -0
- package/dist/index.js +374 -0
- package/dist/knowledge.js +123 -0
- package/knowledge/books/design-of-everyday-things.md +155 -0
- package/knowledge/books/dont-make-me-think.md +154 -0
- package/knowledge/books/grid-typography-classics.md +152 -0
- package/knowledge/books/hooked-retention.md +196 -0
- package/knowledge/books/influence-persuasion.md +232 -0
- package/knowledge/books/positioning-messaging.md +178 -0
- package/knowledge/books/psychology-of-design.md +152 -0
- package/knowledge/books/refactoring-ui.md +157 -0
- package/knowledge/books/storybrand-copywriting.md +194 -0
- package/knowledge/components/buttons.md +90 -0
- package/knowledge/components/cards-lists-modals.md +63 -0
- package/knowledge/components/forms-inputs.md +61 -0
- package/knowledge/components/navigation.md +54 -0
- package/knowledge/craft/design-critique-scoring.md +237 -0
- package/knowledge/craft/typography-craft.md +177 -0
- package/knowledge/craft/ux-writing.md +185 -0
- package/knowledge/craft/visual-craft-standards.md +161 -0
- package/knowledge/design-languages/android-app-design.md +197 -0
- package/knowledge/design-languages/apple-hig-liquid-glass.md +94 -0
- package/knowledge/design-languages/design-tokens-theming.md +108 -0
- package/knowledge/design-languages/fluent-2.md +82 -0
- package/knowledge/design-languages/ios-app-design.md +205 -0
- package/knowledge/design-languages/macos-app-design.md +202 -0
- package/knowledge/design-languages/material-3.md +113 -0
- package/knowledge/design-languages/web-trends-2026.md +87 -0
- package/knowledge/examples/ios.json +299 -0
- package/knowledge/examples/web.json +321 -0
- package/knowledge/geo/geo-fundamentals.md +120 -0
- package/knowledge/geo/geo-tactics-checklist.md +148 -0
- package/knowledge/marketing/ad-creative.md +155 -0
- package/knowledge/marketing/branding-identity.md +153 -0
- package/knowledge/marketing/email-marketing.md +155 -0
- package/knowledge/patterns/mobile/android-patterns.md +181 -0
- package/knowledge/patterns/mobile/auth-patterns.md +69 -0
- package/knowledge/patterns/mobile/checkout-payments.md +77 -0
- package/knowledge/patterns/mobile/empty-states-buttons.md +80 -0
- package/knowledge/patterns/mobile/navigation-home.md +78 -0
- package/knowledge/patterns/mobile/onboarding-paywall.md +85 -0
- package/knowledge/patterns/mobile/settings-lists.md +71 -0
- package/knowledge/patterns/web/dashboards.md +141 -0
- package/knowledge/patterns/web/feature-sections.md +122 -0
- package/knowledge/patterns/web/hero-sections.md +121 -0
- package/knowledge/patterns/web/landing-signup.md +168 -0
- package/knowledge/patterns/web/pricing-sections.md +122 -0
- package/knowledge/patterns/web/social-proof-footer.md +156 -0
- package/knowledge/process/marketing-website-roadmap.md +72 -0
- package/knowledge/process/product-design-roadmap.md +117 -0
- package/knowledge/seo/on-page-seo.md +124 -0
- package/knowledge/seo/seo-for-designers.md +159 -0
- package/knowledge/seo/technical-seo.md +194 -0
- package/knowledge/ux/accessibility.md +68 -0
- package/knowledge/ux/color-systems.md +58 -0
- package/knowledge/ux/conversion-ux.md +78 -0
- package/knowledge/ux/mobile-ux.md +69 -0
- package/knowledge/ux/motion-microinteractions.md +67 -0
- package/knowledge/ux/principles-heuristics.md +57 -0
- package/knowledge/ux/spacing-layout.md +68 -0
- package/knowledge/ux/typography.md +63 -0
- package/package.json +63 -0
- package/scripts/regenerate-examples.md +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { loadKnowledge, searchKnowledge, sections } from "./knowledge.js";
|
|
9
|
+
import { loadExamples, searchExamples, imageMime } from "./examples.js";
|
|
10
|
+
// knowledge/ sits next to dist/ (repo root) both in dev (tsx) and after build
|
|
11
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const knowledgeDir = [join(here, "..", "knowledge"), join(here, "..", "..", "knowledge")].find(existsSync);
|
|
13
|
+
if (!knowledgeDir) {
|
|
14
|
+
console.error("SaglitzDesign: knowledge/ directory not found");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const docs = loadKnowledge(knowledgeDir);
|
|
18
|
+
const examplesDir = join(knowledgeDir, "examples");
|
|
19
|
+
const examples = loadExamples(examplesDir);
|
|
20
|
+
const server = new McpServer({
|
|
21
|
+
name: "saglitzdesign",
|
|
22
|
+
version: "0.3.1",
|
|
23
|
+
});
|
|
24
|
+
function docHeader(d) {
|
|
25
|
+
return `# ${d.title}\n_id: ${d.id} · category: ${d.category} · platform: ${d.platform} · tags: ${d.tags.join(", ")}_\n`;
|
|
26
|
+
}
|
|
27
|
+
function fullDoc(d) {
|
|
28
|
+
const src = d.sources.length ? `\n\n**Sources:** ${d.sources.join(" · ")}` : "";
|
|
29
|
+
return `${docHeader(d)}\n${d.body}${src}`;
|
|
30
|
+
}
|
|
31
|
+
function text(s) {
|
|
32
|
+
return { content: [{ type: "text", text: s }] };
|
|
33
|
+
}
|
|
34
|
+
const CATEGORIES = ["design-language", "component", "ux", "seo", "geo", "pattern", "craft", "book", "process"];
|
|
35
|
+
const PLATFORMS = ["mobile", "web", "macos"];
|
|
36
|
+
// ── Tool 1: list ─────────────────────────────────────────────────────────────
|
|
37
|
+
server.tool("list_design_knowledge", "List every document in the SaglitzDesign knowledge base (design languages, UI components, UX, SEO, GEO, real-world patterns). Use this first to see what expertise is available, then fetch docs with get_design_doc.", {
|
|
38
|
+
category: z.enum(CATEGORIES).optional().describe("Filter by category"),
|
|
39
|
+
platform: z.enum(PLATFORMS).optional().describe("Filter by platform (docs marked 'both' always included)"),
|
|
40
|
+
}, async ({ category, platform }) => {
|
|
41
|
+
const filtered = docs.filter((d) => (!category || d.category === category) &&
|
|
42
|
+
(!platform || d.platform === "both" || d.platform === platform));
|
|
43
|
+
const byCategory = new Map();
|
|
44
|
+
for (const d of filtered) {
|
|
45
|
+
byCategory.set(d.category, [...(byCategory.get(d.category) ?? []), d]);
|
|
46
|
+
}
|
|
47
|
+
const lines = [`SaglitzDesign knowledge base — ${filtered.length} documents\n`];
|
|
48
|
+
for (const [cat, list] of byCategory) {
|
|
49
|
+
lines.push(`## ${cat}`);
|
|
50
|
+
for (const d of list) {
|
|
51
|
+
lines.push(`- **${d.id}** — ${d.title} (${d.platform}) [${d.tags.join(", ")}]`);
|
|
52
|
+
}
|
|
53
|
+
lines.push("");
|
|
54
|
+
}
|
|
55
|
+
return text(lines.join("\n"));
|
|
56
|
+
});
|
|
57
|
+
// ── Tool 2: search ───────────────────────────────────────────────────────────
|
|
58
|
+
server.tool("search_design_knowledge", "Search the design knowledge base with a natural-language query. Covers web, iOS, Android and macOS design: UI components (buttons, forms, navigation…), UX principles, accessibility, typography, color, spacing, motion, conversion, copywriting, SEO, GEO (AI-search optimization), design languages (Material 3, Apple HIG/Liquid Glass, Fluent 2, iOS/macOS app design), expert craft standards, distilled classic design & marketing books (Norman, Krug, Refactoring UI, Cialdini, StoryBrand, Positioning, Hooked…), design-process roadmaps, and real-world patterns researched from top apps/sites (Mobbin). Returns the best-matching docs with the most relevant section excerpted.", {
|
|
59
|
+
query: z.string().describe("What you need guidance on, e.g. 'primary button size mobile', 'pricing page layout', 'dark mode colors', 'llms.txt'"),
|
|
60
|
+
category: z.enum(CATEGORIES).optional().describe("Restrict to one category"),
|
|
61
|
+
platform: z.enum(PLATFORMS).optional().describe("Restrict to one platform"),
|
|
62
|
+
limit: z.number().int().min(1).max(10).optional().describe("Max results (default 5)"),
|
|
63
|
+
}, async ({ query, category, platform, limit }) => {
|
|
64
|
+
const results = searchKnowledge(docs, query, { category, platform, limit });
|
|
65
|
+
if (results.length === 0) {
|
|
66
|
+
return text(`No matches for "${query}". Try broader terms, or call list_design_knowledge to see available topics.`);
|
|
67
|
+
}
|
|
68
|
+
const out = results.map((r) => `${docHeader(r.doc)}\n**Most relevant section:**\n${r.excerpt}\n\n_Full doc: get_design_doc(id: "${r.doc.id}")_`);
|
|
69
|
+
return text(out.join("\n\n---\n\n"));
|
|
70
|
+
});
|
|
71
|
+
// ── Tool 3: get full doc ─────────────────────────────────────────────────────
|
|
72
|
+
server.tool("get_design_doc", "Fetch one knowledge-base document in full by its id (ids come from list_design_knowledge or search results).", {
|
|
73
|
+
id: z.string().describe("Document id, e.g. 'buttons', 'material-3', 'geo-tactics-checklist'"),
|
|
74
|
+
}, async ({ id }) => {
|
|
75
|
+
const doc = docs.find((d) => d.id === id);
|
|
76
|
+
if (!doc) {
|
|
77
|
+
const near = searchKnowledge(docs, id, { limit: 3 }).map((r) => r.doc.id);
|
|
78
|
+
return text(`No document with id "${id}".${near.length ? ` Did you mean: ${near.join(", ")}?` : ""} Use list_design_knowledge to browse.`);
|
|
79
|
+
}
|
|
80
|
+
return text(fullDoc(doc));
|
|
81
|
+
});
|
|
82
|
+
// ── Tool 4: component guidance ───────────────────────────────────────────────
|
|
83
|
+
server.tool("get_component_guidance", "Expert guidance for designing a specific UI component or screen pattern (button, form, navigation, card, modal, hero section, pricing page, onboarding, paywall, checkout, empty state, dashboard…). Combines component specs with real-world patterns observed in top apps and websites.", {
|
|
84
|
+
component: z.string().describe("Component or pattern name, e.g. 'primary button', 'signup form', 'bottom tab bar', 'hero section', 'paywall'"),
|
|
85
|
+
platform: z.enum(PLATFORMS).optional().describe("Target platform — strongly recommended"),
|
|
86
|
+
}, async ({ component, platform }) => {
|
|
87
|
+
const compResults = searchKnowledge(docs, component, { platform, category: "component", limit: 2 });
|
|
88
|
+
const patternResults = searchKnowledge(docs, component, { platform, category: "pattern", limit: 2 });
|
|
89
|
+
const generalResults = searchKnowledge(docs, component, { platform, limit: 2 });
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
const picked = [];
|
|
92
|
+
for (const r of [...compResults, ...patternResults, ...generalResults]) {
|
|
93
|
+
if (picked.length >= 3)
|
|
94
|
+
break;
|
|
95
|
+
if (!seen.has(r.doc.id)) {
|
|
96
|
+
seen.add(r.doc.id);
|
|
97
|
+
picked.push(r);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (picked.length === 0) {
|
|
101
|
+
return text(`No guidance found for "${component}". Call list_design_knowledge to see covered components and patterns.`);
|
|
102
|
+
}
|
|
103
|
+
const out = picked.map((r) => fullDoc(r.doc));
|
|
104
|
+
return text(out.join("\n\n═══════════════════════\n\n"));
|
|
105
|
+
});
|
|
106
|
+
// ── Tool 5: design language ──────────────────────────────────────────────────
|
|
107
|
+
server.tool("get_design_language", "Full reference for a modern design language / platform design system: Material 3 (& Expressive), Apple HIG + Liquid Glass, deep iOS / Android / macOS app design guides, Fluent 2, 2026 web design trends, or design-token/theming architecture.", {
|
|
108
|
+
language: z
|
|
109
|
+
.enum(["material-3", "apple-hig-liquid-glass", "ios-app-design", "android-app-design", "macos-app-design", "fluent-2", "web-trends-2026", "design-tokens-theming"])
|
|
110
|
+
.describe("Which design language / platform reference to fetch"),
|
|
111
|
+
}, async ({ language }) => {
|
|
112
|
+
const doc = docs.find((d) => d.id === language);
|
|
113
|
+
if (!doc)
|
|
114
|
+
return text(`Reference "${language}" is not loaded in the knowledge base yet.`);
|
|
115
|
+
return text(fullDoc(doc));
|
|
116
|
+
});
|
|
117
|
+
// ── Tool 6: design review checklist ─────────────────────────────────────────
|
|
118
|
+
const REVIEW_MAP = {
|
|
119
|
+
"mobile-app": [
|
|
120
|
+
"mobile-ux", "ios-app-design", "android-app-design", "android-patterns",
|
|
121
|
+
"buttons", "forms-inputs", "navigation", "cards-lists-modals",
|
|
122
|
+
"principles-heuristics", "accessibility", "typography", "color-systems",
|
|
123
|
+
"spacing-layout", "motion-microinteractions", "visual-craft-standards", "ux-writing",
|
|
124
|
+
],
|
|
125
|
+
"macos-app": [
|
|
126
|
+
"macos-app-design", "apple-hig-liquid-glass", "buttons", "forms-inputs",
|
|
127
|
+
"cards-lists-modals", "principles-heuristics", "accessibility", "typography",
|
|
128
|
+
"color-systems", "spacing-layout", "visual-craft-standards", "ux-writing",
|
|
129
|
+
],
|
|
130
|
+
website: [
|
|
131
|
+
"conversion-ux", "storybrand-copywriting", "buttons", "forms-inputs", "navigation",
|
|
132
|
+
"principles-heuristics", "accessibility", "typography", "color-systems", "spacing-layout",
|
|
133
|
+
"motion-microinteractions", "visual-craft-standards", "ux-writing",
|
|
134
|
+
"technical-seo", "on-page-seo", "seo-for-designers", "geo-tactics-checklist",
|
|
135
|
+
],
|
|
136
|
+
"landing-page": [
|
|
137
|
+
"conversion-ux", "storybrand-copywriting", "influence-persuasion", "buttons",
|
|
138
|
+
"typography", "color-systems", "spacing-layout", "visual-craft-standards",
|
|
139
|
+
"seo-for-designers", "on-page-seo", "geo-tactics-checklist", "accessibility",
|
|
140
|
+
],
|
|
141
|
+
dashboard: [
|
|
142
|
+
"navigation", "cards-lists-modals", "principles-heuristics", "typography",
|
|
143
|
+
"color-systems", "spacing-layout", "accessibility", "buttons", "forms-inputs",
|
|
144
|
+
"visual-craft-standards", "ux-writing",
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
const FOCUS_MAP = {
|
|
148
|
+
all: () => true,
|
|
149
|
+
ui: (d) => ["component", "design-language", "craft"].includes(d.category) || ["typography", "color-systems", "spacing-layout", "motion-microinteractions"].includes(d.id),
|
|
150
|
+
ux: (d) => ["ux", "component"].includes(d.category) || d.id === "ux-writing",
|
|
151
|
+
accessibility: (d) => d.id === "accessibility",
|
|
152
|
+
seo: (d) => d.category === "seo",
|
|
153
|
+
geo: (d) => d.category === "geo",
|
|
154
|
+
conversion: (d) => ["conversion-ux", "storybrand-copywriting", "influence-persuasion", "positioning-messaging"].includes(d.id) || d.category === "pattern",
|
|
155
|
+
copywriting: (d) => ["ux-writing", "storybrand-copywriting", "positioning-messaging"].includes(d.id),
|
|
156
|
+
};
|
|
157
|
+
server.tool("design_review_checklist", "Generate a structured design-review checklist for a project type (mobile app, website, landing page, dashboard), assembled from the knowledge base: key rules and anti-patterns per area. Use it to audit an existing design or as acceptance criteria for a new one.", {
|
|
158
|
+
project_type: z.enum(["mobile-app", "macos-app", "website", "landing-page", "dashboard"]).describe("What is being reviewed"),
|
|
159
|
+
focus: z.enum(["all", "ui", "ux", "accessibility", "seo", "geo", "conversion", "copywriting"]).optional().describe("Narrow the review to one dimension (default: all)"),
|
|
160
|
+
}, async ({ project_type, focus }) => {
|
|
161
|
+
const focusFn = FOCUS_MAP[focus ?? "all"];
|
|
162
|
+
const ids = REVIEW_MAP[project_type];
|
|
163
|
+
const picked = ids.map((id) => docs.find((d) => d.id === id)).filter((d) => !!d && focusFn(d));
|
|
164
|
+
if (picked.length === 0)
|
|
165
|
+
return text("No checklist sections available for that combination.");
|
|
166
|
+
const lines = [
|
|
167
|
+
`# Design review checklist — ${project_type}${focus && focus !== "all" ? ` (focus: ${focus})` : ""}`,
|
|
168
|
+
`Walk each area below. For full guidance on any area, call get_design_doc with its id.\n`,
|
|
169
|
+
];
|
|
170
|
+
for (const doc of picked) {
|
|
171
|
+
lines.push(`## ${doc.title} \`(${doc.id})\``);
|
|
172
|
+
const secs = sections(doc);
|
|
173
|
+
const anti = secs.find((s) => /anti-pattern/i.test(s.heading));
|
|
174
|
+
const checklist = secs.find((s) => /checklist|rules|hard requirements/i.test(s.heading));
|
|
175
|
+
if (checklist)
|
|
176
|
+
lines.push(`**Check:**\n${checklist.content}`);
|
|
177
|
+
if (anti)
|
|
178
|
+
lines.push(`**Reject if you see:**\n${anti.content}`);
|
|
179
|
+
if (!checklist && !anti) {
|
|
180
|
+
lines.push(secs.slice(0, 1).map((s) => `**${s.heading}:**\n${s.content.slice(0, 500)}`).join("\n"));
|
|
181
|
+
}
|
|
182
|
+
lines.push("");
|
|
183
|
+
}
|
|
184
|
+
return text(lines.join("\n"));
|
|
185
|
+
});
|
|
186
|
+
const CORE_FOUNDATION = ["typography", "color-systems", "spacing-layout", "design-tokens-theming"];
|
|
187
|
+
const CORE_CRAFT = ["visual-craft-standards", "typography-craft", "refactoring-ui"];
|
|
188
|
+
const CORE_VALIDATE = ["design-critique-scoring", "accessibility", "principles-heuristics", "dont-make-me-think"];
|
|
189
|
+
const ROADMAPS = {
|
|
190
|
+
website: {
|
|
191
|
+
intro: "Marketing/company website. Order matters: positioning → copy → structure/SEO → design → CRO loop. Upstream fixes beat downstream polish.",
|
|
192
|
+
fullGuides: ["marketing-website-roadmap", "product-design-roadmap"],
|
|
193
|
+
phases: [
|
|
194
|
+
{ title: "1. Positioning & strategy", goal: "One positioning statement, one conversion goal", docs: ["positioning-messaging", "marketing-website-roadmap"] },
|
|
195
|
+
{ title: "2. Message & copy", goal: "Homepage narrative + proof inventory before wireframes", docs: ["storybrand-copywriting", "influence-persuasion", "ux-writing"] },
|
|
196
|
+
{ title: "3. Architecture & SEO/GEO foundations", goal: "Page map by search intent; rendering, schema, llms.txt planned", docs: ["on-page-seo", "technical-seo", "geo-tactics-checklist", "navigation"] },
|
|
197
|
+
{ title: "4. Wireframe & visual design", goal: "Real copy in layouts; conversion patterns; craft pass", docs: ["conversion-ux", "hero-sections", "pricing-sections", "landing-signup", ...CORE_FOUNDATION, ...CORE_CRAFT] },
|
|
198
|
+
{ title: "5. Build & performance", goal: "CWV budget met; semantic, extractable HTML", docs: ["seo-for-designers", "accessibility", "motion-microinteractions"] },
|
|
199
|
+
{ title: "6. Launch & CRO loop", goal: "Instrumented funnel; one-variable tests; GEO visibility tracking", docs: ["marketing-website-roadmap", "geo-fundamentals", "design-critique-scoring"] },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
"landing-page": {
|
|
203
|
+
intro: "Single conversion-focused page. Condensed website roadmap: one goal, one narrative, ruthless proof.",
|
|
204
|
+
fullGuides: ["marketing-website-roadmap"],
|
|
205
|
+
phases: [
|
|
206
|
+
{ title: "1. Offer & message", goal: "Headline/subhead/CTA + risk reducers written first", docs: ["positioning-messaging", "storybrand-copywriting", "conversion-ux"] },
|
|
207
|
+
{ title: "2. Page narrative", goal: "Hero → proof → benefits → objections → final CTA", docs: ["conversion-ux", "hero-sections", "social-proof-footer", "influence-persuasion"] },
|
|
208
|
+
{ title: "3. Design & craft", goal: "CTA pops (squint test); mobile-first", docs: ["buttons", ...CORE_FOUNDATION, "visual-craft-standards", "refactoring-ui"] },
|
|
209
|
+
{ title: "4. Performance, SEO/GEO & launch", goal: "Lighthouse ≥90; schema + answer-first content; funnel instrumented", docs: ["seo-for-designers", "on-page-seo", "geo-tactics-checklist", "accessibility"] },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
"ios-app": {
|
|
213
|
+
intro: "iOS app, HIG/Liquid Glass era. Native navigation and platform conventions are non-negotiable; App Store presence is part of the design.",
|
|
214
|
+
fullGuides: ["product-design-roadmap"],
|
|
215
|
+
phases: [
|
|
216
|
+
{ title: "1. Discovery & positioning", goal: "Persona, job-to-be-done, success metric, competitor teardown", docs: ["product-design-roadmap", "positioning-messaging"] },
|
|
217
|
+
{ title: "2. IA & flows", goal: "≤5 tab destinations; critical flows mapped; trunk test", docs: ["navigation", "ios-app-design", "navigation-home"] },
|
|
218
|
+
{ title: "3. Wireframes, copy & edge states", goal: "Real copy; empty/loading/error/offline designed", docs: ["ux-writing", "empty-states-buttons", "dont-make-me-think"] },
|
|
219
|
+
{ title: "4. Design system on HIG baseline", goal: "Tokens + core components; Dynamic Type; dark mode", docs: ["apple-hig-liquid-glass", "ios-app-design", ...CORE_FOUNDATION] },
|
|
220
|
+
{ title: "5. Hi-fi design & craft", goal: "All states, all sizes; motion + haptics spec; reduced motion", docs: ["mobile-ux", "buttons", "forms-inputs", "cards-lists-modals", "motion-microinteractions", ...CORE_CRAFT] },
|
|
221
|
+
{ title: "6. Patterns for key flows", goal: "Onboarding/paywall/auth/checkout follow proven patterns", docs: ["onboarding-paywall", "auth-patterns", "checkout-payments", "settings-lists", "hooked-retention"] },
|
|
222
|
+
{ title: "7. Validate & ship", goal: "5-user tests; a11y audit; App Store assets; design QA on device", docs: [...CORE_VALIDATE, "ios-app-design"] },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
"android-app": {
|
|
226
|
+
intro: "Android app on Material 3 (Expressive). Same skeleton as iOS but Material navigation, shapes and motion physics.",
|
|
227
|
+
fullGuides: ["product-design-roadmap"],
|
|
228
|
+
phases: [
|
|
229
|
+
{ title: "1. Discovery & positioning", goal: "Persona, job-to-be-done, success metric", docs: ["product-design-roadmap", "positioning-messaging"] },
|
|
230
|
+
{ title: "2. IA & flows", goal: "Nav bar destinations; critical flows; predictive back correct", docs: ["android-app-design", "navigation", "navigation-home"] },
|
|
231
|
+
{ title: "3. Wireframes, copy & edge states", goal: "Real copy; all edge states", docs: ["ux-writing", "empty-states-buttons", "dont-make-me-think"] },
|
|
232
|
+
{ title: "4. Design system on M3 baseline", goal: "Dynamic color, shape scale, motion springs, dark theme, edge-to-edge", docs: ["material-3", "android-app-design", ...CORE_FOUNDATION] },
|
|
233
|
+
{ title: "5. Hi-fi design & craft", goal: "All states/sizes; 60fps; reduced motion", docs: ["mobile-ux", "buttons", "forms-inputs", "cards-lists-modals", "motion-microinteractions", ...CORE_CRAFT] },
|
|
234
|
+
{ title: "6. Patterns for key flows", goal: "Onboarding/paywall/auth/checkout via proven patterns; Android conventions respected", docs: ["android-patterns", "onboarding-paywall", "auth-patterns", "checkout-payments", "settings-lists", "hooked-retention"] },
|
|
235
|
+
{ title: "7. Validate & ship", goal: "Usability tests; a11y (TalkBack); Play Store assets", docs: [...CORE_VALIDATE, "android-app-design"] },
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
"macos-app": {
|
|
239
|
+
intro: "macOS app. Keyboard-first, menu bar complete, multi-window sane, resizable everything — that's what 'native' means on Mac.",
|
|
240
|
+
fullGuides: ["product-design-roadmap"],
|
|
241
|
+
phases: [
|
|
242
|
+
{ title: "1. Discovery & app model", goal: "Document-based vs shoebox vs utility decided; persona + metric", docs: ["product-design-roadmap", "macos-app-design"] },
|
|
243
|
+
{ title: "2. IA: windows, menus, shortcuts", goal: "Window anatomy, full menu bar map, shortcut table BEFORE wireframes", docs: ["macos-app-design", "navigation"] },
|
|
244
|
+
{ title: "3. Wireframes, copy & edge states", goal: "Real copy; empty/error/loading; resizing behavior per pane", docs: ["ux-writing", "cards-lists-modals", "dont-make-me-think"] },
|
|
245
|
+
{ title: "4. Design system on macOS HIG", goal: "Tokens; density for desktop; dark mode; Liquid Glass adoption", docs: ["macos-app-design", "apple-hig-liquid-glass", ...CORE_FOUNDATION] },
|
|
246
|
+
{ title: "5. Hi-fi design & craft", goal: "Pointer+keyboard interactions; drag & drop; undo everywhere", docs: ["buttons", "forms-inputs", "motion-microinteractions", ...CORE_CRAFT] },
|
|
247
|
+
{ title: "6. Validate & ship", goal: "Keyboard-only pass; VoiceOver; multi-window/multi-display QA", docs: CORE_VALIDATE },
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
"saas-web-app": {
|
|
251
|
+
intro: "SaaS product UI (dashboard/app shell). Density, navigation clarity and empty states decide perceived quality.",
|
|
252
|
+
fullGuides: ["product-design-roadmap"],
|
|
253
|
+
phases: [
|
|
254
|
+
{ title: "1. Discovery & jobs", goal: "Core workflows ranked; success metric per workflow", docs: ["product-design-roadmap", "positioning-messaging"] },
|
|
255
|
+
{ title: "2. IA & app shell", goal: "Sidebar structure, command palette, breadcrumbs", docs: ["navigation", "dashboards"] },
|
|
256
|
+
{ title: "3. Wireframes, copy & edge states", goal: "Real data shapes; empty/loading/error/zero-results for every view", docs: ["ux-writing", "cards-lists-modals", "empty-states-buttons"] },
|
|
257
|
+
{ title: "4. Design system", goal: "Tokens incl. density mode; tables/forms/charts standardized", docs: [...CORE_FOUNDATION, "forms-inputs", "buttons"] },
|
|
258
|
+
{ title: "5. Hi-fi & craft", goal: "Dense screens first; keyboard support; dark mode", docs: [...CORE_CRAFT, "motion-microinteractions", "principles-heuristics"] },
|
|
259
|
+
{ title: "6. Onboarding & retention", goal: "Time-to-value <60s; activation instrumented", docs: ["onboarding-paywall", "hooked-retention", "conversion-ux"] },
|
|
260
|
+
{ title: "7. Validate & iterate", goal: "Task-based tests; heuristic score; funnel review cadence", docs: CORE_VALIDATE },
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
server.tool("get_design_roadmap", "The SaglitzDesign roadmap: a phased, expert design process for a given project type (website, landing page, iOS app, Android app, macOS app, SaaS web app). Each phase has a goal and the exact knowledge-base docs to consult. Use this FIRST when starting any design project, then fetch phase docs as you reach them.", {
|
|
265
|
+
project_type: z.enum(["website", "landing-page", "ios-app", "android-app", "macos-app", "saas-web-app"]).describe("What is being designed"),
|
|
266
|
+
}, async ({ project_type }) => {
|
|
267
|
+
const rm = ROADMAPS[project_type];
|
|
268
|
+
const known = new Set(docs.map((d) => d.id));
|
|
269
|
+
const lines = [
|
|
270
|
+
`# SaglitzDesign roadmap — ${project_type}`,
|
|
271
|
+
"",
|
|
272
|
+
rm.intro,
|
|
273
|
+
"",
|
|
274
|
+
`**Full written guides:** ${rm.fullGuides.map((g) => `get_design_doc("${g}")`).join(", ")}`,
|
|
275
|
+
"",
|
|
276
|
+
];
|
|
277
|
+
for (const phase of rm.phases) {
|
|
278
|
+
lines.push(`## ${phase.title}`);
|
|
279
|
+
lines.push(`**Goal / exit criteria:** ${phase.goal}`);
|
|
280
|
+
const available = phase.docs.filter((id) => known.has(id));
|
|
281
|
+
lines.push(`**Consult:** ${available.map((id) => `\`${id}\``).join(", ")}`);
|
|
282
|
+
lines.push("");
|
|
283
|
+
}
|
|
284
|
+
lines.push("_Fetch any doc with get_design_doc(id). Audit finished work with design_review_checklist._");
|
|
285
|
+
return text(lines.join("\n"));
|
|
286
|
+
});
|
|
287
|
+
// ── Tool 8: SEO / GEO guide ──────────────────────────────────────────────────
|
|
288
|
+
server.tool("seo_geo_guide", "Search-optimization expertise for websites: classic SEO (technical, on-page, design-impact) and GEO — Generative Engine Optimization for AI answer engines (ChatGPT, Perplexity, Google AI Overviews). Returns the full relevant guides.", {
|
|
289
|
+
scope: z.enum(["seo", "geo", "both"]).describe("Which discipline"),
|
|
290
|
+
topic: z.string().optional().describe("Optional narrower topic, e.g. 'core web vitals', 'llms.txt', 'structured data'"),
|
|
291
|
+
}, async ({ scope, topic }) => {
|
|
292
|
+
const cats = scope === "both" ? ["seo", "geo"] : [scope];
|
|
293
|
+
let picked = docs.filter((d) => cats.includes(d.category));
|
|
294
|
+
if (topic) {
|
|
295
|
+
const hits = new Set(cats.flatMap((c) => searchKnowledge(docs, topic, { category: c, limit: 2 }).map((r) => r.doc.id)));
|
|
296
|
+
const narrowed = picked.filter((d) => hits.has(d.id));
|
|
297
|
+
if (narrowed.length > 0)
|
|
298
|
+
picked = narrowed;
|
|
299
|
+
}
|
|
300
|
+
if (picked.length === 0)
|
|
301
|
+
return text("No SEO/GEO docs loaded yet.");
|
|
302
|
+
return text(picked.map(fullDoc).join("\n\n═══════════════════════\n\n"));
|
|
303
|
+
});
|
|
304
|
+
// ── Tool 9: visual design examples ──────────────────────────────────────────
|
|
305
|
+
server.tool("get_design_examples", "Fetch REAL screenshot examples of a design pattern from top apps and websites (curated from Mobbin). Returns the actual images plus notes on what each does well — use these as visual references when designing paywalls, onboarding, auth, navigation, checkout, settings, empty states, heroes, pricing, features, social proof, signup pages, dashboards and footers.", {
|
|
306
|
+
query: z.string().describe("Pattern to see examples of, e.g. 'paywall', 'pricing section', 'dark hero', 'empty state'"),
|
|
307
|
+
platform: z.enum(["mobile", "web"]).optional().describe("'mobile' for iOS app screens, 'web' for website examples"),
|
|
308
|
+
limit: z.number().int().min(1).max(6).optional().describe("Max examples to return (default 4; images are large)"),
|
|
309
|
+
}, async ({ query, platform, limit }) => {
|
|
310
|
+
// examples are stored with platform "ios" | "web"; map "mobile"→"ios"
|
|
311
|
+
const mapped = platform === "mobile" ? "ios" : platform;
|
|
312
|
+
const hits = searchExamples(examples, query, { platform: mapped, limit: limit ?? 4 });
|
|
313
|
+
if (hits.length === 0) {
|
|
314
|
+
const patterns = [...new Set(examples.map((e) => e.pattern))].sort().join(", ");
|
|
315
|
+
return text(`No visual examples match "${query}". Available patterns: ${patterns || "(example library is empty)"}.`);
|
|
316
|
+
}
|
|
317
|
+
const content = [];
|
|
318
|
+
for (const e of hits) {
|
|
319
|
+
content.push({
|
|
320
|
+
type: "text",
|
|
321
|
+
text: `### ${e.title}\n${e.description}\n_Pattern: ${e.pattern} · Platform: ${e.platform} · Source: ${e.mobbin_url}_`,
|
|
322
|
+
});
|
|
323
|
+
const imagePath = e.image ? join(examplesDir, e.image) : "";
|
|
324
|
+
if (imagePath && existsSync(imagePath)) {
|
|
325
|
+
try {
|
|
326
|
+
const data = readFileSync(imagePath).toString("base64");
|
|
327
|
+
content.push({ type: "image", data, mimeType: imageMime(e.image) });
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
content.push({ type: "text", text: `(image unreadable — view it at ${e.mobbin_url})` });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
content.push({
|
|
335
|
+
type: "text",
|
|
336
|
+
text: `(screenshot not bundled in this installation — view it at ${e.mobbin_url})`,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return { content };
|
|
341
|
+
});
|
|
342
|
+
// ── Tool 10: knowledge freshness ─────────────────────────────────────────────
|
|
343
|
+
const STALE_DAYS = {
|
|
344
|
+
seo: 120, geo: 120, "design-language": 240, pattern: 300,
|
|
345
|
+
component: 365, ux: 365, craft: 365, book: 730, process: 365, marketing: 240,
|
|
346
|
+
};
|
|
347
|
+
server.tool("knowledge_freshness", "Report how fresh each knowledge document is (age since last verification vs its category's staleness threshold). Use this to decide which docs need re-research; refresh workflow is documented in the repo's /refresh-knowledge command.", {
|
|
348
|
+
only_stale: z.boolean().optional().describe("Return only docs past their staleness threshold (default false)"),
|
|
349
|
+
}, async ({ only_stale }) => {
|
|
350
|
+
const now = Date.now();
|
|
351
|
+
const rows = docs
|
|
352
|
+
.map((d) => {
|
|
353
|
+
const ageDays = Math.floor((now - new Date(d.updated).getTime()) / 86_400_000);
|
|
354
|
+
const threshold = STALE_DAYS[d.category] ?? 365;
|
|
355
|
+
return { d, ageDays, threshold, stale: ageDays > threshold };
|
|
356
|
+
})
|
|
357
|
+
.filter((r) => !only_stale || r.stale)
|
|
358
|
+
.sort((a, b) => (b.ageDays / b.threshold) - (a.ageDays / a.threshold));
|
|
359
|
+
if (rows.length === 0)
|
|
360
|
+
return text("All documents are within their freshness thresholds. ✅");
|
|
361
|
+
const staleCount = rows.filter((r) => r.stale).length;
|
|
362
|
+
const lines = [
|
|
363
|
+
`# Knowledge freshness — ${docs.length} docs, ${staleCount} stale`,
|
|
364
|
+
"",
|
|
365
|
+
"| doc | category | updated | age (days) | threshold | status |",
|
|
366
|
+
"|---|---|---|---|---|---|",
|
|
367
|
+
...rows.map((r) => `| ${r.d.id} | ${r.d.category} | ${r.d.updated} | ${r.ageDays} | ${r.threshold} | ${r.stale ? "⚠️ STALE" : "ok"} |`),
|
|
368
|
+
];
|
|
369
|
+
return text(lines.join("\n"));
|
|
370
|
+
});
|
|
371
|
+
// ── start ────────────────────────────────────────────────────────────────────
|
|
372
|
+
const transport = new StdioServerTransport();
|
|
373
|
+
await server.connect(transport);
|
|
374
|
+
console.error(`SaglitzDesign MCP server running — ${docs.length} knowledge docs loaded from ${knowledgeDir}`);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join, extname } from "node:path";
|
|
3
|
+
/** Minimal frontmatter parser — supports strings, quoted strings and inline arrays. */
|
|
4
|
+
function parseFrontmatter(raw) {
|
|
5
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
6
|
+
if (!match)
|
|
7
|
+
return { meta: {}, body: raw };
|
|
8
|
+
const meta = {};
|
|
9
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
10
|
+
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
11
|
+
if (!kv)
|
|
12
|
+
continue;
|
|
13
|
+
const key = kv[1];
|
|
14
|
+
let value = kv[2].trim();
|
|
15
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
16
|
+
meta[key] = value
|
|
17
|
+
.slice(1, -1)
|
|
18
|
+
.split(",")
|
|
19
|
+
.map((v) => v.trim().replace(/^["']|["']$/g, ""))
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
meta[key] = value.replace(/^["']|["']$/g, "");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { meta, body: raw.slice(match[0].length) };
|
|
27
|
+
}
|
|
28
|
+
function walk(dir, out = []) {
|
|
29
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
30
|
+
const full = join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory())
|
|
32
|
+
walk(full, out);
|
|
33
|
+
else if (extname(entry.name) === ".md")
|
|
34
|
+
out.push(full);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
export function loadKnowledge(rootDir) {
|
|
39
|
+
const docs = [];
|
|
40
|
+
for (const path of walk(rootDir)) {
|
|
41
|
+
try {
|
|
42
|
+
const raw = readFileSync(path, "utf8");
|
|
43
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
44
|
+
if (!meta.id)
|
|
45
|
+
continue;
|
|
46
|
+
docs.push({
|
|
47
|
+
id: String(meta.id),
|
|
48
|
+
title: String(meta.title ?? meta.id),
|
|
49
|
+
category: String(meta.category ?? "general"),
|
|
50
|
+
platform: String(meta.platform ?? "both"),
|
|
51
|
+
tags: Array.isArray(meta.tags) ? meta.tags : [],
|
|
52
|
+
sources: Array.isArray(meta.sources) ? meta.sources : [],
|
|
53
|
+
updated: typeof meta.updated === "string" && meta.updated
|
|
54
|
+
? meta.updated
|
|
55
|
+
: statSync(path).mtime.toISOString().slice(0, 10),
|
|
56
|
+
body: body.trim(),
|
|
57
|
+
path,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// skip unreadable files
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return docs.sort((a, b) => a.id.localeCompare(b.id));
|
|
65
|
+
}
|
|
66
|
+
function tokenize(text) {
|
|
67
|
+
return text
|
|
68
|
+
.toLowerCase()
|
|
69
|
+
.split(/[^a-z0-9ğüşıöç]+/)
|
|
70
|
+
.filter((t) => t.length > 1);
|
|
71
|
+
}
|
|
72
|
+
/** Split a doc body into ## sections; returns [heading, content] pairs. */
|
|
73
|
+
export function sections(doc) {
|
|
74
|
+
const parts = doc.body.split(/^## /m);
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const part of parts.slice(1)) {
|
|
77
|
+
const nl = part.indexOf("\n");
|
|
78
|
+
out.push({
|
|
79
|
+
heading: part.slice(0, nl === -1 ? undefined : nl).trim(),
|
|
80
|
+
content: nl === -1 ? "" : part.slice(nl + 1).trim(),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
export function searchKnowledge(docs, query, opts = {}) {
|
|
86
|
+
const terms = [...new Set(tokenize(query))];
|
|
87
|
+
if (terms.length === 0)
|
|
88
|
+
return [];
|
|
89
|
+
const results = [];
|
|
90
|
+
for (const doc of docs) {
|
|
91
|
+
if (opts.category && doc.category !== opts.category)
|
|
92
|
+
continue;
|
|
93
|
+
if (opts.platform && doc.platform !== "both" && doc.platform !== opts.platform)
|
|
94
|
+
continue;
|
|
95
|
+
const titleTokens = new Set(tokenize(doc.title + " " + doc.id));
|
|
96
|
+
const tagTokens = new Set(doc.tags.flatMap(tokenize));
|
|
97
|
+
const bodyLower = doc.body.toLowerCase();
|
|
98
|
+
let score = 0;
|
|
99
|
+
for (const term of terms) {
|
|
100
|
+
if (titleTokens.has(term))
|
|
101
|
+
score += 8;
|
|
102
|
+
if (tagTokens.has(term))
|
|
103
|
+
score += 6;
|
|
104
|
+
const occurrences = bodyLower.split(term).length - 1;
|
|
105
|
+
score += Math.min(occurrences, 10);
|
|
106
|
+
}
|
|
107
|
+
if (score === 0)
|
|
108
|
+
continue;
|
|
109
|
+
// Best-matching section as excerpt
|
|
110
|
+
let best = { heading: "", content: doc.body.slice(0, 600), hits: -1 };
|
|
111
|
+
for (const sec of sections(doc)) {
|
|
112
|
+
const secLower = (sec.heading + "\n" + sec.content).toLowerCase();
|
|
113
|
+
const hits = terms.reduce((n, t) => n + (secLower.split(t).length - 1), 0);
|
|
114
|
+
if (hits > best.hits)
|
|
115
|
+
best = { ...sec, hits };
|
|
116
|
+
}
|
|
117
|
+
const excerpt = (best.heading ? `## ${best.heading}\n` : "") +
|
|
118
|
+
(best.content.length > 900 ? best.content.slice(0, 900) + "\n…(truncated)" : best.content);
|
|
119
|
+
results.push({ doc, score, excerpt });
|
|
120
|
+
}
|
|
121
|
+
results.sort((a, b) => b.score - a.score);
|
|
122
|
+
return results.slice(0, opts.limit ?? 5);
|
|
123
|
+
}
|