@symbo.ls/mcp-server 3.6.7 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/mcp-server",
3
- "version": "3.6.7",
3
+ "version": "3.7.0",
4
4
  "description": "HTTP proxy for the Symbols MCP server — runs as a Cloudflare Worker",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -15,10 +15,88 @@ function readSkill(filename) {
15
15
  return skills[filename] || `Skill '${filename}' not found`;
16
16
  }
17
17
 
18
+ function readSkills(...filenames) {
19
+ return filenames
20
+ .map((f) => skills[f])
21
+ .filter(Boolean)
22
+ .join('\n\n---\n\n');
23
+ }
24
+
18
25
  function listSkillFiles() {
19
26
  return Object.keys(skills);
20
27
  }
21
28
 
29
+ // ---------------------------------------------------------------------------
30
+ // Audit helpers (deterministic rule checking)
31
+ // ---------------------------------------------------------------------------
32
+
33
+ const V2_PATTERNS = [
34
+ [/\bextend\s*:/g, "v2 syntax: use 'extends' (plural) instead of 'extend'"],
35
+ [/\bchildExtend\s*:/g, "v2 syntax: use 'childExtends' (plural) instead of 'childExtend'"],
36
+ [
37
+ /\bon\s*:\s*\{/g,
38
+ 'v2 syntax: flatten event handlers with onX prefix (e.g. onClick) instead of on: {} wrapper',
39
+ ],
40
+ [
41
+ /\bprops\s*:\s*\{(?!\s*\})/g,
42
+ 'v2 syntax: flatten props directly on the component instead of props: {} wrapper',
43
+ ],
44
+ ];
45
+
46
+ const RULE_CHECKS = [
47
+ [
48
+ /\bimport\s+.*\bfrom\s+['"]\.\//,
49
+ 'FORBIDDEN: No imports between project files — reference components by PascalCase key name',
50
+ ],
51
+ [
52
+ /\bexport\s+default\s+\{/,
53
+ 'Components should use named exports (export const Name = {}), not default exports',
54
+ ],
55
+ [
56
+ /\bfunction\s+\w+\s*\(.*\)\s*\{[\s\S]*?return\s*\{/,
57
+ 'Components must be plain objects, not functions that return objects',
58
+ ],
59
+ [
60
+ /(?:padding|margin|gap|width|height)\s*:\s*['"]?\d+px/,
61
+ 'Use design tokens (A, B, C) instead of hardcoded pixel values',
62
+ ],
63
+ ];
64
+
65
+ function auditCode(code) {
66
+ const violations = [];
67
+ const warnings = [];
68
+
69
+ for (const [pattern, message] of V2_PATTERNS) {
70
+ const regex = new RegExp(pattern.source, pattern.flags);
71
+ let match;
72
+ while ((match = regex.exec(code)) !== null) {
73
+ const line = code.slice(0, match.index).split('\n').length;
74
+ violations.push({ line, severity: 'error', message });
75
+ }
76
+ }
77
+
78
+ for (const [pattern, message] of RULE_CHECKS) {
79
+ const regex = new RegExp(pattern.source, pattern.flags);
80
+ let match;
81
+ while ((match = regex.exec(code)) !== null) {
82
+ const line = code.slice(0, match.index).split('\n').length;
83
+ const level = message.includes('FORBIDDEN') ? 'error' : 'warning';
84
+ (level === 'error' ? violations : warnings).push({ line, severity: level, message });
85
+ }
86
+ }
87
+
88
+ const totalIssues = violations.length + warnings.length;
89
+ const score = Math.max(1, 10 - totalIssues);
90
+
91
+ return {
92
+ passed: violations.length === 0,
93
+ score,
94
+ violations,
95
+ warnings,
96
+ summary: `${violations.length} errors, ${warnings.length} warnings — compliance score: ${score}/10`,
97
+ };
98
+ }
99
+
22
100
  // ---------------------------------------------------------------------------
23
101
  // Tools
24
102
  // ---------------------------------------------------------------------------
@@ -49,6 +127,136 @@ const TOOLS = [
49
127
  required: ['query'],
50
128
  },
51
129
  },
130
+ {
131
+ name: 'generate_component',
132
+ description:
133
+ 'Generate a Symbols.app DOMQL v3 component from a description. Returns rules, syntax, component catalog, cookbook examples, and default library reference as context.',
134
+ inputSchema: {
135
+ type: 'object',
136
+ properties: {
137
+ description: {
138
+ type: 'string',
139
+ description: 'What the component should do and look like',
140
+ },
141
+ component_name: {
142
+ type: 'string',
143
+ description: 'PascalCase name for the component',
144
+ default: 'MyComponent',
145
+ },
146
+ },
147
+ required: ['description'],
148
+ },
149
+ },
150
+ {
151
+ name: 'generate_page',
152
+ description:
153
+ 'Generate a Symbols.app page with routing integration. Returns rules, project structure, patterns, snippets, and default library reference as context.',
154
+ inputSchema: {
155
+ type: 'object',
156
+ properties: {
157
+ description: {
158
+ type: 'string',
159
+ description: 'What the page should contain and do',
160
+ },
161
+ page_name: {
162
+ type: 'string',
163
+ description: 'camelCase name for the page',
164
+ default: 'home',
165
+ },
166
+ },
167
+ required: ['description'],
168
+ },
169
+ },
170
+ {
171
+ name: 'convert_react',
172
+ description:
173
+ 'Convert React/JSX code to Symbols.app DOMQL v3. Returns migration rules, syntax reference, and examples as context.',
174
+ inputSchema: {
175
+ type: 'object',
176
+ properties: {
177
+ source_code: {
178
+ type: 'string',
179
+ description: 'The React/JSX source code to convert',
180
+ },
181
+ },
182
+ required: ['source_code'],
183
+ },
184
+ },
185
+ {
186
+ name: 'convert_html',
187
+ description:
188
+ 'Convert raw HTML/CSS to Symbols.app DOMQL v3 components. Returns component catalog, syntax reference, and design tokens as context.',
189
+ inputSchema: {
190
+ type: 'object',
191
+ properties: {
192
+ source_code: {
193
+ type: 'string',
194
+ description: 'The HTML/CSS source code to convert',
195
+ },
196
+ },
197
+ required: ['source_code'],
198
+ },
199
+ },
200
+ {
201
+ name: 'audit_component',
202
+ description:
203
+ 'Audit a Symbols/DOMQL component for v3 compliance and best practices. Returns a structured report with violations, warnings, and compliance score.',
204
+ inputSchema: {
205
+ type: 'object',
206
+ properties: {
207
+ component_code: {
208
+ type: 'string',
209
+ description: 'The JavaScript component code to audit',
210
+ },
211
+ },
212
+ required: ['component_code'],
213
+ },
214
+ },
215
+ {
216
+ name: 'detect_environment',
217
+ description:
218
+ 'Detect what type of Symbols environment the user is working in (local project, CDN, JSON runtime, or remote server) based on project indicators, and return the appropriate setup guide and code format.',
219
+ inputSchema: {
220
+ type: 'object',
221
+ properties: {
222
+ has_symbols_json: {
223
+ type: 'boolean',
224
+ description: 'Whether symbols.json exists in the project root',
225
+ },
226
+ has_symbols_dir: {
227
+ type: 'boolean',
228
+ description: 'Whether a symbols/ directory exists with components/, pages/, etc.',
229
+ },
230
+ has_package_json: {
231
+ type: 'boolean',
232
+ description: 'Whether package.json exists with smbls dependency',
233
+ },
234
+ has_cdn_import: {
235
+ type: 'boolean',
236
+ description:
237
+ 'Whether HTML files contain CDN imports (esm.sh/smbls, cdn.jsdelivr.net/npm/smbls, etc.)',
238
+ },
239
+ has_iife_script: {
240
+ type: 'boolean',
241
+ description: 'Whether HTML files use <script src="...smbls"> (IIFE global)',
242
+ },
243
+ has_json_data: {
244
+ type: 'boolean',
245
+ description: 'Whether the project uses frank-generated JSON data files',
246
+ },
247
+ has_mermaid_config: {
248
+ type: 'boolean',
249
+ description:
250
+ 'Whether mermaid/wrangler config or GATEWAY_URL/JSON_PATH env vars are present',
251
+ },
252
+ file_list: {
253
+ type: 'string',
254
+ description:
255
+ 'Comma-separated list of key files in the project root (e.g. "index.html,package.json,symbols.json")',
256
+ },
257
+ },
258
+ },
259
+ },
52
260
  ];
53
261
 
54
262
  function callTool(name, args = {}) {
@@ -89,6 +297,194 @@ function callTool(name, args = {}) {
89
297
  : `No results found for '${query}'. Try a different search term.`;
90
298
  }
91
299
 
300
+ if (name === 'generate_component') {
301
+ const desc = args.description || '';
302
+ const compName = args.component_name || 'MyComponent';
303
+ const context = readSkills(
304
+ 'RULES.md',
305
+ 'COMPONENTS.md',
306
+ 'SYNTAX.md',
307
+ 'COOKBOOK.md',
308
+ 'DEFAULT_LIBRARY.md',
309
+ );
310
+ return `# Generate Component: ${compName}\n\n## Description\n${desc}\n\n## Requirements\n- Named export: \`export const ${compName} = { ... }\`\n- DOMQL v3 syntax only (extends, childExtends, flattened props, onX events)\n- Use design tokens for spacing (A, B, C), colors from theme\n- NO imports between files — PascalCase keys auto-extend registered components\n- Include responsive breakpoints where appropriate (@tabletS, @mobileL)\n- Use the default library components (Button, Avatar, Icon, Field, etc.) via extends\n- Follow modern UI/UX: visual hierarchy, confident typography, minimal cognitive load\n\n## Context — Rules, Syntax & Examples\n\n${context}`;
311
+ }
312
+
313
+ if (name === 'generate_page') {
314
+ const desc = args.description || '';
315
+ const pageName = args.page_name || 'home';
316
+ const context = readSkills(
317
+ 'RULES.md',
318
+ 'PROJECT_STRUCTURE.md',
319
+ 'PATTERNS.md',
320
+ 'SNIPPETS.md',
321
+ 'DEFAULT_LIBRARY.md',
322
+ 'COMPONENTS.md',
323
+ );
324
+ return `# Generate Page: ${pageName}\n\n## Description\n${desc}\n\n## Requirements\n- Export as: \`export const ${pageName} = { ... }\`\n- Page is a plain object composing components\n- Add to pages/index.js route map: \`'/${pageName}': ${pageName}\`\n- Use components by PascalCase key (Header, Footer, Hero, etc.)\n- Use design tokens — no hardcoded pixels\n- Include responsive layout adjustments\n\n## Context — Rules, Structure, Patterns & Snippets\n\n${context}`;
325
+ }
326
+
327
+ if (name === 'convert_react') {
328
+ const source = args.source_code || '';
329
+ const context = readSkills(
330
+ 'RULES.md',
331
+ 'MIGRATION.md',
332
+ 'SYNTAX.md',
333
+ 'COMPONENTS.md',
334
+ 'LEARNINGS.md',
335
+ );
336
+ return `# Convert React → Symbols DOMQL v3\n\n## Source Code to Convert\n\`\`\`jsx\n${source}\n\`\`\`\n\n## Conversion Rules\n- Function/class components → plain object exports\n- JSX → nested object children (PascalCase keys auto-extend)\n- import/export between files → REMOVE (reference by key name)\n- useState → state: { key: val } + s.update({ key: newVal })\n- useEffect → onRender (mount), onStateUpdate (deps)\n- props → flattened directly on component (no props wrapper)\n- onClick={handler} → onClick: (event, el, state) => {}\n- className → use design tokens and theme directly\n- map() → children: (el, s) => s.items, childExtends, childProps\n- conditional rendering → if: (el, s) => boolean\n- CSS modules/styled → CSS-in-props with design tokens\n- React.Fragment → not needed, just nest children\n\n## Context — Migration Guide, Syntax & Rules\n\n${context}`;
337
+ }
338
+
339
+ if (name === 'convert_html') {
340
+ const source = args.source_code || '';
341
+ const context = readSkills(
342
+ 'RULES.md',
343
+ 'SYNTAX.md',
344
+ 'COMPONENTS.md',
345
+ 'DESIGN_SYSTEM.md',
346
+ 'SNIPPETS.md',
347
+ 'LEARNINGS.md',
348
+ );
349
+ return `# Convert HTML → Symbols DOMQL v3\n\n## Source Code to Convert\n\`\`\`html\n${source}\n\`\`\`\n\n## Conversion Rules\n- <div> → Box, Flex, or Grid (based on layout purpose)\n- <span>, <p>, <h1>-<h6> → Text, P, H with tag property\n- <a> → Link (has built-in SPA router)\n- <button> → Button (has icon/text support)\n- <input> → Input, Radio, Checkbox (based on type)\n- <img> → Img\n- <form> → Form (extends Box with tag: 'form')\n- <ul>/<ol> + <li> → children array with childExtends\n- CSS classes → flatten as CSS-in-props on the component\n- CSS px values → design tokens (16px → 'A', 26px → 'B', 42px → 'C')\n- CSS colors → theme color tokens\n- media queries → @tabletS, @mobileL, @screenS breakpoints\n- id/class attributes → not needed (use key names and themes)\n- inline styles → flatten as component properties\n- <style> blocks → distribute to component-level properties\n\n## Context — Syntax, Components & Design System\n\n${context}`;
350
+ }
351
+
352
+ if (name === 'audit_component') {
353
+ const code = args.component_code || '';
354
+ const result = auditCode(code);
355
+ const rulesContext = readSkill('AUDIT.md');
356
+
357
+ let output = `# Audit Report\n\n## Summary\n${result.summary}\nPassed: ${result.passed ? 'Yes' : 'No'}\n\n## Violations (Errors)\n`;
358
+ if (result.violations.length) {
359
+ for (const v of result.violations) {
360
+ output += `- **Line ${v.line}**: ${v.message}\n`;
361
+ }
362
+ } else {
363
+ output += 'No violations found.\n';
364
+ }
365
+
366
+ output += '\n## Warnings\n';
367
+ if (result.warnings.length) {
368
+ for (const w of result.warnings) {
369
+ output += `- **Line ${w.line}**: ${w.message}\n`;
370
+ }
371
+ } else {
372
+ output += 'No warnings.\n';
373
+ }
374
+
375
+ output += `\n## Detailed Rules Reference\n\n${rulesContext}`;
376
+ return output;
377
+ }
378
+
379
+ if (name === 'detect_environment') {
380
+ const {
381
+ has_symbols_json,
382
+ has_symbols_dir,
383
+ has_package_json,
384
+ has_cdn_import,
385
+ has_iife_script,
386
+ has_json_data,
387
+ has_mermaid_config,
388
+ file_list,
389
+ } = args;
390
+
391
+ // Detect environment type
392
+ let envType = 'unknown';
393
+ let confidence = 'low';
394
+
395
+ if (has_mermaid_config) {
396
+ envType = 'remote_server';
397
+ confidence = 'high';
398
+ } else if (has_json_data) {
399
+ envType = 'json_runtime';
400
+ confidence = 'high';
401
+ } else if (has_symbols_json && has_symbols_dir) {
402
+ envType = 'local_project';
403
+ confidence = 'high';
404
+ } else if (has_symbols_dir || (has_package_json && has_symbols_json)) {
405
+ envType = 'local_project';
406
+ confidence = 'medium';
407
+ } else if (has_cdn_import || has_iife_script) {
408
+ envType = 'cdn';
409
+ confidence = 'high';
410
+ } else if (has_package_json) {
411
+ envType = 'local_project';
412
+ confidence = 'low';
413
+ } else if (file_list) {
414
+ const files = file_list.toLowerCase();
415
+ if (
416
+ files.includes('index.html') &&
417
+ !files.includes('package.json') &&
418
+ !files.includes('symbols.json')
419
+ ) {
420
+ envType = 'cdn';
421
+ confidence = 'medium';
422
+ }
423
+ }
424
+
425
+ // Build response with appropriate guide
426
+ const fullGuide = readSkill('RUNNING_APPS.md');
427
+ let output = `# Environment Detection\n\n**Detected: ${envType}** (confidence: ${confidence})\n\n`;
428
+
429
+ if (envType === 'local_project') {
430
+ output += `## Your Environment: Local Project\n\n`;
431
+ output += `You're working in a standard Symbols project with file-based structure.\n\n`;
432
+ output += `### Code Format\n`;
433
+ output += `- Components: \`export const Name = { extends: 'Flex', ... }\` in \`components/\`\n`;
434
+ output += `- Pages: \`export const pageName = { extends: 'Page', ... }\` in \`pages/\`\n`;
435
+ output += `- State: \`export default { key: value }\` in \`state.js\`\n`;
436
+ output += `- Functions: \`export const fn = function() {}\` in \`functions/\`\n`;
437
+ output += `- No imports between files (except pages/index.js)\n\n`;
438
+ output += `### Commands\n`;
439
+ output += `\`\`\`bash\nnpm start # dev server\nsmbls build # production build\nsmbls push # deploy to platform\nsmbls deploy # deploy to provider\n\`\`\`\n\n`;
440
+ const structureGuide = readSkill('PROJECT_STRUCTURE.md');
441
+ output += `### Full Project Structure Reference\n\n${structureGuide}`;
442
+ } else if (envType === 'cdn') {
443
+ output += `## Your Environment: CDN (Browser-Only)\n\n`;
444
+ output += `You're running Symbols directly in the browser via CDN import.\n\n`;
445
+ output += `### Code Format\n`;
446
+ output += `- Single HTML file with \`<script type="module">\`\n`;
447
+ output += `- Import: \`import { create } from 'https://esm.sh/smbls'\`\n`;
448
+ output += `- Define app as inline object tree\n`;
449
+ output += `- Mount: \`create(App, { designSystem, components, functions, state })\`\n`;
450
+ output += `- Components defined as JS variables (no file-based registry)\n\n`;
451
+ output += `### Limitations\n`;
452
+ output += `- No file-based routing (use tab/view switching)\n`;
453
+ output += `- No SSR\n`;
454
+ output += `- \`childExtends: 'Name'\` needs components passed to \`create()\`\n\n`;
455
+ const cdnGuide = readSkill('RUNNING_APPS.md');
456
+ output += `### Full CDN Reference\n\n${cdnGuide}`;
457
+ } else if (envType === 'json_runtime') {
458
+ output += `## Your Environment: JSON Runtime (Frank)\n\n`;
459
+ output += `You're running Symbols from serialized JSON project data.\n\n`;
460
+ output += `### Code Format\n`;
461
+ output += `- Project data is a JSON object with components, pages, designSystem, state, functions\n`;
462
+ output += `- Functions are serialized as strings\n`;
463
+ output += `- Convert with: \`smbls frank to-json ./symbols\` or \`toJSON({ entry: './symbols' })\`\n`;
464
+ output += `- Reverse with: \`smbls frank to-fs data.json -o ./output\` or \`toFS(data, './output')\`\n`;
465
+ output += `- Can be loaded by Mermaid server via JSON_PATH env var\n\n`;
466
+ output += `### Full Reference\n\n${fullGuide}`;
467
+ } else if (envType === 'remote_server') {
468
+ output += `## Your Environment: Remote Symbols Server (Mermaid)\n\n`;
469
+ output += `You're working with the Mermaid rendering server for hosted Symbols apps.\n\n`;
470
+ output += `### URL Patterns\n`;
471
+ output += `- Production: \`https://app.user.preview.symbols.app/\`\n`;
472
+ output += `- Development: \`https://app.user.preview.dev.symbols.app/\`\n`;
473
+ output += `- Staging: \`https://app.user.preview.staging.symbols.app/\`\n`;
474
+ output += `- Legacy: \`https://app.symbo.ls/\`\n`;
475
+ output += `- Custom domains supported\n\n`;
476
+ output += `### Deployment\n`;
477
+ output += `\`\`\`bash\nsmbls push # deploy to Symbols platform\n\`\`\`\n\n`;
478
+ output += `### Full Reference\n\n${fullGuide}`;
479
+ } else {
480
+ output += `## Could Not Determine Environment\n\n`;
481
+ output += `Provide more details about your project files to get specific guidance.\n\n`;
482
+ output += `### All 4 Ways to Run Symbols Apps\n\n${fullGuide}`;
483
+ }
484
+
485
+ return output;
486
+ }
487
+
92
488
  throw new Error(`Unknown tool: ${name}`);
93
489
  }
94
490
 
@@ -108,6 +504,20 @@ const SKILL_RESOURCES = {
108
504
  'symbols://skills/audit': 'AUDIT.md',
109
505
  'symbols://skills/design-to-code': 'DESIGN_TO_CODE.md',
110
506
  'symbols://skills/seo-metadata': 'SEO-METADATA.md',
507
+ 'symbols://skills/ssr-brender': 'SSR-BRENDER.md',
508
+ 'symbols://skills/cookbook': 'COOKBOOK.md',
509
+ 'symbols://skills/snippets': 'SNIPPETS.md',
510
+ 'symbols://skills/default-library': 'DEFAULT_LIBRARY.md',
511
+ 'symbols://skills/default-components': 'DEFAULT_COMPONENTS.md',
512
+ 'symbols://skills/learnings': 'LEARNINGS.md',
513
+ 'symbols://skills/running-apps': 'RUNNING_APPS.md',
514
+ 'symbols://skills/brand-identity': 'BRAND_IDENTITY.md',
515
+ 'symbols://skills/design-critique': 'DESIGN_CRITIQUE.md',
516
+ 'symbols://skills/design-system-architect': 'DESIGN_SYSTEM_ARCHITECT.md',
517
+ 'symbols://skills/design-trend': 'DESIGN_TREND.md',
518
+ 'symbols://skills/figma-matching': 'FIGMA_MATCHING.md',
519
+ 'symbols://skills/marketing-assets': 'MARKETING_ASSETS.md',
520
+ 'symbols://skills/presentation': 'PRESENTATION.md',
111
521
  };
112
522
 
113
523
  function listResources() {
@@ -162,13 +572,23 @@ const PROMPTS = [
162
572
  description: 'Prompt template for reviewing Symbols/DOMQL code.',
163
573
  arguments: [],
164
574
  },
575
+ {
576
+ name: 'symbols_convert_html_prompt',
577
+ description: 'Prompt template for converting HTML to Symbols.app components.',
578
+ arguments: [],
579
+ },
580
+ {
581
+ name: 'symbols_design_review_prompt',
582
+ description: 'Prompt template for visual/design audit against the design system.',
583
+ arguments: [],
584
+ },
165
585
  ];
166
586
 
167
587
  // ---------------------------------------------------------------------------
168
588
  // MCP JSON-RPC handler
169
589
  // ---------------------------------------------------------------------------
170
590
 
171
- const SERVER_INFO = { name: 'Symbols MCP', version: '1.0.10' };
591
+ const SERVER_INFO = { name: 'Symbols MCP', version: '1.1.1' };
172
592
 
173
593
  export function handleJsonRpc(req) {
174
594
  const { method, params, id } = req;
@@ -21,7 +21,8 @@ import {
21
21
  const CORS_HEADERS = {
22
22
  'Access-Control-Allow-Origin': '*',
23
23
  'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
24
- 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization, Mcp-Session-Id, MCP-Protocol-Version',
24
+ 'Access-Control-Allow-Headers':
25
+ 'Content-Type, Accept, Authorization, Mcp-Session-Id, MCP-Protocol-Version',
25
26
  'Access-Control-Expose-Headers': 'Mcp-Session-Id',
26
27
  };
27
28
 
@@ -56,7 +57,7 @@ function sseResponse(data, extraHeaders = {}) {
56
57
  function generateSessionId() {
57
58
  const bytes = new Uint8Array(16);
58
59
  crypto.getRandomValues(bytes);
59
- return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
60
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
60
61
  }
61
62
 
62
63
  function isNotificationOrResponse(msg) {
@@ -116,7 +117,7 @@ export async function handleRequest(request) {
116
117
  }
117
118
  return json({
118
119
  name: 'Symbols MCP',
119
- version: '1.0.11',
120
+ version: '1.1.1',
120
121
  description: 'MCP server for Symbols.app — documentation search and framework reference',
121
122
  endpoints: {
122
123
  mcp: 'POST / (Streamable HTTP transport)',