@symbo.ls/mcp-server 3.7.4 → 3.7.6

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 (2) hide show
  1. package/package.json +2 -6
  2. package/src/mcp-handler.js +855 -418
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * Uses pre-bundled skills data (generated by build.js) so it works
5
5
  * in both Node.js and Cloudflare Worker environments without fs access.
6
+ *
7
+ * Full parity with bin/symbols-mcp.js — 18 tools, all audit checks (Rules 27-33).
6
8
  */
7
9
 
8
10
  import { skills } from './skills-data.js';
@@ -27,7 +29,7 @@ function listSkillFiles() {
27
29
  }
28
30
 
29
31
  // ---------------------------------------------------------------------------
30
- // Audit helpers (deterministic rule checking)
32
+ // Audit helpers (deterministic rule checking — Rules 27-33)
31
33
  // ---------------------------------------------------------------------------
32
34
 
33
35
  const V2_PATTERNS = [
@@ -57,8 +59,168 @@ const RULE_CHECKS = [
57
59
  'Components must be plain objects, not functions that return objects',
58
60
  ],
59
61
  [
60
- /(?:padding|margin|gap|width|height)\s*:\s*['"]?\d+px/,
61
- 'Use design tokens (A, B, C) instead of hardcoded pixel values',
62
+ /\bextends\s*:\s*(?!['"])\w+/,
63
+ "FORBIDDEN: extends must be a quoted string name (extends: 'Name'), not a variable reference register in components/ and use string lookup (Rule 10)",
64
+ ],
65
+ [
66
+ /extends\s*:\s*['"]Flex['"]/,
67
+ "Replace extends: 'Flex' with flow: 'x' or flow: 'y' — do NOT just remove it, the element needs flow to stay flex (Rule 26)",
68
+ ],
69
+ [/extends\s*:\s*['"]Box['"]/, "Remove extends: 'Box' — every element is already a Box (Rule 26)"],
70
+ [
71
+ /extends\s*:\s*['"]Text['"]/,
72
+ "Remove extends: 'Text' — any element with text: is already Text (Rule 26)",
73
+ ],
74
+ [
75
+ /\bchildExtends\s*:\s*\{/,
76
+ 'FORBIDDEN: childExtends must be a quoted string name, not an inline object — register as a named component (Rule 10)',
77
+ ],
78
+ [
79
+ /(?:padding|margin|gap|width|height|fontSize|borderRadius|minWidth|maxWidth|minHeight|maxHeight|top|left|right|bottom|letterSpacing|lineHeight|borderWidth|outlineWidth)\s*:\s*['"]?\d+(?:\.\d+)?px/,
80
+ 'FORBIDDEN: No raw px values — use design system tokens (A, B, C, etc.) instead of hardcoded pixels (Rule 28)',
81
+ ],
82
+ [
83
+ /(?:color|background|backgroundColor|borderColor|fill|stroke)\s*:\s*['"]#[0-9a-fA-F]/,
84
+ 'Use design system color tokens (primary, secondary, white, gray.5) instead of hardcoded hex colors (Rule 27)',
85
+ ],
86
+ [
87
+ /(?:color|background|backgroundColor|borderColor|fill|stroke)\s*:\s*['"]rgb/,
88
+ 'Use design system color tokens instead of hardcoded rgb/rgba values (Rule 27)',
89
+ ],
90
+ [
91
+ /(?:color|background|backgroundColor|borderColor|fill|stroke)\s*:\s*['"]hsl/,
92
+ 'Use design system color tokens instead of hardcoded hsl/hsla values (Rule 27)',
93
+ ],
94
+ [
95
+ /<svg[\s>]/,
96
+ 'FORBIDDEN: Use the Icon component for SVG icons — store SVGs in designSystem/icons, never inline (Rule 29)',
97
+ ],
98
+ [
99
+ /tag\s*:\s*['"]svg['"]/,
100
+ "FORBIDDEN: Never use tag: 'svg' — store SVGs in designSystem/icons and use Icon component (Rule 29)",
101
+ ],
102
+ [
103
+ /tag\s*:\s*['"]path['"]/,
104
+ "FORBIDDEN: Never use tag: 'path' — store SVG paths in designSystem/icons and use Icon component (Rule 29)",
105
+ ],
106
+ [
107
+ /extends\s*:\s*['"]Svg['"]/,
108
+ 'Use Icon component for icons, not Svg — Svg is only for decorative/structural SVGs (Rule 29)',
109
+ ],
110
+ [
111
+ /\biconName\s*:/,
112
+ "FORBIDDEN: Use icon: not iconName: — the prop is icon: 'name' matching a key in designSystem/icons (Rule 29)",
113
+ ],
114
+ [
115
+ /document\.createElement\b/,
116
+ 'FORBIDDEN: No direct DOM manipulation — use DOMQL declarative object syntax instead (Rule 30)',
117
+ ],
118
+ [
119
+ /\.querySelector\b/,
120
+ 'FORBIDDEN: No DOM queries — reference elements by key name in the DOMQL object tree (Rule 30)',
121
+ ],
122
+ [
123
+ /\.appendChild\b/,
124
+ 'FORBIDDEN: No direct DOM manipulation — nest children as object keys or use children array (Rule 30)',
125
+ ],
126
+ [
127
+ /\.removeChild\b/,
128
+ 'FORBIDDEN: No direct DOM manipulation — use if: (el, s) => condition to show/hide (Rule 30)',
129
+ ],
130
+ [
131
+ /\.insertBefore\b/,
132
+ 'FORBIDDEN: No direct DOM manipulation — use children array ordering (Rule 30)',
133
+ ],
134
+ [/\.innerHTML\s*=/, 'FORBIDDEN: No direct DOM manipulation — use text: or html: prop (Rule 30)'],
135
+ [
136
+ /\.classList\./,
137
+ "FORBIDDEN: No direct class manipulation — use isX + '.isX' pattern (Rule 19/30)",
138
+ ],
139
+ [
140
+ /\.setAttribute\b/,
141
+ 'FORBIDDEN: No direct DOM manipulation — set attributes at root level in DOMQL (Rule 30)',
142
+ ],
143
+ [
144
+ /\.addEventListener\b/,
145
+ 'FORBIDDEN: No direct event binding — use onX props: onClick, onInput, etc. (Rule 30)',
146
+ ],
147
+ [
148
+ /\.style\.\w+\s*=/,
149
+ 'FORBIDDEN: No direct style manipulation — use DOMQL CSS-in-props (Rule 30)',
150
+ ],
151
+ [
152
+ /html\s*:\s*\(?.*\)?\s*=>\s*/,
153
+ 'FORBIDDEN: Never use html: as a function returning markup — use DOMQL children, nesting, text:, and if: instead (Rule 31)',
154
+ ],
155
+ [
156
+ /return\s*`<\w+/,
157
+ 'FORBIDDEN: Never return HTML template literals — use DOMQL declarative children and nesting (Rule 31)',
158
+ ],
159
+ [
160
+ /style\s*=\s*['"`]/,
161
+ 'FORBIDDEN: No inline style= strings in html — use DOMQL CSS-in-props (Rule 31)',
162
+ ],
163
+ [
164
+ /window\.innerWidth/,
165
+ 'FORBIDDEN: No window.innerWidth checks — use @mobileL, @tabletS responsive breakpoints (Rule 31)',
166
+ ],
167
+ [
168
+ /\.parentNode\b/,
169
+ 'FORBIDDEN: No DOM traversal — use state and reactive props instead of walking the DOM tree (Rule 32)',
170
+ ],
171
+ [
172
+ /\.childNodes\b/,
173
+ 'FORBIDDEN: No DOM traversal — use state-driven children with if: props (Rule 32)',
174
+ ],
175
+ [/\.textContent\b/, 'FORBIDDEN: No DOM property access — use state and text: prop (Rule 32)'],
176
+ [
177
+ /Array\.from\(\w+\.children\)/,
178
+ 'FORBIDDEN: No DOM child iteration — use state arrays with children/childExtends and if: filtering (Rule 32)',
179
+ ],
180
+ [
181
+ /\.style\.display\s*=/,
182
+ 'FORBIDDEN: No style.display toggling — use show:/hide: to toggle visibility or if: to remove from DOM (Rule 32)',
183
+ ],
184
+ [/\.style\.cssText\s*=/, 'FORBIDDEN: No direct cssText — use DOMQL CSS-in-props (Rule 32)'],
185
+ [
186
+ /\.dataset\./,
187
+ 'FORBIDDEN: No dataset manipulation — use state and attr: for data-* attributes (Rule 32)',
188
+ ],
189
+ [
190
+ /\.remove\(\)/,
191
+ 'FORBIDDEN: No DOM node removal — use if: (el, s) => condition to conditionally render (Rule 32)',
192
+ ],
193
+ [
194
+ /el\.node\.\w+\s*=/,
195
+ 'FORBIDDEN: No direct el.node property assignment — use DOMQL props (placeholder:, value:, text:, etc.). Reading el.node is fine (Rule 39), writing is not (Rule 32)',
196
+ ],
197
+ [
198
+ /document\.getElementById\b/,
199
+ "FORBIDDEN: No document.getElementById — use el.lookdown('key') to find DOMQL elements (Rule 40)",
200
+ ],
201
+ [
202
+ /document\.querySelectorAll\b/,
203
+ "FORBIDDEN: No document.querySelectorAll — use el.lookdownAll('key') to find DOMQL elements (Rule 40)",
204
+ ],
205
+ [
206
+ /el\.parent\.state\b/,
207
+ "FORBIDDEN: Never use el.parent.state — with childrenAs: 'state', use s.field directly (Rule 36)",
208
+ ],
209
+ [
210
+ /el\.context\.designSystem\b/,
211
+ 'FORBIDDEN: Never read designSystem from el.context in props — use token strings directly (Rule 38)',
212
+ ],
213
+ [
214
+ /^const\s+\w+\s*=\s*(?:\(|function)/m,
215
+ "FORBIDDEN: No module-level helper functions — move to functions/ and call via el.call('fnName') (Rule 33)",
216
+ ],
217
+ [
218
+ /^let\s+\w+\s*=/m,
219
+ 'FORBIDDEN: No module-level variables — use el.scope for local state, functions/ for helpers (Rule 33)',
220
+ ],
221
+ [
222
+ /^var\s+\w+\s*=/m,
223
+ 'FORBIDDEN: No module-level variables — use el.scope for local state, functions/ for helpers (Rule 33)',
62
224
  ],
63
225
  ];
64
226
 
@@ -76,7 +238,7 @@ function auditCode(code) {
76
238
  }
77
239
 
78
240
  for (const [pattern, message] of RULE_CHECKS) {
79
- const regex = new RegExp(pattern.source, pattern.flags);
241
+ const regex = new RegExp(pattern.source, pattern.flags || 'g');
80
242
  let match;
81
243
  while ((match = regex.exec(code)) !== null) {
82
244
  const line = code.slice(0, match.index).split('\n').length;
@@ -97,6 +259,322 @@ function auditCode(code) {
97
259
  };
98
260
  }
99
261
 
262
+ // ---------------------------------------------------------------------------
263
+ // JS-to-JSON conversion (mirrors frank pipeline)
264
+ // ---------------------------------------------------------------------------
265
+
266
+ const DATA_KEYS = [
267
+ 'components',
268
+ 'pages',
269
+ 'snippets',
270
+ 'functions',
271
+ 'methods',
272
+ 'designSystem',
273
+ 'state',
274
+ 'dependencies',
275
+ 'files',
276
+ 'config',
277
+ ];
278
+ const CODE_SECTIONS = new Set(['components', 'pages', 'functions', 'methods', 'snippets']);
279
+
280
+ function findObjectEnd(code, start) {
281
+ if (start >= code.length) return -1;
282
+ let i = start;
283
+ while (i < code.length && ' \t\n\r'.includes(code[i])) i++;
284
+ if (i >= code.length || !'{['.includes(code[i])) return -1;
285
+ const opener = code[i];
286
+ const closer = opener === '{' ? '}' : ']';
287
+ let depth = 1;
288
+ i++;
289
+ let inString = null,
290
+ inTemplate = false,
291
+ escaped = false;
292
+ while (i < code.length && depth > 0) {
293
+ const ch = code[i];
294
+ if (escaped) {
295
+ escaped = false;
296
+ i++;
297
+ continue;
298
+ }
299
+ if (ch === '\\') {
300
+ escaped = true;
301
+ i++;
302
+ continue;
303
+ }
304
+ if (inString) {
305
+ if (ch === inString) inString = null;
306
+ i++;
307
+ continue;
308
+ }
309
+ if (inTemplate) {
310
+ if (ch === '`') inTemplate = false;
311
+ i++;
312
+ continue;
313
+ }
314
+ if (ch === "'" || ch === '"') inString = ch;
315
+ else if (ch === '`') inTemplate = true;
316
+ else if (ch === opener) depth++;
317
+ else if (ch === closer) depth--;
318
+ i++;
319
+ }
320
+ return depth === 0 ? i : -1;
321
+ }
322
+
323
+ function parseJsToJson(sourceCode) {
324
+ const result = {};
325
+ const code = sourceCode.replace(/^\s*import\s+.*$/gm, '');
326
+ const stripped = code.trim();
327
+ if (stripped.startsWith('{')) {
328
+ try {
329
+ return JSON.parse(stripped);
330
+ } catch {}
331
+ }
332
+ const exportRe = /export\s+const\s+(\w+)\s*=\s*/g;
333
+ let m;
334
+ const matches = [];
335
+ while ((m = exportRe.exec(code)) !== null) matches.push(m);
336
+ for (const match of matches) {
337
+ const name = match[1];
338
+ const start = match.index + match[0].length;
339
+ const end = findObjectEnd(code, start);
340
+ if (end === -1) continue;
341
+ result[name] = code.slice(start, end).trim();
342
+ }
343
+ if (!matches.length) {
344
+ const defMatch = /export\s+default\s+/.exec(code);
345
+ if (defMatch) {
346
+ const start = defMatch.index + defMatch[0].length;
347
+ const end = findObjectEnd(code, start);
348
+ if (end !== -1) result['__default__'] = code.slice(start, end).trim();
349
+ }
350
+ }
351
+ return result;
352
+ }
353
+
354
+ function normalizeQuotes(s) {
355
+ const result = [];
356
+ let i = 0;
357
+ while (i < s.length) {
358
+ if (s[i] === "'") {
359
+ let j = i + 1;
360
+ while (j < s.length) {
361
+ if (s[j] === '\\' && j + 1 < s.length) {
362
+ j += 2;
363
+ continue;
364
+ }
365
+ if (s[j] === "'") break;
366
+ j++;
367
+ }
368
+ const inner = s.slice(i + 1, j).replace(/"/g, '\\"');
369
+ result.push(`"${inner}"`);
370
+ i = j + 1;
371
+ } else if (s[i] === '"') {
372
+ let j = i + 1;
373
+ while (j < s.length) {
374
+ if (s[j] === '\\' && j + 1 < s.length) {
375
+ j += 2;
376
+ continue;
377
+ }
378
+ if (s[j] === '"') break;
379
+ j++;
380
+ }
381
+ result.push(s.slice(i, j + 1));
382
+ i = j + 1;
383
+ } else {
384
+ result.push(s[i]);
385
+ i++;
386
+ }
387
+ }
388
+ return result.join('');
389
+ }
390
+
391
+ function isValuePosition(code, pos) {
392
+ let j = pos - 1;
393
+ while (j >= 0 && ' \t\n\r'.includes(code[j])) j--;
394
+ return j >= 0 && ':=,['.includes(code[j]);
395
+ }
396
+
397
+ function findFunctionEnd(code, start, isArrow) {
398
+ let i = start;
399
+ if (isArrow) {
400
+ const arrowPos = code.indexOf('=>', i);
401
+ if (arrowPos === -1) return -1;
402
+ i = arrowPos + 2;
403
+ while (i < code.length && ' \t\n\r'.includes(code[i])) i++;
404
+ if (i < code.length && code[i] === '{') return findObjectEnd(code, i);
405
+ let depth = 0;
406
+ while (i < code.length) {
407
+ const ch = code[i];
408
+ if ('({['.includes(ch)) depth++;
409
+ else if (')}]'.includes(ch)) {
410
+ if (depth === 0) return i;
411
+ depth--;
412
+ } else if (ch === ',' && depth === 0) return i;
413
+ i++;
414
+ }
415
+ return i;
416
+ } else {
417
+ const brace = code.indexOf('{', i);
418
+ if (brace === -1) return -1;
419
+ return findObjectEnd(code, brace);
420
+ }
421
+ }
422
+
423
+ function stringifyFunctionsInJs(code) {
424
+ const result = [];
425
+ let i = 0;
426
+ while (i < code.length) {
427
+ const rest = code.slice(i);
428
+ const arrowMatch = rest.match(/^(\([^)]*\)\s*=>|\w+\s*=>)\s*/);
429
+ const funcMatch = rest.match(/^function\s*\w*\s*\(/);
430
+ if (arrowMatch && isValuePosition(code, i)) {
431
+ const fnEnd = findFunctionEnd(code, i, true);
432
+ if (fnEnd > i) {
433
+ result.push(JSON.stringify(code.slice(i, fnEnd).trim()));
434
+ i = fnEnd;
435
+ continue;
436
+ }
437
+ }
438
+ if (funcMatch && isValuePosition(code, i)) {
439
+ const fnEnd = findFunctionEnd(code, i, false);
440
+ if (fnEnd > i) {
441
+ result.push(JSON.stringify(code.slice(i, fnEnd).trim()));
442
+ i = fnEnd;
443
+ continue;
444
+ }
445
+ }
446
+ result.push(code[i]);
447
+ i++;
448
+ }
449
+ return result.join('');
450
+ }
451
+
452
+ function jsObjToJson(rawJs) {
453
+ let s = rawJs.trim();
454
+ s = stringifyFunctionsInJs(s);
455
+ s = normalizeQuotes(s);
456
+ s = s.replace(/(?<=[\{,\n])\s*([a-zA-Z_$][\w$]*)\s*:/g, ' "$1":');
457
+ s = s.replace(/(?<=[\{,\n])\s*(@[\w$]+)\s*:/g, ' "$1":');
458
+ s = s.replace(/,\s*([}\]])/g, '$1');
459
+ try {
460
+ return JSON.parse(s);
461
+ } catch {
462
+ return s;
463
+ }
464
+ }
465
+
466
+ function encodeSchemaCode(codeStr) {
467
+ return codeStr.replace(/\n/g, '/////n').replace(/`/g, '/////tilde');
468
+ }
469
+
470
+ function buildSchemaItem(section, key, value) {
471
+ const codeStr = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
472
+ const item = {
473
+ title: key,
474
+ key,
475
+ type: section,
476
+ code: encodeSchemaCode(`export default ${codeStr}`),
477
+ };
478
+ if (section === 'components' || section === 'pages') {
479
+ Object.assign(item, {
480
+ settings: { gridOptions: {} },
481
+ props: {},
482
+ interactivity: [],
483
+ dataTypes: [],
484
+ error: null,
485
+ });
486
+ }
487
+ return item;
488
+ }
489
+
490
+ function buildChangesAndSchema(data) {
491
+ const changes = [],
492
+ granular = [],
493
+ orders = [];
494
+ for (const [sectionKey, sectionData] of Object.entries(data)) {
495
+ if (!DATA_KEYS.includes(sectionKey)) continue;
496
+ if (typeof sectionData !== 'object' || sectionData === null || Array.isArray(sectionData)) {
497
+ changes.push(['update', [sectionKey], sectionData]);
498
+ granular.push(['update', [sectionKey], sectionData]);
499
+ continue;
500
+ }
501
+ const sectionItemKeys = [];
502
+ for (const [itemKey, itemValue] of Object.entries(sectionData)) {
503
+ const itemPath = [sectionKey, itemKey];
504
+ changes.push(['update', itemPath, itemValue]);
505
+ sectionItemKeys.push(itemKey);
506
+ if (typeof itemValue === 'object' && itemValue !== null && !Array.isArray(itemValue)) {
507
+ const itemKeys = [];
508
+ for (const [propKey, propValue] of Object.entries(itemValue)) {
509
+ granular.push(['update', [...itemPath, propKey], propValue]);
510
+ itemKeys.push(propKey);
511
+ }
512
+ if (itemKeys.length) orders.push({ path: itemPath, keys: itemKeys });
513
+ } else {
514
+ granular.push(['update', itemPath, itemValue]);
515
+ }
516
+ if (CODE_SECTIONS.has(sectionKey)) {
517
+ const schemaItem = buildSchemaItem(sectionKey, itemKey, itemValue);
518
+ const schemaPath = ['schema', sectionKey, itemKey];
519
+ changes.push(['update', schemaPath, schemaItem]);
520
+ granular.push(['delete', [...schemaPath, 'code']]);
521
+ for (const [sk, sv] of Object.entries(schemaItem)) {
522
+ granular.push(['update', [...schemaPath, sk], sv]);
523
+ }
524
+ }
525
+ }
526
+ if (sectionItemKeys.length) orders.push({ path: [sectionKey], keys: sectionItemKeys });
527
+ }
528
+ return { changes, granular, orders };
529
+ }
530
+
531
+ // ---------------------------------------------------------------------------
532
+ // API helpers
533
+ // ---------------------------------------------------------------------------
534
+
535
+ const API_BASE = 'https://api.symbols.app';
536
+
537
+ const AUTH_HELP = `To authenticate, provide one of:
538
+ - **token**: JWT from \`smbls login\` (stored in ~/.smblsrc) or env var SYMBOLS_TOKEN
539
+ - **api_key**: API key (sk_live_...) from your project's integration settings
540
+
541
+ To get a token:
542
+ 1. Run \`smbls login\` in your terminal, or
543
+ 2. Use the \`login\` tool with your email and password`;
544
+
545
+ function authHeader(token, apiKey) {
546
+ if (apiKey) return { 'Authorization': `ApiKey ${apiKey}`, 'Content-Type': 'application/json' };
547
+ if (token) return { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
548
+ return null;
549
+ }
550
+
551
+ async function apiRequest(method, path, { token, api_key, body } = {}) {
552
+ const headers = authHeader(token, api_key);
553
+ if (!headers) return { success: false, error: 'No credentials provided', message: AUTH_HELP };
554
+ const opts = { method, headers };
555
+ if (body) opts.body = JSON.stringify(body);
556
+ const resp = await fetch(`${API_BASE}${path}`, opts);
557
+ try {
558
+ return await resp.json();
559
+ } catch {
560
+ return { success: false, error: `HTTP ${resp.status}`, message: await resp.text() };
561
+ }
562
+ }
563
+
564
+ async function resolveProjectId(project, token, apiKey) {
565
+ if (!project) return { id: '', error: 'Project identifier is required.' };
566
+ const isKey = project.startsWith('pr_') || !/^[0-9a-f]+$/.test(project);
567
+ if (isKey) {
568
+ const result = await apiRequest('GET', `/core/projects/key/${project}`, {
569
+ token,
570
+ api_key: apiKey,
571
+ });
572
+ if (result.success) return { id: result.data?._id || '', error: null };
573
+ return { id: '', error: `Project '${project}' not found: ${result.error || 'unknown error'}` };
574
+ }
575
+ return { id: project, error: null };
576
+ }
577
+
100
578
  // ---------------------------------------------------------------------------
101
579
  // Tools
102
580
  // ---------------------------------------------------------------------------
@@ -110,34 +588,27 @@ const TOOLS = [
110
588
  },
111
589
  {
112
590
  name: 'search_symbols_docs',
113
- description: 'Search the Symbols documentation knowledge base for relevant information.',
591
+ description:
592
+ 'Search the Symbols documentation knowledge base for relevant information including CLI commands, SDK services, syntax, components, and more.',
114
593
  inputSchema: {
115
594
  type: 'object',
116
595
  properties: {
117
596
  query: {
118
597
  type: 'string',
119
- description: 'Natural language search query about Symbols/DOMQL',
120
- },
121
- max_results: {
122
- type: 'number',
123
- description: 'Maximum number of results (1-5)',
124
- default: 3,
598
+ description: 'Natural language search query about Symbols/DOMQL/CLI/SDK',
125
599
  },
600
+ max_results: { type: 'number', description: 'Maximum number of results (1-5)', default: 3 },
126
601
  },
127
602
  required: ['query'],
128
603
  },
129
604
  },
130
605
  {
131
606
  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.',
607
+ description: 'Generate a Symbols.app DOMQL v3 component from a description with full context.',
134
608
  inputSchema: {
135
609
  type: 'object',
136
610
  properties: {
137
- description: {
138
- type: 'string',
139
- description: 'What the component should do and look like',
140
- },
611
+ description: { type: 'string', description: 'What the component should do and look like' },
141
612
  component_name: {
142
613
  type: 'string',
143
614
  description: 'PascalCase name for the component',
@@ -149,49 +620,54 @@ const TOOLS = [
149
620
  },
150
621
  {
151
622
  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.',
623
+ description: 'Generate a Symbols.app page with routing integration and full context.',
154
624
  inputSchema: {
155
625
  type: 'object',
156
626
  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
- },
627
+ description: { type: 'string', description: 'What the page should contain and do' },
628
+ page_name: { type: 'string', description: 'camelCase name for the page', default: 'home' },
166
629
  },
167
630
  required: ['description'],
168
631
  },
169
632
  },
170
633
  {
171
634
  name: 'convert_react',
172
- description:
173
- 'Convert React/JSX code to Symbols.app DOMQL v3. Returns migration rules, syntax reference, and examples as context.',
635
+ description: 'Convert React/JSX code to Symbols.app DOMQL v3 with migration context.',
174
636
  inputSchema: {
175
637
  type: 'object',
176
638
  properties: {
177
- source_code: {
178
- type: 'string',
179
- description: 'The React/JSX source code to convert',
180
- },
639
+ source_code: { type: 'string', description: 'The React/JSX source code to convert' },
181
640
  },
182
641
  required: ['source_code'],
183
642
  },
184
643
  },
185
644
  {
186
645
  name: 'convert_html',
646
+ description: 'Convert raw HTML/CSS to Symbols.app DOMQL v3 components with full context.',
647
+ inputSchema: {
648
+ type: 'object',
649
+ properties: {
650
+ source_code: { type: 'string', description: 'The HTML/CSS source code to convert' },
651
+ },
652
+ required: ['source_code'],
653
+ },
654
+ },
655
+ {
656
+ name: 'convert_to_json',
187
657
  description:
188
- 'Convert raw HTML/CSS to Symbols.app DOMQL v3 components. Returns component catalog, syntax reference, and design tokens as context.',
658
+ 'Convert DOMQL v3 JavaScript source code to platform JSON format for save_to_project.',
189
659
  inputSchema: {
190
660
  type: 'object',
191
661
  properties: {
192
662
  source_code: {
193
663
  type: 'string',
194
- description: 'The HTML/CSS source code to convert',
664
+ description: 'JavaScript source code with export const/default statements',
665
+ },
666
+ section: {
667
+ type: 'string',
668
+ description:
669
+ 'Target section: components, pages, functions, snippets, designSystem, state',
670
+ default: 'components',
195
671
  },
196
672
  },
197
673
  required: ['source_code'],
@@ -200,202 +676,149 @@ const TOOLS = [
200
676
  {
201
677
  name: 'audit_component',
202
678
  description:
203
- 'Audit a Symbols/DOMQL component for v3 compliance and best practices. Returns a structured report with violations, warnings, and compliance score.',
679
+ 'Audit a Symbols/DOMQL component for v3 compliance checks for v2 syntax, raw px values, hardcoded colors, direct DOM manipulation, and more.',
204
680
  inputSchema: {
205
681
  type: 'object',
206
682
  properties: {
207
- component_code: {
208
- type: 'string',
209
- description: 'The JavaScript component code to audit',
210
- },
683
+ component_code: { type: 'string', description: 'The JavaScript component code to audit' },
211
684
  },
212
685
  required: ['component_code'],
213
686
  },
214
687
  },
215
688
  {
216
- name: 'login',
689
+ name: 'detect_environment',
217
690
  description:
218
- 'Log in to the Symbols platform with email and password. Returns a JWT token for use with publish and push tools.',
691
+ 'Detect project type (local, CDN, JSON runtime, or remote server) based on project indicators.',
219
692
  inputSchema: {
220
693
  type: 'object',
221
694
  properties: {
222
- email: {
223
- type: 'string',
224
- description: 'Symbols account email address',
225
- },
226
- password: {
227
- type: 'string',
228
- description: 'Symbols account password',
229
- },
695
+ has_symbols_json: { type: 'boolean' },
696
+ has_symbols_dir: { type: 'boolean' },
697
+ has_package_json: { type: 'boolean' },
698
+ has_cdn_import: { type: 'boolean' },
699
+ has_iife_script: { type: 'boolean' },
700
+ has_json_data: { type: 'boolean' },
701
+ has_mermaid_config: { type: 'boolean' },
702
+ file_list: { type: 'string', default: '' },
230
703
  },
704
+ },
705
+ },
706
+ {
707
+ name: 'get_cli_reference',
708
+ description: 'Returns the complete Symbols CLI (@symbo.ls/cli) command reference.',
709
+ inputSchema: { type: 'object', properties: {} },
710
+ },
711
+ {
712
+ name: 'get_sdk_reference',
713
+ description: 'Returns the complete Symbols SDK (@symbo.ls/sdk) API reference.',
714
+ inputSchema: { type: 'object', properties: {} },
715
+ },
716
+ {
717
+ name: 'login',
718
+ description: 'Log in to the Symbols platform and get an access token.',
719
+ inputSchema: {
720
+ type: 'object',
721
+ properties: { email: { type: 'string' }, password: { type: 'string' } },
231
722
  required: ['email', 'password'],
232
723
  },
233
724
  },
234
725
  {
235
- name: 'publish',
236
- description:
237
- 'Publish a version of a Symbols project to the platform. Requires authentication via token or api_key.',
726
+ name: 'list_projects',
727
+ description: "List the user's Symbols projects (names, keys, IDs).",
728
+ inputSchema: {
729
+ type: 'object',
730
+ properties: { token: { type: 'string' }, api_key: { type: 'string' } },
731
+ },
732
+ },
733
+ {
734
+ name: 'create_project',
735
+ description: 'Create a new Symbols project on the platform.',
238
736
  inputSchema: {
239
737
  type: 'object',
240
738
  properties: {
241
- project: {
242
- type: 'string',
243
- description: 'Project ID (MongoDB ObjectId) or project key (pr_xxxx)',
244
- },
245
- token: {
246
- type: 'string',
247
- description: 'JWT access token from login or ~/.smblsrc',
248
- },
249
- api_key: {
250
- type: 'string',
251
- description:
252
- 'API key (sk_live_...) from project integration settings. Alternative to token.',
253
- },
254
- version: {
255
- type: 'string',
256
- description: 'Version string or version ID to publish. Leave empty for latest.',
257
- default: '',
258
- },
259
- branch: {
260
- type: 'string',
261
- description: 'Branch to publish from',
262
- default: 'main',
263
- },
739
+ name: { type: 'string' },
740
+ key: { type: 'string' },
741
+ token: { type: 'string' },
742
+ api_key: { type: 'string' },
743
+ visibility: { type: 'string', default: 'private' },
744
+ },
745
+ required: ['name'],
746
+ },
747
+ },
748
+ {
749
+ name: 'get_project',
750
+ description: "Get a project's current data (components, pages, design system, state).",
751
+ inputSchema: {
752
+ type: 'object',
753
+ properties: {
754
+ project: { type: 'string' },
755
+ token: { type: 'string' },
756
+ api_key: { type: 'string' },
757
+ branch: { type: 'string', default: 'main' },
264
758
  },
265
759
  required: ['project'],
266
760
  },
267
761
  },
268
762
  {
269
- name: 'push',
763
+ name: 'save_to_project',
270
764
  description:
271
- 'Push/deploy a Symbols project to a specific environment. Requires authentication via token or api_key.',
765
+ 'Save components/pages/data to a project creates a new version with change tuples, granular changes, orders, and auto-generated schema entries.',
272
766
  inputSchema: {
273
767
  type: 'object',
274
768
  properties: {
275
- project: {
276
- type: 'string',
277
- description: 'Project ID (MongoDB ObjectId) or project key (pr_xxxx)',
278
- },
279
- token: {
280
- type: 'string',
281
- description: 'JWT access token from login or ~/.smblsrc',
282
- },
283
- api_key: {
284
- type: 'string',
285
- description:
286
- 'API key (sk_live_...) from project integration settings. Alternative to token.',
287
- },
288
- environment: {
289
- type: 'string',
290
- description: 'Target environment key (e.g. "production", "staging", "dev")',
291
- default: 'production',
292
- },
293
- mode: {
294
- type: 'string',
295
- description:
296
- 'Deploy mode — "latest" (newest from branch), "published" (current published version), "version" (specific version), or "branch" (track a branch)',
297
- default: 'published',
298
- },
299
- version: {
300
- type: 'string',
301
- description: 'Required when mode is "version" — the version string or ID to deploy',
302
- default: '',
303
- },
304
- branch: {
305
- type: 'string',
306
- description: 'Branch to deploy from when mode is "latest" or "branch"',
307
- default: 'main',
308
- },
769
+ project: { type: 'string' },
770
+ changes: { type: 'string', description: 'JSON string with project data' },
771
+ token: { type: 'string' },
772
+ api_key: { type: 'string' },
773
+ message: { type: 'string' },
774
+ branch: { type: 'string', default: 'main' },
775
+ },
776
+ required: ['project', 'changes'],
777
+ },
778
+ },
779
+ {
780
+ name: 'publish',
781
+ description: 'Publish a version of a Symbols project (make it live).',
782
+ inputSchema: {
783
+ type: 'object',
784
+ properties: {
785
+ project: { type: 'string' },
786
+ token: { type: 'string' },
787
+ api_key: { type: 'string' },
788
+ version: { type: 'string' },
789
+ branch: { type: 'string', default: 'main' },
309
790
  },
310
791
  required: ['project'],
311
792
  },
312
793
  },
313
794
  {
314
- name: 'detect_environment',
315
- description:
316
- '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.',
795
+ name: 'push',
796
+ description: 'Deploy a Symbols project to an environment (production, staging, dev).',
317
797
  inputSchema: {
318
798
  type: 'object',
319
799
  properties: {
320
- has_symbols_json: {
321
- type: 'boolean',
322
- description: 'Whether symbols.json exists in the project root',
323
- },
324
- has_symbols_dir: {
325
- type: 'boolean',
326
- description: 'Whether a symbols/ directory exists with components/, pages/, etc.',
327
- },
328
- has_package_json: {
329
- type: 'boolean',
330
- description: 'Whether package.json exists with smbls dependency',
331
- },
332
- has_cdn_import: {
333
- type: 'boolean',
334
- description:
335
- 'Whether HTML files contain CDN imports (esm.sh/smbls, cdn.jsdelivr.net/npm/smbls, etc.)',
336
- },
337
- has_iife_script: {
338
- type: 'boolean',
339
- description: 'Whether HTML files use <script src="...smbls"> (IIFE global)',
340
- },
341
- has_json_data: {
342
- type: 'boolean',
343
- description: 'Whether the project uses frank-generated JSON data files',
344
- },
345
- has_mermaid_config: {
346
- type: 'boolean',
347
- description:
348
- 'Whether mermaid/wrangler config or GATEWAY_URL/JSON_PATH env vars are present',
349
- },
350
- file_list: {
351
- type: 'string',
352
- description:
353
- 'Comma-separated list of key files in the project root (e.g. "index.html,package.json,symbols.json")',
354
- },
800
+ project: { type: 'string' },
801
+ token: { type: 'string' },
802
+ api_key: { type: 'string' },
803
+ environment: { type: 'string', default: 'production' },
804
+ mode: { type: 'string', default: 'published' },
805
+ version: { type: 'string' },
806
+ branch: { type: 'string', default: 'main' },
355
807
  },
808
+ required: ['project'],
356
809
  },
357
810
  },
358
811
  ];
359
812
 
360
- const API_BASE = 'https://api.symbols.app';
361
-
362
- const AUTH_HELP = `To authenticate, provide one of:
363
- - **token**: JWT from \`smbls login\` (stored in ~/.smblsrc) or env var SYMBOLS_TOKEN
364
- - **api_key**: API key (sk_live_...) from your project's integration settings
365
-
366
- To get a token:
367
- 1. Run \`smbls login\` in your terminal, or
368
- 2. Use the \`login\` tool with your email and password
369
-
370
- Your project can be identified by either:
371
- - **project**: MongoDB ObjectId (from project settings or symbols.json)
372
- - **project**: Project key (pr_xxxx, found in symbols.json or project URL)`;
373
-
374
- function authHeader(token, apiKey) {
375
- if (apiKey) return { 'Authorization': `ApiKey ${apiKey}`, 'Content-Type': 'application/json' };
376
- if (token) return { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
377
- return null;
378
- }
379
-
380
- async function apiRequest(method, path, { token, api_key, body } = {}) {
381
- const headers = authHeader(token, api_key);
382
- if (!headers) return { success: false, error: 'No credentials provided', message: AUTH_HELP };
383
- const resp = await fetch(`${API_BASE}${path}`, {
384
- method,
385
- headers,
386
- body: body ? JSON.stringify(body) : undefined,
387
- });
388
- try {
389
- return await resp.json();
390
- } catch {
391
- return { success: false, error: `HTTP ${resp.status}`, message: await resp.text() };
392
- }
393
- }
813
+ const MANDATORY_REQUIREMENTS = `- **MANDATORY: ALL values MUST use design system tokens** — spacing (A, B, C, D), colors (primary, surface, white, gray.5), typography (fontSize: 'B'). ZERO px values, ZERO hex colors, ZERO rgb/hsl.
814
+ - Use Icon component for SVGs — store icons in designSystem/icons
815
+ - NO direct DOM manipulation all structure via DOMQL declarative syntax
816
+ - NO variables outside component scope use el.call('fn') or el.scope`;
394
817
 
395
818
  async function callTool(name, args = {}) {
396
- if (name === 'get_project_rules') {
397
- return readSkill('RULES.md');
398
- }
819
+ if (name === 'get_project_rules') return readSkill('RULES.md');
820
+ if (name === 'get_cli_reference') return readSkill('CLI.md');
821
+ if (name === 'get_sdk_reference') return readSkill('SDK.md');
399
822
 
400
823
  if (name === 'search_symbols_docs') {
401
824
  const query = args.query || '';
@@ -405,13 +828,10 @@ async function callTool(name, args = {}) {
405
828
  .split(/\s+/)
406
829
  .filter((w) => w.length > 2);
407
830
  if (!keywords.length) keywords.push(query.toLowerCase());
408
-
409
831
  const results = [];
410
832
  for (const fname of listSkillFiles()) {
411
833
  const content = readSkill(fname);
412
- const contentLower = content.toLowerCase();
413
- if (!keywords.some((kw) => contentLower.includes(kw))) continue;
414
-
834
+ if (!keywords.some((kw) => content.toLowerCase().includes(kw))) continue;
415
835
  const lines = content.split('\n');
416
836
  for (let i = 0; i < lines.length; i++) {
417
837
  if (keywords.some((kw) => lines[i].toLowerCase().includes(kw))) {
@@ -424,41 +844,37 @@ async function callTool(name, args = {}) {
424
844
  }
425
845
  if (results.length >= maxResults) break;
426
846
  }
427
-
428
- return results.length
429
- ? JSON.stringify(results, null, 2)
430
- : `No results found for '${query}'. Try a different search term.`;
847
+ return results.length ? JSON.stringify(results, null, 2) : `No results found for '${query}'.`;
431
848
  }
432
849
 
433
850
  if (name === 'generate_component') {
434
- const desc = args.description || '';
435
851
  const compName = args.component_name || 'MyComponent';
436
852
  const context = readSkills(
437
853
  'RULES.md',
854
+ 'COMMON_MISTAKES.md',
438
855
  'COMPONENTS.md',
439
856
  'SYNTAX.md',
440
857
  'COOKBOOK.md',
441
858
  'DEFAULT_LIBRARY.md',
442
859
  );
443
- 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}`;
860
+ return `# Generate Component: ${compName}\n\n## Description\n${args.description}\n\n## Requirements\n- Named export: \`export const ${compName} = { ... }\`\n- DOMQL v3 syntax only (extends, childExtends, flattened props, onX events)\n${MANDATORY_REQUIREMENTS}\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}`;
444
861
  }
445
862
 
446
863
  if (name === 'generate_page') {
447
- const desc = args.description || '';
448
864
  const pageName = args.page_name || 'home';
449
865
  const context = readSkills(
450
866
  'RULES.md',
867
+ 'COMMON_MISTAKES.md',
451
868
  'PROJECT_STRUCTURE.md',
452
869
  'PATTERNS.md',
453
870
  'SNIPPETS.md',
454
871
  'DEFAULT_LIBRARY.md',
455
872
  'COMPONENTS.md',
456
873
  );
457
- 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}`;
874
+ return `# Generate Page: ${pageName}\n\n## Description\n${args.description}\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${MANDATORY_REQUIREMENTS}\n- Include responsive layout adjustments\n\n## Context — Rules, Structure, Patterns & Snippets\n\n${context}`;
458
875
  }
459
876
 
460
877
  if (name === 'convert_react') {
461
- const source = args.source_code || '';
462
878
  const context = readSkills(
463
879
  'RULES.md',
464
880
  'MIGRATION.md',
@@ -466,11 +882,10 @@ async function callTool(name, args = {}) {
466
882
  'COMPONENTS.md',
467
883
  'LEARNINGS.md',
468
884
  );
469
- 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}`;
885
+ return `# Convert React → Symbols DOMQL v3\n\n## Source Code to Convert\n\`\`\`jsx\n${args.source_code}\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${MANDATORY_REQUIREMENTS}\n\n## Context — Migration Guide, Syntax & Rules\n\n${context}`;
470
886
  }
471
887
 
472
888
  if (name === 'convert_html') {
473
- const source = args.source_code || '';
474
889
  const context = readSkills(
475
890
  'RULES.md',
476
891
  'SYNTAX.md',
@@ -479,43 +894,97 @@ async function callTool(name, args = {}) {
479
894
  'SNIPPETS.md',
480
895
  'LEARNINGS.md',
481
896
  );
482
- 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}`;
897
+ return `# Convert HTML → Symbols DOMQL v3\n\n## Source Code to Convert\n\`\`\`html\n${args.source_code}\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${MANDATORY_REQUIREMENTS}\n\n## Context — Syntax, Components & Design System\n\n${context}`;
898
+ }
899
+
900
+ if (name === 'convert_to_json') {
901
+ const section = args.section || 'components';
902
+ const parsed = parseJsToJson(args.source_code);
903
+ if (!Object.keys(parsed).length) return 'Could not parse any exports from the source code.';
904
+ const result = {};
905
+ for (const [exportName, rawValue] of Object.entries(parsed)) {
906
+ const converted = typeof rawValue === 'string' ? jsObjToJson(rawValue) : rawValue;
907
+ if (exportName === '__default__') {
908
+ if (['designSystem', 'state', 'dependencies', 'config'].includes(section))
909
+ result[section] = converted;
910
+ else {
911
+ if (!result[section]) result[section] = {};
912
+ result[section]['default'] = converted;
913
+ }
914
+ } else {
915
+ if (!result[section]) result[section] = {};
916
+ result[section][exportName] = converted;
917
+ }
918
+ }
919
+ const output = JSON.stringify(result, null, 2);
920
+ const sections = Object.keys(result);
921
+ const items = [];
922
+ for (const sec of sections)
923
+ if (typeof result[sec] === 'object' && result[sec]) items.push(...Object.keys(result[sec]));
924
+ return `# Converted to Platform JSON\n\n**Section:** ${sections.join(', ')}\n**Items:** ${items.join(', ') || 'default export'}\n\n\`\`\`json\n${output}\n\`\`\`\n\nThis JSON is ready to use with \`save_to_project\`.`;
483
925
  }
484
926
 
485
927
  if (name === 'audit_component') {
486
- const code = args.component_code || '';
487
- const result = auditCode(code);
928
+ const result = auditCode(args.component_code || '');
488
929
  const rulesContext = readSkill('AUDIT.md');
489
-
490
930
  let output = `# Audit Report\n\n## Summary\n${result.summary}\nPassed: ${result.passed ? 'Yes' : 'No'}\n\n## Violations (Errors)\n`;
491
931
  if (result.violations.length) {
492
- for (const v of result.violations) {
493
- output += `- **Line ${v.line}**: ${v.message}\n`;
494
- }
495
- } else {
496
- output += 'No violations found.\n';
497
- }
498
-
932
+ for (const v of result.violations) output += `- **Line ${v.line}**: ${v.message}\n`;
933
+ } else output += 'No violations found.\n';
499
934
  output += '\n## Warnings\n';
500
935
  if (result.warnings.length) {
501
- for (const w of result.warnings) {
502
- output += `- **Line ${w.line}**: ${w.message}\n`;
503
- }
504
- } else {
505
- output += 'No warnings.\n';
936
+ for (const w of result.warnings) output += `- **Line ${w.line}**: ${w.message}\n`;
937
+ } else output += 'No warnings.\n';
938
+ if (result.violations.length) {
939
+ output +=
940
+ '\n## MANDATORY ACTION\n\n**Every violation above MUST be fixed. There is NO concept of "known debt" or "accepted violations" in Symbols. Do NOT label any violation as "known debt" or defer it. Rewrite the code using proper DOMQL syntax.**\n\n';
506
941
  }
507
-
508
942
  output += `\n## Detailed Rules Reference\n\n${rulesContext}`;
509
943
  return output;
510
944
  }
511
945
 
946
+ if (name === 'detect_environment') {
947
+ let envType = 'unknown',
948
+ confidence = 'low';
949
+ if (args.has_mermaid_config) {
950
+ envType = 'remote_server';
951
+ confidence = 'high';
952
+ } else if (args.has_json_data) {
953
+ envType = 'json_runtime';
954
+ confidence = 'high';
955
+ } else if (args.has_symbols_json && args.has_symbols_dir) {
956
+ envType = 'local_project';
957
+ confidence = 'high';
958
+ } else if (args.has_symbols_dir || (args.has_package_json && args.has_symbols_json)) {
959
+ envType = 'local_project';
960
+ confidence = 'medium';
961
+ } else if (args.has_cdn_import || args.has_iife_script) {
962
+ envType = 'cdn';
963
+ confidence = 'high';
964
+ } else if (args.has_package_json) {
965
+ envType = 'local_project';
966
+ confidence = 'low';
967
+ } else if (args.file_list) {
968
+ const files = args.file_list.toLowerCase();
969
+ if (
970
+ files.includes('index.html') &&
971
+ !files.includes('package.json') &&
972
+ !files.includes('symbols.json')
973
+ ) {
974
+ envType = 'cdn';
975
+ confidence = 'medium';
976
+ }
977
+ }
978
+ const guide = readSkill('RUNNING_APPS.md');
979
+ return `# Environment Detection\n\n**Detected: ${envType}** (confidence: ${confidence})\n\n${guide}`;
980
+ }
981
+
512
982
  if (name === 'login') {
513
- const { email, password } = args;
514
- if (!email || !password) return 'Error: email and password are required.';
983
+ if (!args.email || !args.password) return 'Error: email and password are required.';
515
984
  const resp = await fetch(`${API_BASE}/core/auth/login`, {
516
985
  method: 'POST',
517
986
  headers: { 'Content-Type': 'application/json' },
518
- body: JSON.stringify({ email, password }),
987
+ body: JSON.stringify({ email: args.email, password: args.password }),
519
988
  });
520
989
  let result;
521
990
  try {
@@ -524,169 +993,161 @@ async function callTool(name, args = {}) {
524
993
  return `Login failed: HTTP ${resp.status}`;
525
994
  }
526
995
  if (result.success) {
527
- const data = result.data || {};
528
- const tokens = data.tokens || {};
529
- const user = data.user || {};
530
- return `Logged in as ${user.name || user.email || 'unknown'}.\nToken: ${tokens.accessToken || ''}\nExpires: ${(tokens.accessTokenExp || {}).expiresAt || 'unknown'}\n\nUse this token with the \`publish\` and \`push\` tools.`;
996
+ const { tokens = {}, user = {} } = result.data || {};
997
+ return `Logged in as ${user.name || user.email || 'unknown'}.\nToken: ${tokens.accessToken || ''}\nExpires: ${tokens.accessTokenExp?.expiresAt || 'unknown'}\n\nUse this token with project, save, publish and push tools.`;
531
998
  }
532
999
  return `Login failed: ${result.error || result.message || 'Unknown error'}`;
533
1000
  }
534
1001
 
535
- if (name === 'publish') {
536
- const { project, token, api_key, version, branch = 'main' } = args;
537
- if (!project) return 'Error: project (ID or key) is required.';
538
- if (!token && !api_key) return `Authentication required.\n\n${AUTH_HELP}`;
539
- const body = { branch };
540
- if (version) body.version = version;
541
- const result = await apiRequest('POST', `/${project}/publish`, { token, api_key, body });
1002
+ if (name === 'list_projects') {
1003
+ if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
1004
+ const result = await apiRequest('GET', '/core/projects', {
1005
+ token: args.token,
1006
+ api_key: args.api_key,
1007
+ });
542
1008
  if (result.success) {
543
- const data = result.data || {};
544
- return `Published successfully.\nVersion: ${data.value || data.id || 'unknown'}`;
1009
+ const projects = result.data || [];
1010
+ if (!projects.length) return 'No projects found. Use `create_project` to create one.';
1011
+ return (
1012
+ '# Your Projects\n\n' +
1013
+ projects
1014
+ .map(
1015
+ (p) =>
1016
+ `- **${p.name || 'Untitled'}** — key: \`${p.key || '—'}\`, id: \`${p._id || ''}\``,
1017
+ )
1018
+ .join('\n')
1019
+ );
545
1020
  }
546
- return `Publish failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
1021
+ return `Failed to list projects: ${result.error || 'Unknown error'}`;
547
1022
  }
548
1023
 
549
- if (name === 'push') {
550
- const {
551
- project,
552
- token,
553
- api_key,
554
- environment = 'production',
555
- mode = 'published',
556
- version,
557
- branch = 'main',
558
- } = args;
559
- if (!project) return 'Error: project (ID or key) is required.';
560
- if (!token && !api_key) return `Authentication required.\n\n${AUTH_HELP}`;
561
- const body = { mode, branch };
562
- if (version) body.version = version;
563
- const result = await apiRequest('POST', `/${project}/environments/${environment}/publish`, {
564
- token,
565
- api_key,
1024
+ if (name === 'create_project') {
1025
+ if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
1026
+ const body = {
1027
+ name: args.name,
1028
+ visibility: args.visibility || 'private',
1029
+ language: 'javascript',
1030
+ };
1031
+ if (args.key) body.key = args.key;
1032
+ const result = await apiRequest('POST', '/core/projects', {
1033
+ token: args.token,
1034
+ api_key: args.api_key,
566
1035
  body,
567
1036
  });
568
1037
  if (result.success) {
569
- const data = result.data || {};
570
- const config = data.config || {};
571
- return `Pushed to ${data.key || environment} successfully.\nMode: ${config.mode || mode}\nVersion: ${config.version || 'latest'}\nBranch: ${config.branch || branch}`;
1038
+ const d = result.data || {};
1039
+ return `Project created.\nName: ${d.name}\nKey: \`${d.key}\`\nID: \`${d._id}\`\n\nUse with \`save_to_project\` to push components.`;
572
1040
  }
573
- return `Push failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
1041
+ return `Create failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
574
1042
  }
575
1043
 
576
- if (name === 'detect_environment') {
577
- const {
578
- has_symbols_json,
579
- has_symbols_dir,
580
- has_package_json,
581
- has_cdn_import,
582
- has_iife_script,
583
- has_json_data,
584
- has_mermaid_config,
585
- file_list,
586
- } = args;
587
-
588
- // Detect environment type
589
- let envType = 'unknown';
590
- let confidence = 'low';
591
-
592
- if (has_mermaid_config) {
593
- envType = 'remote_server';
594
- confidence = 'high';
595
- } else if (has_json_data) {
596
- envType = 'json_runtime';
597
- confidence = 'high';
598
- } else if (has_symbols_json && has_symbols_dir) {
599
- envType = 'local_project';
600
- confidence = 'high';
601
- } else if (has_symbols_dir || (has_package_json && has_symbols_json)) {
602
- envType = 'local_project';
603
- confidence = 'medium';
604
- } else if (has_cdn_import || has_iife_script) {
605
- envType = 'cdn';
606
- confidence = 'high';
607
- } else if (has_package_json) {
608
- envType = 'local_project';
609
- confidence = 'low';
610
- } else if (file_list) {
611
- const files = file_list.toLowerCase();
612
- if (
613
- files.includes('index.html') &&
614
- !files.includes('package.json') &&
615
- !files.includes('symbols.json')
616
- ) {
617
- envType = 'cdn';
618
- confidence = 'medium';
619
- }
1044
+ if (name === 'get_project') {
1045
+ if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
1046
+ const branch = args.branch || 'main';
1047
+ const isKey = args.project.startsWith('pr_') || !/^[0-9a-f]+$/.test(args.project);
1048
+ const path = isKey
1049
+ ? `/core/projects/key/${args.project}/data?branch=${branch}&version=latest`
1050
+ : `/core/projects/${args.project}/data?branch=${branch}&version=latest`;
1051
+ const result = await apiRequest('GET', path, { token: args.token, api_key: args.api_key });
1052
+ if (result.success) {
1053
+ const data = result.data || {};
1054
+ return `# Project Data (branch: ${branch})\n\n**Components (${Object.keys(data.components || {}).length}):** ${
1055
+ Object.keys(data.components || {})
1056
+ .slice(0, 20)
1057
+ .join(', ') || 'none'
1058
+ }\n**Pages (${Object.keys(data.pages || {}).length}):** ${
1059
+ Object.keys(data.pages || {})
1060
+ .slice(0, 20)
1061
+ .join(', ') || 'none'
1062
+ }\n\n\`\`\`json\n${JSON.stringify(data, null, 2).slice(0, 8000)}\n\`\`\``;
620
1063
  }
1064
+ return `Failed to get project: ${result.error || 'Unknown error'}`;
1065
+ }
621
1066
 
622
- // Build response with appropriate guide
623
- const fullGuide = readSkill('RUNNING_APPS.md');
624
- let output = `# Environment Detection\n\n**Detected: ${envType}** (confidence: ${confidence})\n\n`;
625
-
626
- if (envType === 'local_project') {
627
- output += `## Your Environment: Local Project\n\n`;
628
- output += `You're working in a standard Symbols project with file-based structure.\n\n`;
629
- output += `### Code Format\n`;
630
- output += `- Components: \`export const Name = { extends: 'Flex', ... }\` in \`components/\`\n`;
631
- output += `- Pages: \`export const pageName = { extends: 'Page', ... }\` in \`pages/\`\n`;
632
- output += `- State: \`export default { key: value }\` in \`state.js\`\n`;
633
- output += `- Functions: \`export const fn = function() {}\` in \`functions/\`\n`;
634
- output += `- No imports between files (except pages/index.js)\n\n`;
635
- output += `### Commands\n`;
636
- output += `\`\`\`bash\nnpm start # dev server\nsmbls build # production build\nsmbls push # deploy to platform\nsmbls deploy # deploy to provider\n\`\`\`\n\n`;
637
- const structureGuide = readSkill('PROJECT_STRUCTURE.md');
638
- output += `### Full Project Structure Reference\n\n${structureGuide}`;
639
- } else if (envType === 'cdn') {
640
- output += `## Your Environment: CDN (Browser-Only)\n\n`;
641
- output += `You're running Symbols directly in the browser via CDN import.\n\n`;
642
- output += `### Code Format\n`;
643
- output += `- Single HTML file with \`<script type="module">\`\n`;
644
- output += `- Import: \`import { create } from 'https://esm.sh/smbls'\`\n`;
645
- output += `- Define app as inline object tree\n`;
646
- output += `- Mount: \`create(App, { designSystem, components, functions, state })\`\n`;
647
- output += `- Components defined as JS variables (no file-based registry)\n\n`;
648
- output += `### Limitations\n`;
649
- output += `- No file-based routing (use tab/view switching)\n`;
650
- output += `- No SSR\n`;
651
- output += `- \`childExtends: 'Name'\` needs components passed to \`create()\`\n\n`;
652
- const cdnGuide = readSkill('RUNNING_APPS.md');
653
- output += `### Full CDN Reference\n\n${cdnGuide}`;
654
- } else if (envType === 'json_runtime') {
655
- output += `## Your Environment: JSON Runtime (Frank)\n\n`;
656
- output += `You're running Symbols from serialized JSON project data.\n\n`;
657
- output += `### Code Format\n`;
658
- output += `- Project data is a JSON object with components, pages, designSystem, state, functions\n`;
659
- output += `- Functions are serialized as strings\n`;
660
- output += `- Convert with: \`smbls frank to-json ./symbols\` or \`toJSON({ entry: './symbols' })\`\n`;
661
- output += `- Reverse with: \`smbls frank to-fs data.json -o ./output\` or \`toFS(data, './output')\`\n`;
662
- output += `- Can be loaded by Mermaid server via JSON_PATH env var\n\n`;
663
- output += `### Full Reference\n\n${fullGuide}`;
664
- } else if (envType === 'remote_server') {
665
- output += `## Your Environment: Remote Symbols Server (Mermaid)\n\n`;
666
- output += `You're working with the Mermaid rendering server for hosted Symbols apps.\n\n`;
667
- output += `### URL Patterns\n`;
668
- output += `- Production: \`https://app.user.preview.symbols.app/\`\n`;
669
- output += `- Development: \`https://app.user.preview.dev.symbols.app/\`\n`;
670
- output += `- Staging: \`https://app.user.preview.staging.symbols.app/\`\n`;
671
- output += `- Legacy: \`https://app.symbo.ls/\`\n`;
672
- output += `- Custom domains supported\n\n`;
673
- output += `### Deployment\n`;
674
- output += `\`\`\`bash\nsmbls push # deploy to Symbols platform\n\`\`\`\n\n`;
675
- output += `### Full Reference\n\n${fullGuide}`;
676
- } else {
677
- output += `## Could Not Determine Environment\n\n`;
678
- output += `Provide more details about your project files to get specific guidance.\n\n`;
679
- output += `### All 4 Ways to Run Symbols Apps\n\n${fullGuide}`;
1067
+ if (name === 'save_to_project') {
1068
+ if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
1069
+ let changesData;
1070
+ try {
1071
+ changesData = JSON.parse(args.changes);
1072
+ } catch (e) {
1073
+ return `Invalid JSON: ${e.message}`;
680
1074
  }
1075
+ const { id: projectId, error: resolveErr } = await resolveProjectId(
1076
+ args.project,
1077
+ args.token,
1078
+ args.api_key,
1079
+ );
1080
+ if (resolveErr) return resolveErr;
1081
+ const { changes, granular, orders } = buildChangesAndSchema(changesData);
1082
+ if (!changes.length) return 'No valid changes found.';
1083
+ const branch = args.branch || 'main';
1084
+ const result = await apiRequest('POST', `/core/projects/${projectId}/changes`, {
1085
+ token: args.token,
1086
+ api_key: args.api_key,
1087
+ body: {
1088
+ changes,
1089
+ granularChanges: granular,
1090
+ orders,
1091
+ message: args.message || 'Updated via Symbols MCP',
1092
+ branch,
1093
+ type: 'patch',
1094
+ },
1095
+ });
1096
+ if (result.success) {
1097
+ const d = result.data || {};
1098
+ return `Saved to \`${args.project}\`.\nVersion: ${d.value || d.version || d.id || 'unknown'}\nBranch: ${branch}\nSections: ${Object.keys(changesData).join(', ')}\n\nUse \`publish\` then \`push\` to deploy.`;
1099
+ }
1100
+ return `Save failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
1101
+ }
681
1102
 
682
- return output;
1103
+ if (name === 'publish') {
1104
+ if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
1105
+ const { id: projectId, error: resolveErr } = await resolveProjectId(
1106
+ args.project,
1107
+ args.token,
1108
+ args.api_key,
1109
+ );
1110
+ if (resolveErr) return resolveErr;
1111
+ const body = { branch: args.branch || 'main' };
1112
+ if (args.version) body.version = args.version;
1113
+ const result = await apiRequest('POST', `/core/projects/${projectId}/publish`, {
1114
+ token: args.token,
1115
+ api_key: args.api_key,
1116
+ body,
1117
+ });
1118
+ if (result.success)
1119
+ return `Published.\nVersion: ${result.data?.value || result.data?.id || 'unknown'}`;
1120
+ return `Publish failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
1121
+ }
1122
+
1123
+ if (name === 'push') {
1124
+ if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
1125
+ const { id: projectId, error: resolveErr } = await resolveProjectId(
1126
+ args.project,
1127
+ args.token,
1128
+ args.api_key,
1129
+ );
1130
+ if (resolveErr) return resolveErr;
1131
+ const env = args.environment || 'production';
1132
+ const body = { mode: args.mode || 'published', branch: args.branch || 'main' };
1133
+ if (args.version) body.version = args.version;
1134
+ const result = await apiRequest(
1135
+ 'POST',
1136
+ `/core/projects/${projectId}/environments/${env}/publish`,
1137
+ { token: args.token, api_key: args.api_key, body },
1138
+ );
1139
+ if (result.success) {
1140
+ const config = result.data?.config || {};
1141
+ return `Pushed to ${result.data?.key || env}.\nMode: ${config.mode || body.mode}\nVersion: ${config.version || 'latest'}`;
1142
+ }
1143
+ return `Push failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
683
1144
  }
684
1145
 
685
1146
  throw new Error(`Unknown tool: ${name}`);
686
1147
  }
687
1148
 
688
1149
  // ---------------------------------------------------------------------------
689
- // Resources — skill files exposed as MCP resources
1150
+ // Resources
690
1151
  // ---------------------------------------------------------------------------
691
1152
 
692
1153
  const SKILL_RESOURCES = {
@@ -708,6 +1169,9 @@ const SKILL_RESOURCES = {
708
1169
  'symbols://skills/default-components': 'DEFAULT_COMPONENTS.md',
709
1170
  'symbols://skills/learnings': 'LEARNINGS.md',
710
1171
  'symbols://skills/running-apps': 'RUNNING_APPS.md',
1172
+ 'symbols://skills/cli': 'CLI.md',
1173
+ 'symbols://skills/sdk': 'SDK.md',
1174
+ 'symbols://skills/common-mistakes': 'COMMON_MISTAKES.md',
711
1175
  'symbols://skills/brand-identity': 'BRAND_IDENTITY.md',
712
1176
  'symbols://skills/design-critique': 'DESIGN_CRITIQUE.md',
713
1177
  'symbols://skills/design-system-architect': 'DESIGN_SYSTEM_ARCHITECT.md',
@@ -718,21 +1182,14 @@ const SKILL_RESOURCES = {
718
1182
  };
719
1183
 
720
1184
  function listResources() {
721
- const resources = [];
722
- for (const [uri, filename] of Object.entries(SKILL_RESOURCES)) {
723
- if (skills[filename]) {
724
- resources.push({ uri, name: filename.replace('.md', ''), mimeType: 'text/markdown' });
725
- }
726
- }
727
- return resources;
1185
+ return Object.entries(SKILL_RESOURCES)
1186
+ .filter(([, f]) => skills[f])
1187
+ .map(([uri, f]) => ({ uri, name: f.replace('.md', ''), mimeType: 'text/markdown' }));
728
1188
  }
729
1189
 
730
1190
  function readResource(uri) {
731
- const filename = SKILL_RESOURCES[uri];
732
- if (filename && skills[filename]) {
733
- return readSkill(filename);
734
- }
735
- return null;
1191
+ const f = SKILL_RESOURCES[uri];
1192
+ return f && skills[f] ? readSkill(f) : null;
736
1193
  }
737
1194
 
738
1195
  // ---------------------------------------------------------------------------
@@ -742,41 +1199,35 @@ function readResource(uri) {
742
1199
  const PROMPTS = [
743
1200
  {
744
1201
  name: 'symbols_component_prompt',
745
- description: 'Prompt template for generating a Symbols.app component.',
1202
+ description: 'Generate a Symbols.app component.',
746
1203
  arguments: [
747
- { name: 'description', description: 'Component description', required: true },
748
- { name: 'component_name', description: 'Component name', required: false },
1204
+ { name: 'description', required: true },
1205
+ { name: 'component_name', required: false },
749
1206
  ],
750
1207
  },
751
1208
  {
752
1209
  name: 'symbols_migration_prompt',
753
- description: 'Prompt template for migrating code to Symbols.app.',
754
- arguments: [
755
- {
756
- name: 'source_framework',
757
- description: 'Source framework (React, Vue, etc.)',
758
- required: false,
759
- },
760
- ],
1210
+ description: 'Migrate code to Symbols.app.',
1211
+ arguments: [{ name: 'source_framework', required: false }],
761
1212
  },
762
1213
  {
763
1214
  name: 'symbols_project_prompt',
764
- description: 'Prompt template for scaffolding a complete Symbols project.',
765
- arguments: [{ name: 'description', description: 'Project description', required: true }],
1215
+ description: 'Scaffold a complete Symbols project.',
1216
+ arguments: [{ name: 'description', required: true }],
766
1217
  },
767
1218
  {
768
1219
  name: 'symbols_review_prompt',
769
- description: 'Prompt template for reviewing Symbols/DOMQL code.',
1220
+ description: 'Review Symbols/DOMQL code for v3 compliance.',
770
1221
  arguments: [],
771
1222
  },
772
1223
  {
773
1224
  name: 'symbols_convert_html_prompt',
774
- description: 'Prompt template for converting HTML to Symbols.app components.',
1225
+ description: 'Convert HTML to Symbols.app components.',
775
1226
  arguments: [],
776
1227
  },
777
1228
  {
778
1229
  name: 'symbols_design_review_prompt',
779
- description: 'Prompt template for visual/design audit against the design system.',
1230
+ description: 'Visual/design audit against the design system.',
780
1231
  arguments: [],
781
1232
  },
782
1233
  ];
@@ -785,12 +1236,11 @@ const PROMPTS = [
785
1236
  // MCP JSON-RPC handler
786
1237
  // ---------------------------------------------------------------------------
787
1238
 
788
- const SERVER_INFO = { name: 'Symbols MCP', version: '1.1.1' };
1239
+ const SERVER_INFO = { name: 'Symbols MCP', version: '1.0.15' };
789
1240
 
790
1241
  export async function handleJsonRpc(req) {
791
1242
  const { method, params, id } = req;
792
-
793
- if (method === 'initialize') {
1243
+ if (method === 'initialize')
794
1244
  return {
795
1245
  jsonrpc: '2.0',
796
1246
  id,
@@ -800,15 +1250,9 @@ export async function handleJsonRpc(req) {
800
1250
  serverInfo: SERVER_INFO,
801
1251
  },
802
1252
  };
803
- }
804
-
805
1253
  if (method === 'notifications/initialized') return null;
806
1254
  if (method === 'ping') return { jsonrpc: '2.0', id, result: {} };
807
-
808
- if (method === 'tools/list') {
809
- return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
810
- }
811
-
1255
+ if (method === 'tools/list') return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
812
1256
  if (method === 'tools/call') {
813
1257
  const { name, arguments: args = {} } = params || {};
814
1258
  try {
@@ -822,32 +1266,25 @@ export async function handleJsonRpc(req) {
822
1266
  };
823
1267
  }
824
1268
  }
825
-
826
- if (method === 'resources/list') {
1269
+ if (method === 'resources/list')
827
1270
  return { jsonrpc: '2.0', id, result: { resources: listResources() } };
828
- }
829
-
830
1271
  if (method === 'resources/read') {
831
- const uri = params?.uri;
832
- const content = readResource(uri);
833
- if (content === null) {
834
- return { jsonrpc: '2.0', id, error: { code: -32602, message: `Resource not found: ${uri}` } };
835
- }
1272
+ const content = readResource(params?.uri);
1273
+ if (content === null)
1274
+ return {
1275
+ jsonrpc: '2.0',
1276
+ id,
1277
+ error: { code: -32602, message: `Resource not found: ${params?.uri}` },
1278
+ };
836
1279
  return {
837
1280
  jsonrpc: '2.0',
838
1281
  id,
839
- result: { contents: [{ uri, mimeType: 'text/markdown', text: content }] },
1282
+ result: { contents: [{ uri: params.uri, mimeType: 'text/markdown', text: content }] },
840
1283
  };
841
1284
  }
842
-
843
- if (method === 'prompts/list') {
844
- return { jsonrpc: '2.0', id, result: { prompts: PROMPTS } };
845
- }
846
-
847
- if (id !== undefined) {
1285
+ if (method === 'prompts/list') return { jsonrpc: '2.0', id, result: { prompts: PROMPTS } };
1286
+ if (id !== undefined)
848
1287
  return { jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } };
849
- }
850
-
851
1288
  return null;
852
1289
  }
853
1290