design system token v0.7
This commit is contained in:
150
mcp/server.ts
150
mcp/server.ts
@@ -257,10 +257,160 @@ server.tool(
|
||||
},
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Brand tools & resources
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const BRAND_SKILL_PATH = path.join(ROOT, 'skill', 'BRAND.md')
|
||||
|
||||
function readBrandSkill(): string {
|
||||
try {
|
||||
return fs.readFileSync(BRAND_SKILL_PATH, 'utf-8')
|
||||
} catch {
|
||||
return '(skill/BRAND.md not found — hand-curated brand skill is missing)'
|
||||
}
|
||||
}
|
||||
|
||||
server.tool(
|
||||
'get_brand_rules',
|
||||
'Returns the Greyhaven brand voice, tone, and messaging rules. Use this BEFORE generating any user-facing marketing copy, CTAs, landing page content, or product explanations. Covers positioning, brand axes, tone, writing rules, reasoning patterns, CTA guidance, logo usage, and a self-check list.',
|
||||
{
|
||||
section: z
|
||||
.enum([
|
||||
'all',
|
||||
'positioning',
|
||||
'axes',
|
||||
'tone',
|
||||
'writing-rules',
|
||||
'reasoning-patterns',
|
||||
'cta',
|
||||
'logo',
|
||||
'self-check',
|
||||
])
|
||||
.optional()
|
||||
.describe('Optional section filter. Default returns the full brand skill.'),
|
||||
},
|
||||
async ({ section }) => {
|
||||
const full = readBrandSkill()
|
||||
|
||||
if (!section || section === 'all') {
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: full }],
|
||||
}
|
||||
}
|
||||
|
||||
// Section anchors in BRAND.md (markdown H2 headings)
|
||||
const anchors: Record<string, RegExp> = {
|
||||
positioning: /## 2\. Core Positioning[\s\S]*?(?=\n## |\n---)/,
|
||||
axes: /## 3\. The Three Brand Axes[\s\S]*?(?=\n## |\n---)/,
|
||||
tone: /## 4\. Tone of Voice[\s\S]*?(?=\n## |\n---)/,
|
||||
'writing-rules': /## 5\. Writing Rules[\s\S]*?(?=\n## |\n---)/,
|
||||
'reasoning-patterns': /## 6\. Patterns for Reasoning[\s\S]*?(?=\n## |\n---)/,
|
||||
cta: /## 7\. CTA Guidance[\s\S]*?(?=\n## |\n---)/,
|
||||
logo: /## 10\. Logo Usage[\s\S]*?(?=\n## |\n---)/,
|
||||
'self-check': /## 11\. Self-check[\s\S]*?(?=\n## |\n---)/,
|
||||
}
|
||||
|
||||
const re = anchors[section]
|
||||
const match = re ? full.match(re) : null
|
||||
|
||||
if (!match) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Section "${section}" not found. Returning full brand skill instead.\n\n${full}`,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: match[0] }],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
server.tool(
|
||||
'validate_copy',
|
||||
'Checks a piece of user-facing copy against Greyhaven brand rules. Flags hype words, sales language, vague superlatives, and other brand violations. Use on marketing copy, CTAs, headlines, product descriptions before shipping.',
|
||||
{ text: z.string().describe('The copy to validate') },
|
||||
async ({ text }) => {
|
||||
const lower = text.toLowerCase()
|
||||
|
||||
const bannedWords = [
|
||||
'unleash', 'transform', 'revolutionary', 'revolutionize',
|
||||
'seamless', 'seamlessly', 'game-changing', 'cutting-edge',
|
||||
'next-gen', 'next-generation', 'leverage', 'synergy', 'unlock',
|
||||
'supercharge', 'empower', 'empowered', 'unprecedented',
|
||||
'best-in-class', 'industry-leading', 'world-class',
|
||||
'lightning-fast', 'blazing fast',
|
||||
]
|
||||
|
||||
const vagueSuperlatives = [
|
||||
'amazing', 'incredible', 'awesome', 'stunning', 'beautiful',
|
||||
'powerful', 'robust', 'cutting edge', 'state-of-the-art',
|
||||
]
|
||||
|
||||
const urgencyPhrases = [
|
||||
'limited time', 'act now', "don't miss out", 'hurry',
|
||||
'last chance', 'today only',
|
||||
]
|
||||
|
||||
const findings: string[] = []
|
||||
|
||||
for (const w of bannedWords) {
|
||||
if (lower.includes(w)) {
|
||||
findings.push(`⚠ Banned hype/sales word: "${w}"`)
|
||||
}
|
||||
}
|
||||
for (const w of vagueSuperlatives) {
|
||||
if (lower.includes(w)) {
|
||||
findings.push(`⚠ Vague superlative: "${w}" — replace with specifics`)
|
||||
}
|
||||
}
|
||||
for (const p of urgencyPhrases) {
|
||||
if (lower.includes(p)) {
|
||||
findings.push(`⚠ Urgency framing: "${p}" — Greyhaven does not use urgency`)
|
||||
}
|
||||
}
|
||||
|
||||
// Exclamation marks
|
||||
const exclamations = (text.match(/!/g) || []).length
|
||||
if (exclamations > 0) {
|
||||
findings.push(`⚠ Found ${exclamations} exclamation mark(s) — Greyhaven copy does not use them`)
|
||||
}
|
||||
|
||||
if (findings.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: 'No obvious brand violations found. Still run the self-check list from get_brand_rules({section: "self-check"}) before shipping.',
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Found ${findings.length} potential brand violation(s):\n\n${findings.join('\n')}\n\nFor detailed guidance, call get_brand_rules() or get_brand_rules({section: "tone"}).`,
|
||||
}],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resources
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
server.resource('brand://guidelines', 'brand://guidelines', async (uri) => {
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
mimeType: 'text/markdown',
|
||||
text: readBrandSkill(),
|
||||
}],
|
||||
}
|
||||
})
|
||||
|
||||
server.resource('tokens://all', 'tokens://all', async (uri) => {
|
||||
const tokens = getTokens(ROOT)
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user