@octanejs/mcp-server 0.2.22 → 0.2.24

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/README.md CHANGED
@@ -163,9 +163,11 @@ one manifest suite by name (`js-framework`, `todomvc`, `weather-app`,
163
163
  `controlled-form`, `external-store-fanout`, `external-store-integrations`,
164
164
  `scheduler-responsiveness`, `suspense-recovery`, `event-delegation`,
165
165
  `application-composition`, `scaling-curves`, `dev-form-diagnostics`,
166
- `behavior-root-events`, `router-dispatch`, `floating-tree-navigation`, `manifest-cache-invalidation`, `vite-client-assets`, `activity`, `streaming-ssr`, `streaming-backpressure`,
166
+ `behavior-root-events`, `radix-collection-order`, `router-dispatch`,
167
+ `floating-tree-navigation`, `manifest-cache-invalidation`, `vite-client-assets`, `activity`,
168
+ `streaming-ssr`, `streaming-backpressure`,
167
169
  `compiler-throughput`, `tsrx-component-graph`, `codegen-size`, `hook-memo`,
168
- `template-call-memo`, `bundle-size`, `bundle-reachability`, `three-renderer`,
170
+ `transition-hooks`, `template-call-memo`, `tsrx-renderer-selection`, `bundle-size`, `bundle-reachability`, `three-renderer`,
169
171
  `three-bundle-size`, …)
170
172
  or every suite with `all`; `quick` selects the reduced-iteration smoke pass. The
171
173
  suite list mirrors the runner manifest and `node benchmarks/bench.mjs --list`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/mcp-server",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.22.2"
package/src/bridge.js CHANGED
@@ -6,6 +6,7 @@ import { join, resolve } from 'node:path';
6
6
  // expected union from the workspace manifests, so publishing a new binding
7
7
  // without registering it in either catalog fails the mcp-server tests.
8
8
  export const KNOWN_BINDINGS = {
9
+ 'react-is': '@octanejs/octane-is',
9
10
  '@gsap/react': '@octanejs/gsap',
10
11
  animejs: '@octanejs/animejs',
11
12
  'usehooks-ts': '@octanejs/usehooks-ts',
@@ -30,6 +31,7 @@ export const KNOWN_BINDINGS = {
30
31
  '@tanstack/react-store': '@octanejs/tanstack-store',
31
32
  '@xstate/react': '@octanejs/xstate',
32
33
  '@xstate/store-react': '@octanejs/xstate-store',
34
+ '@xyflow/react': '@octanejs/xyflow',
33
35
  '@tanstack/react-router-ssr-query': '@octanejs/tanstack-router-ssr-query',
34
36
  '@tanstack/react-hotkeys': '@octanejs/tanstack-hotkeys',
35
37
  '@tanstack/react-pacer': '@octanejs/tanstack-pacer',
@@ -50,6 +52,10 @@ export const KNOWN_BINDINGS = {
50
52
  '@lexical/react': '@octanejs/lexical',
51
53
  '@tiptap/react': '@octanejs/tiptap',
52
54
  'react-pdf': '@octanejs/pdf',
55
+ '@portabletext/react': '@octanejs/portabletext',
56
+ '@sanity/icons': '@octanejs/sanity-icons',
57
+ '@sanity/react-loader': '@octanejs/sanity-loader',
58
+ '@sanity/logos': '@octanejs/sanity-logos',
53
59
  '@monaco-editor/react': '@octanejs/monaco-editor',
54
60
  'lucide-react': '@octanejs/lucide',
55
61
  '@phosphor-icons/react': '@octanejs/phosphor-icons',
@@ -60,13 +66,17 @@ export const KNOWN_BINDINGS = {
60
66
  'react-stately': '@octanejs/aria',
61
67
  'radix-ui': '@octanejs/radix',
62
68
  'react-hook-form': '@octanejs/hook-form',
69
+ 'better-auth/react': '@octanejs/better-auth',
63
70
  '@base-ui-components/react': '@octanejs/base-ui',
64
71
  '@dnd-kit/react': '@octanejs/dnd-kit',
65
72
  'embla-carousel-react': '@octanejs/embla-carousel',
66
73
  'react-dropzone': '@octanejs/dropzone',
67
74
  sonner: '@octanejs/sonner',
68
75
  'react-error-boundary': '@octanejs/react-error-boundary',
76
+ 'react-email': '@octanejs/email',
77
+ '@react-email/components': '@octanejs/email',
69
78
  'react-resizable-panels': '@octanejs/resizable-panels',
79
+ 'react-select': '@octanejs/select',
70
80
  'react-transition-group': '@octanejs/transition-group',
71
81
  'react-day-picker': '@octanejs/day-picker',
72
82
  'input-otp': '@octanejs/input-otp',
@@ -81,6 +91,7 @@ export const KNOWN_BINDINGS = {
81
91
  'react-content-loader': '@octanejs/content-loader',
82
92
  'react-to-print': '@octanejs/to-print',
83
93
  'react-calendar': '@octanejs/calendar',
94
+ 'thinking-orbs': '@octanejs/thinking-orbs',
84
95
  '@formkit/auto-animate': '@octanejs/auto-animate',
85
96
  streamdown: '@octanejs/streamdown',
86
97
  'react-syntax-highlighter': '@octanejs/syntax-highlighter',
@@ -172,6 +183,7 @@ export const KNOWN_BINDINGS = {
172
183
  export const KNOWN_NATIVE_BINDINGS = new Set([
173
184
  '@octanejs/devtools',
174
185
  '@octanejs/electron',
186
+ '@octanejs/email-cli',
175
187
  '@octanejs/tauri',
176
188
  ]);
177
189
 
@@ -199,6 +211,7 @@ export const KNOWN_VANILLA_CORES = {
199
211
  'embla-carousel-react': 'embla-carousel',
200
212
  '@xstate/react': 'xstate',
201
213
  '@xstate/store-react': '@xstate/store',
214
+ '@xyflow/react': '@xyflow/system',
202
215
  'react-redux': 'redux',
203
216
  '@reduxjs/toolkit': 'redux',
204
217
  'react-i18next': 'i18next',
@@ -462,6 +475,20 @@ export async function collectSourceFiles(root, out = [], depth = 0) {
462
475
 
463
476
  export function scanSource(source) {
464
477
  const apis = new Map();
478
+ const symbolExports = new Map();
479
+ // Introspection libraries export element-kind symbols, not components.
480
+ // Require the right-hand identifier to resolve to an actual Symbol.for
481
+ // declaration; ordinary component exports/render calls remain API uses.
482
+ const symbols = new Set(
483
+ [...source.matchAll(/\b([A-Za-z_$][\w$]*)\s*=\s*Symbol\.for\(\s*['"][^'"]+['"]\s*\)/g)].map(
484
+ (match) => match[1],
485
+ ),
486
+ );
487
+ for (const match of source.matchAll(
488
+ /\bexports\.([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*;/g,
489
+ )) {
490
+ if (symbols.has(match[2])) symbolExports.set(match[1], (symbolExports.get(match[1]) ?? 0) + 1);
491
+ }
465
492
  for (const name of Object.keys(REACT_API_MAP)) {
466
493
  if (name === 'onChange') continue;
467
494
  const matches = source.match(new RegExp(`\\b${name}\\b`, 'g'));
@@ -481,13 +508,14 @@ export function scanSource(source) {
481
508
  }
482
509
  }
483
510
  const classComponent = /\bextends\s+(React\.)?(Pure)?Component\b/.test(source);
484
- return { apis, imports, classComponent };
511
+ return { apis, imports, classComponent, symbolExports };
485
512
  }
486
513
 
487
514
  export async function scanPath(root) {
488
515
  const files = await collectSourceFiles(resolve(root));
489
516
  const totals = new Map();
490
517
  const imports = new Set();
518
+ const symbolExports = new Map();
491
519
  let classComponents = false;
492
520
  for (const file of files) {
493
521
  let source;
@@ -501,14 +529,26 @@ export async function scanPath(root) {
501
529
  totals.set(name, (totals.get(name) ?? 0) + count);
502
530
  }
503
531
  for (const spec of result.imports) imports.add(spec);
532
+ for (const [name, count] of result.symbolExports) {
533
+ symbolExports.set(name, (symbolExports.get(name) ?? 0) + count);
534
+ }
504
535
  classComponents ||= result.classComponent;
505
536
  }
506
- return { filesScanned: files.length, totals, imports, classComponents };
537
+ return { filesScanned: files.length, totals, imports, classComponents, symbolExports };
507
538
  }
508
539
 
509
- function apiRows(totals) {
540
+ function apiRows(totals, symbolExports) {
510
541
  return [...totals.entries()]
511
- .map(([name, count]) => ({ name, count, ...REACT_API_MAP[name] }))
542
+ .map(([name, count]) =>
543
+ symbolExports.get(name) === count
544
+ ? {
545
+ name,
546
+ count,
547
+ status: 'rewrite',
548
+ note: 'Exported element-kind marker: map to the Octane kind; predicates for unsupported kinds remain false. This does not require rendering that component.',
549
+ }
550
+ : { name, count, ...REACT_API_MAP[name] },
551
+ )
512
552
  .sort((a, b) => b.count - a.count);
513
553
  }
514
554
 
@@ -569,7 +609,7 @@ export async function bridgeReport({ packageName, path, projectRoot }) {
569
609
  }
570
610
 
571
611
  const scan = await scanPath(scanRoot);
572
- const rows = apiRows(scan.totals);
612
+ const rows = apiRows(scan.totals, scan.symbolExports);
573
613
  report.filesScanned = scan.filesScanned;
574
614
  report.reactImports = [...scan.imports];
575
615
  report.classComponents = scan.classComponents;
@@ -592,7 +632,7 @@ export function bridgeReportFromSource(source, { packageName } = {}) {
592
632
  report.vanillaCore = detectVanillaCore(packageName, null);
593
633
  }
594
634
  const scan = scanSource(source);
595
- const rows = apiRows(scan.apis);
635
+ const rows = apiRows(scan.apis, scan.symbolExports);
596
636
  report.reactImports = [...scan.imports];
597
637
  report.classComponents = scan.classComponent;
598
638
  report.apis = rows;
@@ -41,6 +41,23 @@ describe('scanSource', () => {
41
41
  expect(scanSource('class Memoish extends PureComponent {}').classComponent).toBe(true);
42
42
  });
43
43
 
44
+ it('classifies symbol-kind exports without requiring the corresponding renderer', () => {
45
+ const report = bridgeReportFromSource(`
46
+ var REACT_PROFILER_TYPE = Symbol.for('react.profiler');
47
+ exports.Profiler = REACT_PROFILER_TYPE;
48
+ exports.isProfiler = value => value.type === REACT_PROFILER_TYPE;
49
+ `);
50
+ expect(report.apis.find((row) => row.name === 'Profiler').status).toBe('rewrite');
51
+ expect(report.verdict).toBe('bridgeable-with-rewrites');
52
+ const renderer = bridgeReportFromSource(`
53
+ import { Profiler } from 'react';
54
+ var REACT_PROFILER_TYPE = Symbol.for('react.profiler');
55
+ exports.Profiler = REACT_PROFILER_TYPE;
56
+ export const View = () => <Profiler />;
57
+ `);
58
+ expect(renderer.verdict).toBe('needs-rework');
59
+ });
60
+
44
61
  it('targets only React-style text-host onChange wiring', () => {
45
62
  const source = `
46
63
  function Demo(props) {
@@ -298,6 +315,10 @@ describe('KNOWN_BINDINGS', () => {
298
315
  expect(KNOWN_BINDINGS['react-textarea-autosize']).toBe('@octanejs/textarea-autosize');
299
316
  });
300
317
 
318
+ it('maps react-select to the exact Octane binding', () => {
319
+ expect(KNOWN_BINDINGS['react-select']).toBe('@octanejs/select');
320
+ });
321
+
301
322
  it('maps react-window to its exact official Octane binding', () => {
302
323
  expect(KNOWN_BINDINGS['react-window']).toBe('@octanejs/window');
303
324
  });
@@ -307,6 +328,11 @@ describe('KNOWN_BINDINGS', () => {
307
328
  expect(KNOWN_VANILLA_CORES['@tanstack/react-db']).toBe('@tanstack/db');
308
329
  });
309
330
 
331
+ it('maps XYFlow React to the Octane binding and framework-neutral system core', () => {
332
+ expect(KNOWN_BINDINGS['@xyflow/react']).toBe('@octanejs/xyflow');
333
+ expect(KNOWN_VANILLA_CORES['@xyflow/react']).toBe('@xyflow/system');
334
+ });
335
+
310
336
  it('maps Streamdown and every official plugin package to the consolidated binding', () => {
311
337
  const upstreamPackages = [
312
338
  'streamdown',
package/src/index.js CHANGED
@@ -55,6 +55,7 @@ export const BENCHMARK_SUITES = [
55
55
  'recursive-context',
56
56
  'spa-navigation',
57
57
  'scoped-signals',
58
+ 'scoped-signals-trace',
58
59
  'scoped-native-reads',
59
60
  'signal-favoring',
60
61
  'news',
@@ -71,9 +72,16 @@ export const BENCHMARK_SUITES = [
71
72
  'scaling-curves',
72
73
  'dev-form-diagnostics',
73
74
  'scheduler-depth',
75
+ 'hydration-range-compaction',
76
+ 'deferred-hydration-boundaries',
77
+ 'hydration-render-phase-queue',
74
78
  'behavior-root-events',
79
+ 'radix-collection-order',
75
80
  'router-dispatch',
81
+ 'visx-categorical-scale',
82
+ 'rspack-css-graph',
76
83
  'floating-tree-navigation',
84
+ 'ink-cursor-update',
77
85
  'manifest-cache-invalidation',
78
86
  'vite-client-assets',
79
87
  'store-selector-fanout',
@@ -95,7 +103,10 @@ export const BENCHMARK_SUITES = [
95
103
  'async-waterfall',
96
104
  'async-composition',
97
105
  'lynx-list',
106
+ 'lynx-handle-retirement',
98
107
  'universal-leaf-update',
108
+ 'universal-object-teardown',
109
+ 'universal-template-events',
99
110
  'universal-external-store',
100
111
  'lynx-render',
101
112
  'lynx-table',
@@ -103,13 +114,21 @@ export const BENCHMARK_SUITES = [
103
114
  'lynx-bundle-size',
104
115
  'codegen-size',
105
116
  'hook-memo',
117
+ 'transition-hooks',
106
118
  'template-call-memo',
107
119
  'compiler-throughput',
108
120
  'tsrx-component-graph',
121
+ 'tsrx-void-memo-aliases',
122
+ 'tsrx-hydrate-module-slicing',
123
+ 'tsrx-stable-hookful-propagation',
124
+ 'tsrx-renderer-validation-ranges',
125
+ 'tsrx-local-component-name-catalog',
109
126
  'tsrx-jsx-return-branches',
110
127
  'text-type-roots',
111
128
  'tsrx-nesting-diagnostics',
129
+ 'tsrx-renderer-selection',
112
130
  'tsrx-native-change-analysis',
131
+ 'tsrx-vite-preflight-parsing',
113
132
  'bundle-size',
114
133
  'bundle-reachability',
115
134
  'three-renderer',
package/src/index.test.js CHANGED
@@ -76,6 +76,10 @@ describe('@octanejs/mcp-server helpers', () => {
76
76
  .split('\n')
77
77
  .map((line) => line.trim())
78
78
  .filter((line) => line && line !== 'Available suites:');
79
+ expect(suites).toContain('tsrx-hydrate-module-slicing');
80
+ expect(suites).toContain('tsrx-stable-hookful-propagation');
81
+ expect(suites).toContain('tsrx-renderer-validation-ranges');
82
+ expect(suites).toContain('tsrx-local-component-name-catalog');
79
83
  expect(suites).toEqual(BENCHMARK_SUITES);
80
84
 
81
85
  // The public MCP schema must accept every runner suite, not just keep