@forgespace/branding-mcp 0.4.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.
Files changed (244) hide show
  1. package/.env.example +3 -0
  2. package/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  3. package/.github/workflows/ci.yml +73 -0
  4. package/.github/workflows/release-automation.yml +56 -0
  5. package/.github/workflows/security-scan.yml +37 -0
  6. package/.gitleaks.toml +14 -0
  7. package/.prettierrc.json +10 -0
  8. package/CHANGELOG.md +66 -0
  9. package/CONTRIBUTING.md +203 -0
  10. package/LICENSE +21 -0
  11. package/README.md +105 -0
  12. package/data/README.md +13 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +49 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/lib/branding-core/ai/brand-interpreter.d.ts +5 -0
  18. package/dist/lib/branding-core/ai/brand-interpreter.d.ts.map +1 -0
  19. package/dist/lib/branding-core/ai/brand-interpreter.js +16 -0
  20. package/dist/lib/branding-core/ai/brand-interpreter.js.map +1 -0
  21. package/dist/lib/branding-core/ai/claude-interpreter.d.ts +5 -0
  22. package/dist/lib/branding-core/ai/claude-interpreter.d.ts.map +1 -0
  23. package/dist/lib/branding-core/ai/claude-interpreter.js +55 -0
  24. package/dist/lib/branding-core/ai/claude-interpreter.js.map +1 -0
  25. package/dist/lib/branding-core/ai/intent-applier.d.ts +4 -0
  26. package/dist/lib/branding-core/ai/intent-applier.d.ts.map +1 -0
  27. package/dist/lib/branding-core/ai/intent-applier.js +29 -0
  28. package/dist/lib/branding-core/ai/intent-applier.js.map +1 -0
  29. package/dist/lib/branding-core/ai/keyword-interpreter.d.ts +4 -0
  30. package/dist/lib/branding-core/ai/keyword-interpreter.d.ts.map +1 -0
  31. package/dist/lib/branding-core/ai/keyword-interpreter.js +85 -0
  32. package/dist/lib/branding-core/ai/keyword-interpreter.js.map +1 -0
  33. package/dist/lib/branding-core/ai/prompts.d.ts +4 -0
  34. package/dist/lib/branding-core/ai/prompts.d.ts.map +1 -0
  35. package/dist/lib/branding-core/ai/prompts.js +79 -0
  36. package/dist/lib/branding-core/ai/prompts.js.map +1 -0
  37. package/dist/lib/branding-core/ai/types.d.ts +27 -0
  38. package/dist/lib/branding-core/ai/types.d.ts.map +1 -0
  39. package/dist/lib/branding-core/ai/types.js +2 -0
  40. package/dist/lib/branding-core/ai/types.js.map +1 -0
  41. package/dist/lib/branding-core/documents/html-generator.d.ts +3 -0
  42. package/dist/lib/branding-core/documents/html-generator.d.ts.map +1 -0
  43. package/dist/lib/branding-core/documents/html-generator.js +31 -0
  44. package/dist/lib/branding-core/documents/html-generator.js.map +1 -0
  45. package/dist/lib/branding-core/documents/pdf-generator.d.ts +3 -0
  46. package/dist/lib/branding-core/documents/pdf-generator.d.ts.map +1 -0
  47. package/dist/lib/branding-core/documents/pdf-generator.js +20 -0
  48. package/dist/lib/branding-core/documents/pdf-generator.js.map +1 -0
  49. package/dist/lib/branding-core/exporters/css-variables.d.ts +3 -0
  50. package/dist/lib/branding-core/exporters/css-variables.d.ts.map +1 -0
  51. package/dist/lib/branding-core/exporters/css-variables.js +62 -0
  52. package/dist/lib/branding-core/exporters/css-variables.js.map +1 -0
  53. package/dist/lib/branding-core/exporters/design-tokens.d.ts +3 -0
  54. package/dist/lib/branding-core/exporters/design-tokens.d.ts.map +1 -0
  55. package/dist/lib/branding-core/exporters/design-tokens.js +75 -0
  56. package/dist/lib/branding-core/exporters/design-tokens.js.map +1 -0
  57. package/dist/lib/branding-core/exporters/figma-tokens.d.ts +9 -0
  58. package/dist/lib/branding-core/exporters/figma-tokens.d.ts.map +1 -0
  59. package/dist/lib/branding-core/exporters/figma-tokens.js +69 -0
  60. package/dist/lib/branding-core/exporters/figma-tokens.js.map +1 -0
  61. package/dist/lib/branding-core/exporters/react-theme.d.ts +3 -0
  62. package/dist/lib/branding-core/exporters/react-theme.d.ts.map +1 -0
  63. package/dist/lib/branding-core/exporters/react-theme.js +61 -0
  64. package/dist/lib/branding-core/exporters/react-theme.js.map +1 -0
  65. package/dist/lib/branding-core/exporters/sass-variables.d.ts +3 -0
  66. package/dist/lib/branding-core/exporters/sass-variables.d.ts.map +1 -0
  67. package/dist/lib/branding-core/exporters/sass-variables.js +65 -0
  68. package/dist/lib/branding-core/exporters/sass-variables.js.map +1 -0
  69. package/dist/lib/branding-core/exporters/tailwind-preset.d.ts +3 -0
  70. package/dist/lib/branding-core/exporters/tailwind-preset.d.ts.map +1 -0
  71. package/dist/lib/branding-core/exporters/tailwind-preset.js +55 -0
  72. package/dist/lib/branding-core/exporters/tailwind-preset.js.map +1 -0
  73. package/dist/lib/branding-core/generators/border-system.d.ts +3 -0
  74. package/dist/lib/branding-core/generators/border-system.d.ts.map +1 -0
  75. package/dist/lib/branding-core/generators/border-system.js +37 -0
  76. package/dist/lib/branding-core/generators/border-system.js.map +1 -0
  77. package/dist/lib/branding-core/generators/color-palette.d.ts +7 -0
  78. package/dist/lib/branding-core/generators/color-palette.d.ts.map +1 -0
  79. package/dist/lib/branding-core/generators/color-palette.js +117 -0
  80. package/dist/lib/branding-core/generators/color-palette.js.map +1 -0
  81. package/dist/lib/branding-core/generators/favicon-generator.d.ts +3 -0
  82. package/dist/lib/branding-core/generators/favicon-generator.d.ts.map +1 -0
  83. package/dist/lib/branding-core/generators/favicon-generator.js +23 -0
  84. package/dist/lib/branding-core/generators/favicon-generator.js.map +1 -0
  85. package/dist/lib/branding-core/generators/gradient-system.d.ts +3 -0
  86. package/dist/lib/branding-core/generators/gradient-system.d.ts.map +1 -0
  87. package/dist/lib/branding-core/generators/gradient-system.js +74 -0
  88. package/dist/lib/branding-core/generators/gradient-system.js.map +1 -0
  89. package/dist/lib/branding-core/generators/logo-generator.d.ts +4 -0
  90. package/dist/lib/branding-core/generators/logo-generator.d.ts.map +1 -0
  91. package/dist/lib/branding-core/generators/logo-generator.js +130 -0
  92. package/dist/lib/branding-core/generators/logo-generator.js.map +1 -0
  93. package/dist/lib/branding-core/generators/motion-system.d.ts +3 -0
  94. package/dist/lib/branding-core/generators/motion-system.d.ts.map +1 -0
  95. package/dist/lib/branding-core/generators/motion-system.js +91 -0
  96. package/dist/lib/branding-core/generators/motion-system.js.map +1 -0
  97. package/dist/lib/branding-core/generators/og-image-generator.d.ts +3 -0
  98. package/dist/lib/branding-core/generators/og-image-generator.d.ts.map +1 -0
  99. package/dist/lib/branding-core/generators/og-image-generator.js +72 -0
  100. package/dist/lib/branding-core/generators/og-image-generator.js.map +1 -0
  101. package/dist/lib/branding-core/generators/shadow-system.d.ts +3 -0
  102. package/dist/lib/branding-core/generators/shadow-system.d.ts.map +1 -0
  103. package/dist/lib/branding-core/generators/shadow-system.js +44 -0
  104. package/dist/lib/branding-core/generators/shadow-system.js.map +1 -0
  105. package/dist/lib/branding-core/generators/spacing-scale.d.ts +3 -0
  106. package/dist/lib/branding-core/generators/spacing-scale.d.ts.map +1 -0
  107. package/dist/lib/branding-core/generators/spacing-scale.js +27 -0
  108. package/dist/lib/branding-core/generators/spacing-scale.js.map +1 -0
  109. package/dist/lib/branding-core/generators/typography-system.d.ts +3 -0
  110. package/dist/lib/branding-core/generators/typography-system.d.ts.map +1 -0
  111. package/dist/lib/branding-core/generators/typography-system.js +121 -0
  112. package/dist/lib/branding-core/generators/typography-system.js.map +1 -0
  113. package/dist/lib/branding-core/index.d.ts +24 -0
  114. package/dist/lib/branding-core/index.d.ts.map +1 -0
  115. package/dist/lib/branding-core/index.js +22 -0
  116. package/dist/lib/branding-core/index.js.map +1 -0
  117. package/dist/lib/branding-core/validators/brand-consistency.d.ts +3 -0
  118. package/dist/lib/branding-core/validators/brand-consistency.d.ts.map +1 -0
  119. package/dist/lib/branding-core/validators/brand-consistency.js +70 -0
  120. package/dist/lib/branding-core/validators/brand-consistency.js.map +1 -0
  121. package/dist/lib/branding-core/validators/contrast-checker.d.ts +3 -0
  122. package/dist/lib/branding-core/validators/contrast-checker.d.ts.map +1 -0
  123. package/dist/lib/branding-core/validators/contrast-checker.js +33 -0
  124. package/dist/lib/branding-core/validators/contrast-checker.js.map +1 -0
  125. package/dist/lib/branding-core/validators/token-schema.d.ts +10 -0
  126. package/dist/lib/branding-core/validators/token-schema.d.ts.map +1 -0
  127. package/dist/lib/branding-core/validators/token-schema.js +43 -0
  128. package/dist/lib/branding-core/validators/token-schema.js.map +1 -0
  129. package/dist/lib/config.d.ts +7 -0
  130. package/dist/lib/config.d.ts.map +1 -0
  131. package/dist/lib/config.js +8 -0
  132. package/dist/lib/config.js.map +1 -0
  133. package/dist/lib/logger.d.ts +3 -0
  134. package/dist/lib/logger.d.ts.map +1 -0
  135. package/dist/lib/logger.js +10 -0
  136. package/dist/lib/logger.js.map +1 -0
  137. package/dist/lib/types.d.ts +208 -0
  138. package/dist/lib/types.d.ts.map +1 -0
  139. package/dist/lib/types.js +2 -0
  140. package/dist/lib/types.js.map +1 -0
  141. package/dist/resources/brand-knowledge.d.ts +3 -0
  142. package/dist/resources/brand-knowledge.d.ts.map +1 -0
  143. package/dist/resources/brand-knowledge.js +53 -0
  144. package/dist/resources/brand-knowledge.js.map +1 -0
  145. package/dist/resources/brand-templates.d.ts +3 -0
  146. package/dist/resources/brand-templates.d.ts.map +1 -0
  147. package/dist/resources/brand-templates.js +68 -0
  148. package/dist/resources/brand-templates.js.map +1 -0
  149. package/dist/tools/create-brand-guidelines.d.ts +3 -0
  150. package/dist/tools/create-brand-guidelines.d.ts.map +1 -0
  151. package/dist/tools/create-brand-guidelines.js +85 -0
  152. package/dist/tools/create-brand-guidelines.js.map +1 -0
  153. package/dist/tools/export-design-tokens.d.ts +3 -0
  154. package/dist/tools/export-design-tokens.d.ts.map +1 -0
  155. package/dist/tools/export-design-tokens.js +37 -0
  156. package/dist/tools/export-design-tokens.js.map +1 -0
  157. package/dist/tools/generate-brand-assets.d.ts +3 -0
  158. package/dist/tools/generate-brand-assets.d.ts.map +1 -0
  159. package/dist/tools/generate-brand-assets.js +37 -0
  160. package/dist/tools/generate-brand-assets.js.map +1 -0
  161. package/dist/tools/generate-brand-identity.d.ts +3 -0
  162. package/dist/tools/generate-brand-identity.d.ts.map +1 -0
  163. package/dist/tools/generate-brand-identity.js +73 -0
  164. package/dist/tools/generate-brand-identity.js.map +1 -0
  165. package/dist/tools/generate-color-palette.d.ts +3 -0
  166. package/dist/tools/generate-color-palette.d.ts.map +1 -0
  167. package/dist/tools/generate-color-palette.js +33 -0
  168. package/dist/tools/generate-color-palette.js.map +1 -0
  169. package/dist/tools/generate-typography-system.d.ts +3 -0
  170. package/dist/tools/generate-typography-system.d.ts.map +1 -0
  171. package/dist/tools/generate-typography-system.js +28 -0
  172. package/dist/tools/generate-typography-system.js.map +1 -0
  173. package/dist/tools/refine-brand-element.d.ts +3 -0
  174. package/dist/tools/refine-brand-element.d.ts.map +1 -0
  175. package/dist/tools/refine-brand-element.js +41 -0
  176. package/dist/tools/refine-brand-element.js.map +1 -0
  177. package/dist/tools/validate-brand-consistency.d.ts +3 -0
  178. package/dist/tools/validate-brand-consistency.d.ts.map +1 -0
  179. package/dist/tools/validate-brand-consistency.js +25 -0
  180. package/dist/tools/validate-brand-consistency.js.map +1 -0
  181. package/docs/API.md +110 -0
  182. package/docs/DATA_SOURCES.md +69 -0
  183. package/docs/INTEGRATION.md +58 -0
  184. package/eslint.config.js +52 -0
  185. package/jest.config.js +40 -0
  186. package/package.json +78 -0
  187. package/src/__tests__/integration/brand-generation.test.ts +84 -0
  188. package/src/__tests__/integration/mcp-server.test.ts +18 -0
  189. package/src/__tests__/unit/ai-interpreter.test.ts +172 -0
  190. package/src/__tests__/unit/border-system.test.ts +77 -0
  191. package/src/__tests__/unit/color-palette.test.ts +161 -0
  192. package/src/__tests__/unit/contrast-checker.test.ts +124 -0
  193. package/src/__tests__/unit/design-tokens.test.ts +184 -0
  194. package/src/__tests__/unit/favicon-generator.test.ts +80 -0
  195. package/src/__tests__/unit/gradient-system.test.ts +122 -0
  196. package/src/__tests__/unit/logo-generator.test.ts +146 -0
  197. package/src/__tests__/unit/motion-system.test.ts +91 -0
  198. package/src/__tests__/unit/og-image-generator.test.ts +115 -0
  199. package/src/__tests__/unit/shadow-system.test.ts +63 -0
  200. package/src/__tests__/unit/spacing-scale.test.ts +60 -0
  201. package/src/__tests__/unit/typography-system.test.ts +71 -0
  202. package/src/index.ts +59 -0
  203. package/src/lib/branding-core/ai/brand-interpreter.ts +30 -0
  204. package/src/lib/branding-core/ai/claude-interpreter.ts +76 -0
  205. package/src/lib/branding-core/ai/intent-applier.ts +59 -0
  206. package/src/lib/branding-core/ai/keyword-interpreter.ts +95 -0
  207. package/src/lib/branding-core/ai/prompts.ts +93 -0
  208. package/src/lib/branding-core/ai/types.ts +36 -0
  209. package/src/lib/branding-core/documents/html-generator.ts +32 -0
  210. package/src/lib/branding-core/documents/pdf-generator.ts +21 -0
  211. package/src/lib/branding-core/exporters/css-variables.ts +71 -0
  212. package/src/lib/branding-core/exporters/design-tokens.ts +86 -0
  213. package/src/lib/branding-core/exporters/figma-tokens.ts +87 -0
  214. package/src/lib/branding-core/exporters/react-theme.ts +69 -0
  215. package/src/lib/branding-core/exporters/sass-variables.ts +74 -0
  216. package/src/lib/branding-core/exporters/tailwind-preset.ts +67 -0
  217. package/src/lib/branding-core/generators/border-system.ts +41 -0
  218. package/src/lib/branding-core/generators/color-palette.ts +147 -0
  219. package/src/lib/branding-core/generators/favicon-generator.ts +33 -0
  220. package/src/lib/branding-core/generators/gradient-system.ts +120 -0
  221. package/src/lib/branding-core/generators/logo-generator.ts +152 -0
  222. package/src/lib/branding-core/generators/motion-system.ts +98 -0
  223. package/src/lib/branding-core/generators/og-image-generator.ts +97 -0
  224. package/src/lib/branding-core/generators/shadow-system.ts +66 -0
  225. package/src/lib/branding-core/generators/spacing-scale.ts +29 -0
  226. package/src/lib/branding-core/generators/typography-system.ts +128 -0
  227. package/src/lib/branding-core/index.ts +28 -0
  228. package/src/lib/branding-core/validators/brand-consistency.ts +79 -0
  229. package/src/lib/branding-core/validators/contrast-checker.ts +37 -0
  230. package/src/lib/branding-core/validators/token-schema.ts +50 -0
  231. package/src/lib/config.ts +13 -0
  232. package/src/lib/logger.ts +12 -0
  233. package/src/lib/types.ts +236 -0
  234. package/src/resources/brand-knowledge.ts +60 -0
  235. package/src/resources/brand-templates.ts +70 -0
  236. package/src/tools/create-brand-guidelines.ts +94 -0
  237. package/src/tools/export-design-tokens.ts +52 -0
  238. package/src/tools/generate-brand-assets.ts +48 -0
  239. package/src/tools/generate-brand-identity.ts +115 -0
  240. package/src/tools/generate-color-palette.ts +43 -0
  241. package/src/tools/generate-typography-system.ts +42 -0
  242. package/src/tools/refine-brand-element.ts +65 -0
  243. package/src/tools/validate-brand-consistency.ts +32 -0
  244. package/tsconfig.json +21 -0
@@ -0,0 +1,115 @@
1
+ import { generateOgImage } from '../../lib/branding-core/generators/og-image-generator.js';
2
+ import { generateColorPalette } from '../../lib/branding-core/generators/color-palette.js';
3
+ import { generateTypographySystem } from '../../lib/branding-core/generators/typography-system.js';
4
+ import { generateSpacingScale } from '../../lib/branding-core/generators/spacing-scale.js';
5
+ import { generateGradientSystem } from '../../lib/branding-core/generators/gradient-system.js';
6
+ import {
7
+ generateSvgLogo,
8
+ defaultLogoConfig,
9
+ } from '../../lib/branding-core/generators/logo-generator.js';
10
+ import type { BrandIdentity } from '../../lib/types.js';
11
+
12
+ function createTestBrand(): BrandIdentity {
13
+ const colors = generateColorPalette('#6B4CE6');
14
+ const typography = generateTypographySystem('sans-serif', 'sans-serif');
15
+ const spacing = generateSpacingScale();
16
+ const gradients = generateGradientSystem(colors, 'minimal');
17
+ const logo = generateSvgLogo({
18
+ ...defaultLogoConfig('TestBrand', colors.primary.hex),
19
+ style: 'minimal',
20
+ });
21
+
22
+ return {
23
+ id: 'test_brand',
24
+ name: 'TestBrand',
25
+ tagline: 'Test tagline',
26
+ industry: 'tech',
27
+ style: 'minimal',
28
+ colors,
29
+ typography,
30
+ spacing,
31
+ gradients,
32
+ logo,
33
+ createdAt: new Date().toISOString(),
34
+ };
35
+ }
36
+
37
+ describe('generateOgImage', () => {
38
+ const brand = createTestBrand();
39
+
40
+ it('default template produces 1200x630', () => {
41
+ const result = generateOgImage(brand);
42
+ expect(result.width).toBe(1200);
43
+ expect(result.height).toBe(630);
44
+ expect(result.template).toBe('default');
45
+ });
46
+
47
+ it('default template includes brand name', () => {
48
+ const result = generateOgImage(brand);
49
+ expect(result.svg).toContain('TestBrand');
50
+ });
51
+
52
+ it('article template produces 1200x630', () => {
53
+ const result = generateOgImage(brand, 'article', 'My Article');
54
+ expect(result.width).toBe(1200);
55
+ expect(result.height).toBe(630);
56
+ expect(result.template).toBe('article');
57
+ });
58
+
59
+ it('article template includes custom title', () => {
60
+ const result = generateOgImage(brand, 'article', 'My Article', 'Subtitle');
61
+ expect(result.svg).toContain('My Article');
62
+ expect(result.svg).toContain('Subtitle');
63
+ });
64
+
65
+ it('social template produces 1200x1200 square', () => {
66
+ const result = generateOgImage(brand, 'social');
67
+ expect(result.width).toBe(1200);
68
+ expect(result.height).toBe(1200);
69
+ expect(result.template).toBe('social');
70
+ });
71
+
72
+ it('all templates produce valid SVG', () => {
73
+ for (const template of ['default', 'article', 'social'] as const) {
74
+ const result = generateOgImage(brand, template);
75
+ expect(result.svg).toContain('xmlns="http://www.w3.org/2000/svg"');
76
+ expect(result.svg).toContain('</svg>');
77
+ }
78
+ });
79
+
80
+ it('uses brand font family', () => {
81
+ const result = generateOgImage(brand);
82
+ expect(result.svg).toContain(brand.typography.headingFont);
83
+ });
84
+
85
+ it('default parameters work', () => {
86
+ const result = generateOgImage(brand);
87
+ expect(result.svg).toBeTruthy();
88
+ expect(result.width).toBe(1200);
89
+ expect(result.height).toBe(630);
90
+ });
91
+
92
+ it('uses brand colors', () => {
93
+ const result = generateOgImage(brand, 'social');
94
+ expect(result.svg).toContain(brand.colors.primary.hex);
95
+ });
96
+
97
+ it('works without gradients', () => {
98
+ const brandNoGradients = { ...brand, gradients: undefined };
99
+ const result = generateOgImage(brandNoGradients);
100
+ expect(result.svg).toBeTruthy();
101
+ expect(result.svg).toContain(brand.colors.primary.hex);
102
+ });
103
+
104
+ it('works without logo', () => {
105
+ const brandNoLogo = { ...brand, logo: undefined };
106
+ const result = generateOgImage(brandNoLogo);
107
+ expect(result.svg).toBeTruthy();
108
+ expect(result.svg).toContain('TestBrand');
109
+ });
110
+
111
+ it('social template with custom title', () => {
112
+ const result = generateOgImage(brand, 'social', 'Custom Title');
113
+ expect(result.svg).toContain('Custom Title');
114
+ });
115
+ });
@@ -0,0 +1,63 @@
1
+ import { generateShadowSystem } from '../../lib/branding-core/generators/shadow-system.js';
2
+
3
+ describe('generateShadowSystem', () => {
4
+ it('generates all 6 elevation levels', () => {
5
+ const system = generateShadowSystem();
6
+ const names = Object.keys(system.levels);
7
+ expect(names).toEqual(['none', 'sm', 'md', 'lg', 'xl', '2xl']);
8
+ });
9
+
10
+ it('none level has zero values', () => {
11
+ const system = generateShadowSystem();
12
+ const none = system.levels.none;
13
+ expect(none.blur).toBe(0);
14
+ expect(none.spread).toBe(0);
15
+ expect(none.offsetY).toBe(0);
16
+ expect(none.cssValue).toBe('none');
17
+ });
18
+
19
+ it('blur increases with elevation', () => {
20
+ const system = generateShadowSystem();
21
+ const levels = ['sm', 'md', 'lg', 'xl', '2xl'] as const;
22
+ for (let i = 1; i < levels.length; i++) {
23
+ expect(system.levels[levels[i]].blur).toBeGreaterThan(system.levels[levels[i - 1]].blur);
24
+ }
25
+ });
26
+
27
+ it('uses brand color for tinting', () => {
28
+ const system = generateShadowSystem('#FF0000');
29
+ const md = system.levels.md;
30
+ expect(md.color).toMatch(/^rgba\(/);
31
+ expect(md.cssValue).toContain('rgba(');
32
+ });
33
+
34
+ it('dark theme reverses shadow direction', () => {
35
+ const light = generateShadowSystem('#6B4CE6', 'light');
36
+ const dark = generateShadowSystem('#6B4CE6', 'dark');
37
+ expect(light.levels.md.offsetY).toBeGreaterThan(0);
38
+ expect(dark.levels.md.offsetY).toBeLessThan(0);
39
+ });
40
+
41
+ it('dark theme has higher opacity', () => {
42
+ const light = generateShadowSystem('#6B4CE6', 'light');
43
+ const dark = generateShadowSystem('#6B4CE6', 'dark');
44
+ expect(dark.levels.lg.opacity).toBeGreaterThan(light.levels.lg.opacity);
45
+ });
46
+
47
+ it('generates valid CSS box-shadow strings', () => {
48
+ const system = generateShadowSystem();
49
+ for (const [name, level] of Object.entries(system.levels)) {
50
+ if (name === 'none') {
51
+ expect(level.cssValue).toBe('none');
52
+ } else {
53
+ expect(level.cssValue).toMatch(/\d+px.*rgba\(/);
54
+ }
55
+ }
56
+ });
57
+
58
+ it('works with default parameters', () => {
59
+ const system = generateShadowSystem();
60
+ expect(system.levels).toBeDefined();
61
+ expect(Object.keys(system.levels)).toHaveLength(6);
62
+ });
63
+ });
@@ -0,0 +1,60 @@
1
+ import { generateSpacingScale } from '../../lib/branding-core/generators/spacing-scale.js';
2
+
3
+ describe('generateSpacingScale', () => {
4
+ it('generates scale with default 4px unit', () => {
5
+ const scale = generateSpacingScale();
6
+ expect(scale.unit).toBe(4);
7
+ expect(scale.values['0']).toBe('0px');
8
+ expect(scale.values['1']).toBe('4px');
9
+ expect(scale.values['2']).toBe('8px');
10
+ expect(scale.values['4']).toBe('16px');
11
+ });
12
+
13
+ it('generates scale with custom unit', () => {
14
+ const scale = generateSpacingScale(8);
15
+ expect(scale.unit).toBe(8);
16
+ expect(scale.values['1']).toBe('8px');
17
+ expect(scale.values['2']).toBe('16px');
18
+ });
19
+
20
+ it('includes all standard steps', () => {
21
+ const scale = generateSpacingScale();
22
+ const expectedKeys = [
23
+ '0',
24
+ '0.5',
25
+ '1',
26
+ '1.5',
27
+ '2',
28
+ '2.5',
29
+ '3',
30
+ '4',
31
+ '5',
32
+ '6',
33
+ '8',
34
+ '10',
35
+ '12',
36
+ '16',
37
+ '20',
38
+ '24',
39
+ ];
40
+ for (const key of expectedKeys) {
41
+ expect(scale.values[key]).toBeDefined();
42
+ }
43
+ });
44
+
45
+ it('zero spacing is always 0px', () => {
46
+ const scale = generateSpacingScale(8);
47
+ expect(scale.values['0']).toBe('0px');
48
+ });
49
+
50
+ it('values increase monotonically', () => {
51
+ const scale = generateSpacingScale();
52
+ const keys = Object.keys(scale.values)
53
+ .map(Number)
54
+ .sort((a, b) => a - b);
55
+ const values = keys.map((k) => parseInt(scale.values[String(k)]));
56
+ for (let i = 1; i < values.length; i++) {
57
+ expect(values[i]).toBeGreaterThanOrEqual(values[i - 1]);
58
+ }
59
+ });
60
+ });
@@ -0,0 +1,71 @@
1
+ import { generateTypographySystem } from '../../lib/branding-core/generators/typography-system.js';
2
+
3
+ describe('generateTypographySystem', () => {
4
+ it('generates a system with default parameters', () => {
5
+ const system = generateTypographySystem();
6
+ expect(system.headingFont).toBeTruthy();
7
+ expect(system.bodyFont).toBeTruthy();
8
+ expect(system.monoFont).toBe('JetBrains Mono');
9
+ expect(system.baseSize).toBe(16);
10
+ expect(system.steps.length).toBeGreaterThan(0);
11
+ });
12
+
13
+ it('applies correct scale ratio', () => {
14
+ const system = generateTypographySystem('sans-serif', 'serif', 'major-third', 16);
15
+ expect(system.scaleRatio).toBe(1.25);
16
+ });
17
+
18
+ it('uses perfect fourth scale', () => {
19
+ const system = generateTypographySystem('sans-serif', 'serif', 'perfect-fourth');
20
+ expect(system.scaleRatio).toBe(1.333);
21
+ });
22
+
23
+ it('generates correct number of type steps', () => {
24
+ const system = generateTypographySystem();
25
+ expect(system.steps.length).toBe(9);
26
+ });
27
+
28
+ it('base step is at index 2 with baseSize', () => {
29
+ const system = generateTypographySystem('sans-serif', 'serif', 'major-third', 16);
30
+ const baseStep = system.steps[2];
31
+ expect(baseStep.name).toBe('base');
32
+ expect(parseFloat(baseStep.size)).toBeCloseTo(16, 0);
33
+ });
34
+
35
+ it('sizes increase progressively', () => {
36
+ const system = generateTypographySystem();
37
+ const sizes = system.steps.map((s) => parseFloat(s.size));
38
+ for (let i = 1; i < sizes.length; i++) {
39
+ expect(sizes[i]).toBeGreaterThan(sizes[i - 1]);
40
+ }
41
+ });
42
+
43
+ it('respects custom base size', () => {
44
+ const system = generateTypographySystem('sans-serif', 'serif', 'major-third', 18);
45
+ expect(system.baseSize).toBe(18);
46
+ const baseStep = system.steps[2];
47
+ expect(parseFloat(baseStep.size)).toBeCloseTo(18, 0);
48
+ });
49
+
50
+ it('generates line heights for all steps', () => {
51
+ const system = generateTypographySystem();
52
+ for (const step of system.steps) {
53
+ expect(parseFloat(step.lineHeight)).toBeGreaterThan(1);
54
+ expect(parseFloat(step.lineHeight)).toBeLessThanOrEqual(1.6);
55
+ }
56
+ });
57
+
58
+ it('generates valid font weights', () => {
59
+ const system = generateTypographySystem();
60
+ for (const step of system.steps) {
61
+ expect([400, 600, 700]).toContain(step.weight);
62
+ }
63
+ });
64
+
65
+ it('pairs serif heading with sans-serif body', () => {
66
+ const system = generateTypographySystem('serif', 'sans-serif');
67
+ expect(system.headingFont).toBeTruthy();
68
+ expect(system.bodyFont).toBeTruthy();
69
+ expect(system.headingFont).not.toBe(system.bodyFont);
70
+ });
71
+ });
package/src/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { loadConfig } from './lib/config.js';
6
+ import { logger } from './lib/logger.js';
7
+
8
+ import { registerGenerateBrandIdentity } from './tools/generate-brand-identity.js';
9
+ import { registerGenerateColorPalette } from './tools/generate-color-palette.js';
10
+ import { registerGenerateTypographySystem } from './tools/generate-typography-system.js';
11
+ import { registerExportDesignTokens } from './tools/export-design-tokens.js';
12
+ import { registerCreateBrandGuidelines } from './tools/create-brand-guidelines.js';
13
+ import { registerValidateBrandConsistency } from './tools/validate-brand-consistency.js';
14
+ import { registerRefineBrandElement } from './tools/refine-brand-element.js';
15
+ import { registerGenerateBrandAssets } from './tools/generate-brand-assets.js';
16
+
17
+ import { registerBrandTemplates } from './resources/brand-templates.js';
18
+ import { registerBrandKnowledge } from './resources/brand-knowledge.js';
19
+
20
+ async function main(): Promise<void> {
21
+ const config = loadConfig();
22
+ logger.info({ env: config.nodeEnv }, 'Starting branding-mcp server');
23
+
24
+ const server = new McpServer({
25
+ name: '@forgespace/branding-mcp',
26
+ version: '0.1.0',
27
+ });
28
+
29
+ registerGenerateBrandIdentity(server);
30
+ registerGenerateColorPalette(server);
31
+ registerGenerateTypographySystem(server);
32
+ registerExportDesignTokens(server);
33
+ registerCreateBrandGuidelines(server);
34
+ registerValidateBrandConsistency(server);
35
+ registerRefineBrandElement(server);
36
+ registerGenerateBrandAssets(server);
37
+
38
+ registerBrandTemplates(server);
39
+ registerBrandKnowledge(server);
40
+
41
+ const transport = new StdioServerTransport();
42
+ await server.connect(transport);
43
+ logger.info('Branding MCP server connected via stdio');
44
+ }
45
+
46
+ main().catch((error) => {
47
+ logger.fatal(error, 'Failed to start branding-mcp server');
48
+ process.exit(1);
49
+ });
50
+
51
+ process.on('SIGTERM', () => {
52
+ logger.info('Received SIGTERM, shutting down');
53
+ process.exit(0);
54
+ });
55
+
56
+ process.on('SIGINT', () => {
57
+ logger.info('Received SIGINT, shutting down');
58
+ process.exit(0);
59
+ });
@@ -0,0 +1,30 @@
1
+ import type { BrandIdentity } from '../../types.js';
2
+ import type { BrandIntent, InterpreterOptions } from './types.js';
3
+ import { interpretWithKeywords } from './keyword-interpreter.js';
4
+ import { interpretWithClaude } from './claude-interpreter.js';
5
+ import { logger } from '../../logger.js';
6
+
7
+ export type InterpreterStrategy = 'keyword' | 'ai' | 'auto';
8
+
9
+ export async function interpretFeedback(
10
+ feedback: string,
11
+ element: 'colors' | 'typography' | 'spacing',
12
+ currentBrand: BrandIdentity,
13
+ strategy: InterpreterStrategy = 'auto',
14
+ options?: InterpreterOptions
15
+ ): Promise<BrandIntent> {
16
+ const useAi = strategy === 'ai' || (strategy === 'auto' && !!options?.anthropicApiKey);
17
+
18
+ if (useAi && options?.anthropicApiKey) {
19
+ try {
20
+ return await interpretWithClaude(feedback, element, currentBrand, options);
21
+ } catch (error) {
22
+ logger.warn(
23
+ { error: error instanceof Error ? error.message : String(error) },
24
+ 'AI interpretation failed, falling back to keywords'
25
+ );
26
+ }
27
+ }
28
+
29
+ return interpretWithKeywords(feedback, element, currentBrand);
30
+ }
@@ -0,0 +1,76 @@
1
+ import type { BrandIdentity } from '../../types.js';
2
+ import type { BrandIntent, InterpreterOptions } from './types.js';
3
+ import { buildRefinementPrompt, buildGenerationPrompt } from './prompts.js';
4
+ import { logger } from '../../logger.js';
5
+
6
+ async function callClaude(prompt: string, options: Required<InterpreterOptions>): Promise<string> {
7
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
8
+ method: 'POST',
9
+ headers: {
10
+ 'Content-Type': 'application/json',
11
+ 'x-api-key': options.anthropicApiKey,
12
+ 'anthropic-version': '2023-06-01',
13
+ },
14
+ body: JSON.stringify({
15
+ model: options.model,
16
+ max_tokens: options.maxTokens,
17
+ messages: [{ role: 'user', content: prompt }],
18
+ }),
19
+ });
20
+
21
+ if (!response.ok) {
22
+ const body = await response.text();
23
+ throw new Error(`Anthropic API error ${response.status}: ${body}`);
24
+ }
25
+
26
+ const data = (await response.json()) as {
27
+ content: Array<{ type: string; text: string }>;
28
+ };
29
+ const textBlock = data.content.find((b) => b.type === 'text');
30
+ if (!textBlock) throw new Error('No text content in Anthropic response');
31
+ return textBlock.text;
32
+ }
33
+
34
+ function parseIntentJson(raw: string): BrandIntent {
35
+ const jsonMatch = raw.match(/\{[\s\S]*\}/);
36
+ if (!jsonMatch) throw new Error('No JSON object found in AI response');
37
+ return JSON.parse(jsonMatch[0]) as BrandIntent;
38
+ }
39
+
40
+ export async function interpretWithClaude(
41
+ feedback: string,
42
+ element: 'colors' | 'typography' | 'spacing',
43
+ currentBrand: BrandIdentity,
44
+ options: InterpreterOptions
45
+ ): Promise<BrandIntent> {
46
+ const opts: Required<InterpreterOptions> = {
47
+ anthropicApiKey: options.anthropicApiKey ?? '',
48
+ model: options.model ?? 'claude-sonnet-4-20250514',
49
+ maxTokens: options.maxTokens ?? 512,
50
+ };
51
+
52
+ const prompt = buildRefinementPrompt(feedback, element, currentBrand);
53
+ logger.info({ element, model: opts.model }, 'Interpreting feedback with Claude');
54
+
55
+ const raw = await callClaude(prompt, opts);
56
+ return parseIntentJson(raw);
57
+ }
58
+
59
+ export async function suggestBrandWithClaude(
60
+ brandName: string,
61
+ industry: string,
62
+ description: string,
63
+ options: InterpreterOptions
64
+ ): Promise<BrandIntent> {
65
+ const opts: Required<InterpreterOptions> = {
66
+ anthropicApiKey: options.anthropicApiKey ?? '',
67
+ model: options.model ?? 'claude-sonnet-4-20250514',
68
+ maxTokens: options.maxTokens ?? 512,
69
+ };
70
+
71
+ const prompt = buildGenerationPrompt(brandName, industry, description);
72
+ logger.info({ brandName, model: opts.model }, 'Generating brand suggestions with Claude');
73
+
74
+ const raw = await callClaude(prompt, opts);
75
+ return parseIntentJson(raw);
76
+ }
@@ -0,0 +1,59 @@
1
+ import type { BrandIdentity } from '../../types.js';
2
+ import type { BrandIntent } from './types.js';
3
+ import { generateColorPalette, hexToHsl, hslToHex } from '../generators/color-palette.js';
4
+ import { generateTypographySystem } from '../generators/typography-system.js';
5
+
6
+ function clamp(value: number, min: number, max: number): number {
7
+ return Math.max(min, Math.min(max, value));
8
+ }
9
+
10
+ function applyColorShifts(
11
+ baseHex: string,
12
+ hueShift: number,
13
+ saturationShift: number,
14
+ lightnessShift: number
15
+ ): string {
16
+ const hsl = hexToHsl(baseHex);
17
+ return hslToHex(
18
+ (hsl.h + hueShift + 360) % 360,
19
+ clamp(hsl.s + saturationShift, 0, 100),
20
+ clamp(hsl.l + lightnessShift, 5, 95)
21
+ );
22
+ }
23
+
24
+ export function applyIntent(brand: BrandIdentity, intent: BrandIntent): BrandIdentity {
25
+ const updated = { ...brand };
26
+
27
+ if (intent.color) {
28
+ const { baseColor, harmony, theme, hueShift, saturationShift, lightnessShift } = intent.color;
29
+
30
+ let effectiveBase = baseColor ?? brand.colors.primary.hex;
31
+
32
+ if (hueShift || saturationShift || lightnessShift) {
33
+ effectiveBase = applyColorShifts(
34
+ effectiveBase,
35
+ hueShift ?? 0,
36
+ saturationShift ?? 0,
37
+ lightnessShift ?? 0
38
+ );
39
+ }
40
+
41
+ updated.colors = generateColorPalette(effectiveBase, harmony ?? 'complementary', theme);
42
+ }
43
+
44
+ if (intent.typography) {
45
+ const { headingCategory, bodyCategory, scaleRatio, baseSize } = intent.typography;
46
+ updated.typography = generateTypographySystem(
47
+ (headingCategory ?? brand.typography.headingFont.includes('serif')) ? 'serif' : 'sans-serif',
48
+ bodyCategory ?? 'sans-serif',
49
+ scaleRatio,
50
+ baseSize ?? brand.typography.baseSize
51
+ );
52
+ }
53
+
54
+ if (intent.style) {
55
+ updated.style = intent.style;
56
+ }
57
+
58
+ return updated;
59
+ }
@@ -0,0 +1,95 @@
1
+ import type { BrandIdentity } from '../../types.js';
2
+ import type { BrandIntent, ColorIntent, TypographyIntent } from './types.js';
3
+
4
+ const COLOR_KEYWORDS: Record<string, ColorIntent> = {
5
+ warm: { harmony: 'analogous', hueShift: 15 },
6
+ cool: { harmony: 'analogous', hueShift: -15 },
7
+ subtle: { harmony: 'analogous', saturationShift: -15 },
8
+ vibrant: { harmony: 'triadic', saturationShift: 15 },
9
+ bold: { harmony: 'complementary', saturationShift: 10 },
10
+ muted: { harmony: 'monochromatic', saturationShift: -20 },
11
+ dark: { theme: 'dark', lightnessShift: -15 },
12
+ light: { theme: 'light', lightnessShift: 15 },
13
+ monochromatic: { harmony: 'monochromatic' },
14
+ complementary: { harmony: 'complementary' },
15
+ analogous: { harmony: 'analogous' },
16
+ triadic: { harmony: 'triadic' },
17
+ premium: { harmony: 'analogous', saturationShift: -10, lightnessShift: -10 },
18
+ playful: { harmony: 'triadic', saturationShift: 15, lightnessShift: 5 },
19
+ corporate: { harmony: 'complementary', saturationShift: -10 },
20
+ organic: { harmony: 'analogous', hueShift: 30 },
21
+ elegant: { harmony: 'monochromatic', saturationShift: -15, lightnessShift: -5 },
22
+ };
23
+
24
+ const TYPOGRAPHY_KEYWORDS: Record<string, TypographyIntent> = {
25
+ serif: { headingCategory: 'serif' },
26
+ 'sans-serif': { headingCategory: 'sans-serif' },
27
+ mono: { bodyCategory: 'monospace' },
28
+ monospace: { bodyCategory: 'monospace' },
29
+ display: { headingCategory: 'display' },
30
+ handwritten: { headingCategory: 'handwriting' },
31
+ compact: { scaleRatio: 'minor-second' },
32
+ tight: { scaleRatio: 'major-second' },
33
+ spacious: { scaleRatio: 'perfect-fourth' },
34
+ dramatic: { scaleRatio: 'golden-ratio' },
35
+ larger: { baseSize: 18 },
36
+ smaller: { baseSize: 14 },
37
+ editorial: { headingCategory: 'serif', scaleRatio: 'perfect-fourth' },
38
+ technical: { bodyCategory: 'monospace', headingCategory: 'sans-serif' },
39
+ modern: { headingCategory: 'sans-serif', bodyCategory: 'sans-serif' },
40
+ classic: { headingCategory: 'serif', bodyCategory: 'serif' },
41
+ };
42
+
43
+ function mergeColorIntents(intents: ColorIntent[]): ColorIntent {
44
+ const merged: ColorIntent = {};
45
+ for (const intent of intents) {
46
+ if (intent.harmony) merged.harmony = intent.harmony;
47
+ if (intent.theme) merged.theme = intent.theme;
48
+ if (intent.baseColor) merged.baseColor = intent.baseColor;
49
+ merged.saturationShift = (merged.saturationShift ?? 0) + (intent.saturationShift ?? 0);
50
+ merged.lightnessShift = (merged.lightnessShift ?? 0) + (intent.lightnessShift ?? 0);
51
+ merged.hueShift = (merged.hueShift ?? 0) + (intent.hueShift ?? 0);
52
+ }
53
+ return merged;
54
+ }
55
+
56
+ export function interpretWithKeywords(
57
+ feedback: string,
58
+ element: 'colors' | 'typography' | 'spacing',
59
+ _currentBrand?: BrandIdentity
60
+ ): BrandIntent {
61
+ const lower = feedback.toLowerCase();
62
+
63
+ if (element === 'colors') {
64
+ const matched: ColorIntent[] = [];
65
+ for (const [keyword, intent] of Object.entries(COLOR_KEYWORDS)) {
66
+ if (lower.includes(keyword)) {
67
+ matched.push(intent);
68
+ }
69
+ }
70
+ return {
71
+ color: matched.length > 0 ? mergeColorIntents(matched) : { harmony: 'complementary' },
72
+ reasoning: `Keyword match: ${matched.length > 0 ? 'matched color keywords' : 'default fallback'}`,
73
+ };
74
+ }
75
+
76
+ if (element === 'typography') {
77
+ const matched: TypographyIntent[] = [];
78
+ for (const [keyword, intent] of Object.entries(TYPOGRAPHY_KEYWORDS)) {
79
+ if (lower.includes(keyword)) {
80
+ matched.push(intent);
81
+ }
82
+ }
83
+ const merged: TypographyIntent = {};
84
+ for (const intent of matched) {
85
+ Object.assign(merged, intent);
86
+ }
87
+ return {
88
+ typography:
89
+ Object.keys(merged).length > 0 ? merged : { headingCategory: 'sans-serif' as const },
90
+ reasoning: `Keyword match: ${matched.length > 0 ? 'matched typography keywords' : 'default fallback'}`,
91
+ };
92
+ }
93
+
94
+ return { reasoning: 'No actionable keywords found for spacing refinement' };
95
+ }