@santi020k/lumen-mcp 1.0.0 → 1.2.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/dist/tools.js CHANGED
@@ -53,6 +53,16 @@ const componentSummary = (component) => ({
53
53
  name: component.name,
54
54
  recipes: component.recipes.map(recipe => recipe.name)
55
55
  });
56
+ const availableNativePlatforms = (component) => Object.keys(component.implementations);
57
+ const nativeComponentSummary = (component) => ({
58
+ accessibility: component.accessibility,
59
+ category: component.category,
60
+ id: component.id,
61
+ name: component.name,
62
+ platforms: availableNativePlatforms(component),
63
+ summary: component.summary,
64
+ tier: component.tier
65
+ });
56
66
  export const resolveComponent = (name, data = loadLumenData()) => {
57
67
  const target = normalize(name);
58
68
  return (data.components.find(component => component.name === name) ??
@@ -63,6 +73,14 @@ export const resolveRecipe = (name, data = loadLumenData()) => {
63
73
  const target = normalize(name);
64
74
  return data.recipes.find(recipe => normalize(recipe.name) === target);
65
75
  };
76
+ export const resolveNativeComponent = (name, data = loadLumenData()) => {
77
+ const target = normalize(name);
78
+ return data.nativeComponents.find(component => (component.name === name ||
79
+ normalize(component.name) === target ||
80
+ normalize(component.id) === target ||
81
+ Object.values(component.implementations).some(implementation => (normalize(implementation.exportName) === target ||
82
+ normalize(implementation.symbol) === target))));
83
+ };
66
84
  const frameworkBehaviorSearchText = (behavior) => [
67
85
  behavior?.description ?? '',
68
86
  behavior?.hook ?? '',
@@ -99,6 +117,28 @@ const componentSearchText = (component, framework) => [
99
117
  .join(' '),
100
118
  frameworkSearchText(component, framework)
101
119
  ].join(' ');
120
+ const nativeImplementationSearchText = (implementation) => [
121
+ implementation.exportName,
122
+ implementation.symbol,
123
+ implementation.packageName,
124
+ implementation.importStatement,
125
+ implementation.api.map(row => (`${row.name} ${row.values} ${row.description}`)).join(' '),
126
+ implementation.example
127
+ ].join(' ');
128
+ const nativeComponentSearchText = (component, platform) => [
129
+ component.name,
130
+ component.id,
131
+ component.summary,
132
+ component.category,
133
+ component.contract,
134
+ component.guidance,
135
+ component.accessibility,
136
+ component.tier,
137
+ platform ?? '',
138
+ ...(platform ?
139
+ [component.implementations[platform]].filter((implementation) => Boolean(implementation)) :
140
+ Object.values(component.implementations)).map(nativeImplementationSearchText)
141
+ ].join(' ');
102
142
  const scoreCandidate = (query, terms, searchableText, primaryName = '') => {
103
143
  const normalizedText = normalize(searchableText);
104
144
  const matchedTerms = terms.filter(term => (Reflect.get(searchTermAliases, term) ?? [term]).some(candidate => normalizedText.includes(candidate)));
@@ -121,14 +161,19 @@ const scoreCandidate = (query, terms, searchableText, primaryName = '') => {
121
161
  score += 500;
122
162
  return { matchedTerms, score };
123
163
  };
124
- const appendSearchResult = (results, candidate, query, terms, searchableText) => {
164
+ const appendSearchResult = (results, candidate, query, terms, searchableText, scoreAdjustment = 0) => {
125
165
  const match = scoreCandidate(query, terms, searchableText, candidate.kind === 'rule' ? '' : candidate.name);
126
- if (match.score > 0)
127
- results.push({ ...candidate, ...match });
166
+ if (match.score > 0) {
167
+ results.push({
168
+ ...candidate,
169
+ ...match,
170
+ score: Math.max(1, match.score + scoreAdjustment)
171
+ });
172
+ }
128
173
  };
129
- const collectSearchResults = (query, terms, data, framework) => {
174
+ const collectComponentSearchResults = (query, terms, data, framework, platform) => {
130
175
  const results = [];
131
- for (const component of data.components) {
176
+ for (const component of platform ? [] : data.components) {
132
177
  if (framework && !Reflect.get(component.frameworks, framework))
133
178
  continue;
134
179
  appendSearchResult(results, {
@@ -139,6 +184,23 @@ const collectSearchResults = (query, terms, data, framework) => {
139
184
  name: component.name
140
185
  }, query, terms, componentSearchText(component, framework));
141
186
  }
187
+ if (!platform || framework)
188
+ return results;
189
+ for (const component of data.nativeComponents) {
190
+ if (!component.implementations[platform])
191
+ continue;
192
+ appendSearchResult(results, {
193
+ category: component.category,
194
+ description: component.summary,
195
+ kind: 'native-component',
196
+ name: component.name,
197
+ platforms: availableNativePlatforms(component)
198
+ }, query, terms, nativeComponentSearchText(component, platform));
199
+ }
200
+ return results;
201
+ };
202
+ const collectReferenceSearchResults = (query, terms, data) => {
203
+ const results = [];
142
204
  for (const recipe of data.recipes) {
143
205
  appendSearchResult(results, {
144
206
  category: recipe.categories.join(', '),
@@ -163,6 +225,10 @@ const collectSearchResults = (query, terms, data, framework) => {
163
225
  }
164
226
  return results;
165
227
  };
228
+ const collectSearchResults = (query, terms, data, framework, platform) => [
229
+ ...collectComponentSearchResults(query, terms, data, framework, platform),
230
+ ...collectReferenceSearchResults(query, terms, data)
231
+ ];
166
232
  export const listComponents = (args = {}, data = loadLumenData()) => {
167
233
  const query = args.query?.trim();
168
234
  const queryTerms = query ? meaningfulSearchTerms(query) : [];
@@ -348,6 +414,101 @@ export const getComponent = (args, data = loadLumenData()) => {
348
414
  text: formatComponentUsage(component, framework, detail)
349
415
  };
350
416
  };
417
+ export const listNativeComponents = (args = {}, data = loadLumenData()) => {
418
+ const query = args.query?.trim();
419
+ const terms = query ? meaningfulSearchTerms(query) : [];
420
+ const components = data.nativeComponents.filter(component => ((!args.platform || Boolean(component.implementations[args.platform])) &&
421
+ (!query || scoreCandidate(query, terms, nativeComponentSearchText(component, args.platform), component.name).score > 0))).map(nativeComponentSummary);
422
+ return {
423
+ data: { components, count: components.length },
424
+ text: components.length === 0 ?
425
+ 'No native components matched the given filters.' :
426
+ `${components.length} native component(s):\n\n${components.map(component => (`${component.name} (${component.id}) — ${component.summary} — ${component.platforms.join(', ')}`)).join('\n')}`
427
+ };
428
+ };
429
+ const nativeComponentDetail = (component, platform, detail, data) => {
430
+ const implementation = component.implementations[platform];
431
+ if (!implementation)
432
+ return undefined;
433
+ const common = {
434
+ ...nativeComponentSummary(component),
435
+ contract: component.contract,
436
+ guidance: component.guidance,
437
+ platform
438
+ };
439
+ if (detail === 'summary') {
440
+ return {
441
+ ...common,
442
+ implementation: {
443
+ exportName: implementation.exportName,
444
+ packageName: implementation.packageName,
445
+ symbol: implementation.symbol
446
+ }
447
+ };
448
+ }
449
+ const usage = { ...common, implementation };
450
+ return detail === 'source' ?
451
+ {
452
+ ...usage,
453
+ source: data.nativeSources[platform][implementation.sourceFile]
454
+ } :
455
+ usage;
456
+ };
457
+ const formatNativeComponent = (component, platform, detail, data) => {
458
+ const implementation = component.implementations[platform];
459
+ if (!implementation)
460
+ return '';
461
+ const sections = [
462
+ `# ${component.name}`,
463
+ component.summary,
464
+ `Category: ${component.category}`,
465
+ `Tier: ${component.tier}`,
466
+ `Platform: ${platform}`,
467
+ `Available platforms: ${availableNativePlatforms(component).join(', ')}`,
468
+ `Package: ${implementation.packageName}`,
469
+ `Install: ${implementation.install}`,
470
+ `Import: ${implementation.importStatement}`,
471
+ `Setup: ${implementation.setup}`,
472
+ '',
473
+ '## Accessibility',
474
+ component.accessibility,
475
+ '',
476
+ '## Contract',
477
+ component.contract,
478
+ '',
479
+ '## Guidance',
480
+ component.guidance
481
+ ];
482
+ if (detail === 'summary')
483
+ return sections.join('\n');
484
+ if (implementation.api.length > 0) {
485
+ sections.push('', '## API', ...implementation.api.map(row => (`- \`${row.name}\` ${row.values} (default: ${row.defaultValue}) — ${row.description}`)));
486
+ }
487
+ sections.push('', '## Example', `\`\`\`${implementation.language}`, implementation.example, '```');
488
+ if (detail === 'source') {
489
+ sections.push('', `## Reference source (${implementation.sourceFile})`, `\`\`\`${implementation.language}`, data.nativeSources[platform][implementation.sourceFile]?.trimEnd() ?? '', '```');
490
+ }
491
+ return sections.join('\n');
492
+ };
493
+ export const getNativeComponent = (args, data = loadLumenData()) => {
494
+ const component = resolveNativeComponent(args.name, data);
495
+ if (!component) {
496
+ const message = `Unknown native component "${args.name}". Use lumen_list_native_components to see available names.`;
497
+ return { data: { found: false, message }, isError: true, text: message };
498
+ }
499
+ if (!component.implementations[args.platform]) {
500
+ const message = `${component.name} is not available for ${args.platform}. Available platforms: ${availableNativePlatforms(component).join(', ')}.`;
501
+ return { data: { found: false, message }, isError: true, text: message };
502
+ }
503
+ const detail = args.detail ?? 'usage';
504
+ return {
505
+ data: {
506
+ component: nativeComponentDetail(component, args.platform, detail, data),
507
+ found: true
508
+ },
509
+ text: formatNativeComponent(component, args.platform, detail, data)
510
+ };
511
+ };
351
512
  export const getRecipe = (args, data = loadLumenData()) => {
352
513
  const recipe = resolveRecipe(args.name, data);
353
514
  if (!recipe) {
@@ -389,12 +550,13 @@ export const search = (args, data = loadLumenData()) => {
389
550
  };
390
551
  }
391
552
  const limit = Math.max(1, Math.min(args.limit ?? 20, 100));
392
- const results = collectSearchResults(query, meaningfulSearchTerms(query), data, args.framework);
553
+ const results = collectSearchResults(query, meaningfulSearchTerms(query), data, args.framework, args.platform);
393
554
  const kindPriority = {
394
555
  component: 0,
395
- recipe: 1,
396
- rule: 3,
397
- token: 2
556
+ 'native-component': 1,
557
+ recipe: 2,
558
+ rule: 4,
559
+ token: 3
398
560
  };
399
561
  results.sort((left, right) => right.score - left.score ||
400
562
  kindPriority[left.kind] - kindPriority[right.kind] ||
@@ -418,7 +580,8 @@ export const getMeta = (data = loadLumenData()) => ({
418
580
  `Server version: ${data.meta.serverVersion}`,
419
581
  `Schema version: ${data.meta.schemaVersion}`,
420
582
  `Catalog hash: ${data.meta.catalogHash}`,
421
- `Components: ${data.meta.componentCount}`,
583
+ `Web components: ${data.meta.componentCount}`,
584
+ `Native components: ${data.meta.nativeComponentCount}`,
422
585
  '',
423
586
  '## Package versions',
424
587
  ...Object.entries(data.meta.packageVersions).map(([name, version]) => `- ${name}: ${version}`)
@@ -453,6 +616,7 @@ export const getCatalogManifest = (data = loadLumenData()) => ({
453
616
  '# Lumen catalog manifest',
454
617
  `Catalog hash: ${data.meta.catalogHash}`,
455
618
  `Components: ${Object.keys(data.catalogManifest.components).length}`,
619
+ `Native components: ${Object.keys(data.catalogManifest.nativeComponents ?? {}).length}`,
456
620
  `Recipes: ${Object.keys(data.catalogManifest.recipes).length}`,
457
621
  '',
458
622
  'Keep this structured result and pass its manifest to lumen_diff_catalog after an MCP update.'
@@ -461,10 +625,14 @@ export const getCatalogManifest = (data = loadLumenData()) => ({
461
625
  export const diffCatalog = (args, data = loadLumenData()) => {
462
626
  const components = diffManifestSection(data.catalogManifest.components, args.baseline.components);
463
627
  const recipes = diffManifestSection(data.catalogManifest.recipes, args.baseline.recipes);
628
+ const nativeComponents = diffManifestSection(data.catalogManifest.nativeComponents ?? {}, args.baseline.nativeComponents ?? {});
464
629
  const changedCount = [
465
630
  ...components.added,
466
631
  ...components.changed,
467
632
  ...components.removed,
633
+ ...nativeComponents.added,
634
+ ...nativeComponents.changed,
635
+ ...nativeComponents.removed,
468
636
  ...recipes.added,
469
637
  ...recipes.changed,
470
638
  ...recipes.removed
@@ -476,6 +644,7 @@ export const diffCatalog = (args, data = loadLumenData()) => {
476
644
  data: {
477
645
  components,
478
646
  currentCatalogHash: data.meta.catalogHash,
647
+ nativeComponents,
479
648
  recipes,
480
649
  unchanged
481
650
  },
@@ -487,21 +656,16 @@ export const diffCatalog = (args, data = loadLumenData()) => {
487
656
  `Changed entries: ${changedCount}`,
488
657
  `Components — added: ${components.added.length}, changed: ${components.changed.length}, ` +
489
658
  `removed: ${components.removed.length}`,
659
+ `Native components — added: ${nativeComponents.added.length}, changed: ${nativeComponents.changed.length}, ` +
660
+ `removed: ${nativeComponents.removed.length}`,
490
661
  `Recipes — added: ${recipes.added.length}, changed: ${recipes.changed.length}, ` +
491
662
  `removed: ${recipes.removed.length}`
492
663
  ].join('\n')
493
664
  };
494
665
  };
495
- export const diagnose = (data = loadLumenData()) => {
666
+ const webDiagnosticChecks = (data) => {
496
667
  const componentNames = data.components.map(component => component.name);
497
- const frameworkCoverage = {
498
- astro: data.components.filter(component => component.frameworks.astro)
499
- .length,
500
- elements: data.components.filter(component => component.frameworks.elements).length,
501
- react: data.components.filter(component => component.frameworks.react)
502
- .length
503
- };
504
- const checks = [
668
+ return [
505
669
  {
506
670
  message: `${data.components.length} component records match metadata.`,
507
671
  name: 'component-count',
@@ -528,24 +692,76 @@ export const diagnose = (data = loadLumenData()) => {
528
692
  if (!available)
529
693
  return true;
530
694
  const details = Reflect.get(component.frameworkDetails, framework);
531
- return (Boolean(details?.example.trim()) &&
532
- Boolean(details?.importStatement.trim()));
695
+ return Boolean(details?.example.trim()) && Boolean(details?.importStatement.trim());
533
696
  })) ?
534
697
  'pass' :
535
698
  'fail'
536
699
  }
537
700
  ];
701
+ };
702
+ const nativeDiagnosticChecks = (data) => {
703
+ const nativeComponentNames = data.nativeComponents.map(component => component.name);
704
+ return [
705
+ {
706
+ message: `${data.nativeComponents.length} native component records match metadata.`,
707
+ name: 'native-component-count',
708
+ status: data.nativeComponents.length === data.meta.nativeComponentCount ?
709
+ 'pass' :
710
+ 'fail'
711
+ },
712
+ {
713
+ message: 'Native component names and ids are unique.',
714
+ name: 'unique-native-components',
715
+ status: new Set(nativeComponentNames).size === nativeComponentNames.length &&
716
+ new Set(data.nativeComponents.map(component => component.id)).size === data.nativeComponents.length ?
717
+ 'pass' :
718
+ 'fail'
719
+ },
720
+ {
721
+ message: 'Every native component has a manifest fingerprint.',
722
+ name: 'native-component-manifest',
723
+ status: nativeComponentNames.every(name => (Reflect.get(data.catalogManifest.nativeComponents ?? {}, name))) ?
724
+ 'pass' :
725
+ 'fail'
726
+ },
727
+ {
728
+ message: 'Every native implementation has usage, import, and bundled reference source.',
729
+ name: 'native-contracts',
730
+ status: data.nativeComponents.every(component => (Object.entries(component.implementations).every(([platform, implementation]) => (Boolean(implementation.example.trim()) &&
731
+ Boolean(implementation.importStatement.trim()) &&
732
+ Boolean(data.nativeSources[platform][implementation.sourceFile]?.trim()))))) ?
733
+ 'pass' :
734
+ 'fail'
735
+ }
736
+ ];
737
+ };
738
+ export const diagnose = (data = loadLumenData()) => {
739
+ const frameworkCoverage = {
740
+ astro: data.components.filter(component => component.frameworks.astro)
741
+ .length,
742
+ elements: data.components.filter(component => component.frameworks.elements).length,
743
+ react: data.components.filter(component => component.frameworks.react)
744
+ .length
745
+ };
746
+ const nativeCoverage = {
747
+ compose: data.nativeComponents.filter(component => component.implementations.compose).length,
748
+ 'react-native': data.nativeComponents.filter(component => component.implementations['react-native']).length,
749
+ swiftui: data.nativeComponents.filter(component => component.implementations.swiftui).length
750
+ };
751
+ const checks = [...webDiagnosticChecks(data), ...nativeDiagnosticChecks(data)];
538
752
  const status = checks.every(check => check.status === 'pass') ?
539
753
  'healthy' :
540
754
  'issues';
541
755
  return {
542
- data: { checks, frameworkCoverage, status },
756
+ data: { checks, frameworkCoverage, nativeCoverage, status },
543
757
  isError: status === 'issues',
544
758
  text: [
545
759
  `# Lumen MCP diagnostics: ${status}`,
546
760
  `Catalog hash: ${data.meta.catalogHash}`,
547
761
  `Framework coverage — Astro: ${frameworkCoverage.astro}, ` +
548
762
  `React: ${frameworkCoverage.react}, Elements: ${frameworkCoverage.elements}`,
763
+ `Native coverage — React Native: ${nativeCoverage['react-native']}, ` +
764
+ `SwiftUI: ${nativeCoverage.swiftui}, Compose: ${nativeCoverage.compose}`,
549
765
  '',
550
766
  ...checks.map(check => `- ${check.status.toUpperCase()} ${check.name}: ${check.message}`)
551
767
  ].join('\n')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@santi020k/lumen-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -33,7 +33,7 @@
33
33
  "lumen-mcp-http": "./bin/lumen-mcp-http.mjs"
34
34
  },
35
35
  "dependencies": {
36
- "@modelcontextprotocol/sdk": "^1.29.0",
36
+ "@modelcontextprotocol/sdk": "^1.30.0",
37
37
  "zod": "^4.4.3"
38
38
  },
39
39
  "devDependencies": {
@@ -52,7 +52,7 @@
52
52
  "url": "https://github.com/santi020k/lumen.git",
53
53
  "directory": "packages/mcp"
54
54
  },
55
- "homepage": "https://lumen.santi020k.com",
55
+ "homepage": "https://lumen.santi020k.com/docs/mcp",
56
56
  "bugs": {
57
57
  "url": "https://github.com/santi020k/lumen/issues"
58
58
  },