@storybook/react-native 10.3.0-next.0 → 10.3.0-next.2

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.
@@ -42,6 +42,13 @@ interface WithStorybookOptions {
42
42
  * This will mock out the default storybook ui so you don't need to install all its dependencies like reanimated etc.
43
43
  */
44
44
  liteMode?: boolean;
45
+ /**
46
+ * Whether to enable MCP (Model Context Protocol) server support. Defaults to false.
47
+ * When enabled, adds an /mcp endpoint to the channel server,
48
+ * allowing AI agents (Claude Code, Cursor, etc.) to query component documentation.
49
+ * If websockets are disabled, MCP documentation tools still work but story selection is unavailable.
50
+ */
51
+ experimental_mcp?: boolean;
45
52
  }
46
53
  /**
47
54
  * Configures Metro bundler to work with Storybook in React Native.
@@ -4,6 +4,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __getProtoOf = Object.getPrototypeOf;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
7
10
  var __commonJS = (cb, mod) => function __require() {
8
11
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
12
  };
@@ -220,7 +223,6 @@ var require_generate = __commonJS({
220
223
  directory: "${specifier.directory}",
221
224
  files: "${specifier.files}",
222
225
  importPathMatcher: /${reg.source}/,
223
- ${useJs ? "" : "// @ts-ignore"}
224
226
  req: require.context(
225
227
  '${pathToStory}',
226
228
  ${r},
@@ -291,7 +293,7 @@ declare global {
291
293
  }
292
294
  `;
293
295
  const fileContent = `/* do not change this file, it is auto generated by storybook. */
294
- import { start, updateView${useJs ? "" : ", View, type Features"} } from '@storybook/react-native';
296
+ ${useJs ? "" : '/// <reference types="@storybook/react-native/metro-env" />\n'}import { start, updateView${useJs ? "" : ", View, type Features"} } from '@storybook/react-native';
295
297
 
296
298
  ${registeredAddons.join("\n")}
297
299
 
@@ -306,7 +308,6 @@ const annotations = ${annotations};
306
308
  globalThis.STORIES = normalizedStories;
307
309
  ${channelHost ? `globalThis.STORYBOOK_WEBSOCKET = { host: '${channelHost}', port: ${port ?? 7007} };` : ""}
308
310
 
309
- ${useJs ? "" : "// @ts-ignore"}
310
311
  module?.hot?.accept?.();
311
312
  ${featuresAssignment ? `
312
313
  ${featuresAssignment}
@@ -336,42 +337,11 @@ export const view${useJs ? "" : ": View"} = globalThis.view;
336
337
  }
337
338
  });
338
339
 
339
- // src/metro/withStorybook.ts
340
- var withStorybook_exports = {};
341
- __export(withStorybook_exports, {
342
- withStorybook: () => withStorybook
343
- });
344
- module.exports = __toCommonJS(withStorybook_exports);
345
- var path2 = __toESM(require("path"));
346
- var import_generate = __toESM(require_generate());
347
- var import_common3 = require("storybook/internal/common");
348
- var import_telemetry = require("storybook/internal/telemetry");
349
-
350
- // src/metro/channelServer.ts
351
- var import_ws = require("ws");
352
- var import_node_http = require("http");
353
-
354
340
  // src/metro/buildIndex.ts
355
- var import_common = require("storybook/internal/common");
356
- var import_node_fs = require("fs");
357
- var import_glob = require("glob");
358
- var import_path = __toESM(require("path"));
359
- var import_csf_tools = require("storybook/internal/csf-tools");
360
- var import_csf = require("storybook/internal/csf");
361
- var import_preview_api = require("storybook/internal/preview-api");
362
- var import_common2 = __toESM(require_common());
363
- var cwd = process.cwd();
364
- var makeTitle = (fileName, specifier, userTitle) => {
365
- const title = (0, import_preview_api.userOrAutoTitleFromSpecifier)(fileName, specifier, userTitle);
366
- if (title) {
367
- return title.replace("./", "");
368
- } else if (userTitle) {
369
- return userTitle.replace("./", "");
370
- } else {
371
- console.error("Could not generate title!!");
372
- process.exit(1);
373
- }
374
- };
341
+ var buildIndex_exports = {};
342
+ __export(buildIndex_exports, {
343
+ buildIndex: () => buildIndex
344
+ });
375
345
  function ensureRelativePathHasDot(relativePath) {
376
346
  return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
377
347
  }
@@ -459,14 +429,369 @@ async function buildIndex({ configPath }) {
459
429
  return index;
460
430
  }
461
431
  }
432
+ var import_common, import_node_fs, import_glob, import_path, import_csf_tools, import_csf, import_preview_api, import_common2, cwd, makeTitle;
433
+ var init_buildIndex = __esm({
434
+ "src/metro/buildIndex.ts"() {
435
+ import_common = require("storybook/internal/common");
436
+ import_node_fs = require("fs");
437
+ import_glob = require("glob");
438
+ import_path = __toESM(require("path"));
439
+ import_csf_tools = require("storybook/internal/csf-tools");
440
+ import_csf = require("storybook/internal/csf");
441
+ import_preview_api = require("storybook/internal/preview-api");
442
+ import_common2 = __toESM(require_common());
443
+ cwd = process.cwd();
444
+ makeTitle = (fileName, specifier, userTitle) => {
445
+ const title = (0, import_preview_api.userOrAutoTitleFromSpecifier)(fileName, specifier, userTitle);
446
+ if (title) {
447
+ return title.replace("./", "");
448
+ } else if (userTitle) {
449
+ return userTitle.replace("./", "");
450
+ } else {
451
+ console.error("Could not generate title!!");
452
+ process.exit(1);
453
+ }
454
+ };
455
+ }
456
+ });
457
+
458
+ // src/metro/manifest/storyInstructions.ts
459
+ var storyInstructions_exports = {};
460
+ __export(storyInstructions_exports, {
461
+ storyInstructions: () => storyInstructions
462
+ });
463
+ var storyInstructions;
464
+ var init_storyInstructions = __esm({
465
+ "src/metro/manifest/storyInstructions.ts"() {
466
+ storyInstructions = `# Writing React Native UI Components
467
+
468
+ When writing UI, prefer breaking larger components up into smaller parts.
469
+
470
+ ALWAYS write a Storybook story for any component written. If editing a component, ensure appropriate changes have been made to stories for that component.
471
+
472
+ ## How to write good stories
473
+
474
+ Goal: Cover every distinct piece of business logic and state the component can reach (happy paths, error/edge states, loading, permissions/roles, empty states, variations from props/context). Avoid redundant stories that show the same logic.
475
+
476
+ Interactivity: For interactive components, create separate stories that demonstrate each interaction state. Use \`fn()\` from \`storybook/test\` to mock callback props so you can verify they are wired up correctly.
477
+
478
+ Data/setup: Provide realistic props, state, and mocked data. Include meaningful labels/text to make behaviors observable. Stub network/services with deterministic fixtures; keep stories reliable.
479
+
480
+ Variants to consider (pick only those that change behavior): default vs. alternate themes; loading vs. loaded vs. empty vs. error; validated vs. invalid input; permissions/roles/capabilities; feature flags; size/density/layout variants that alter logic.
481
+
482
+ Accessibility: Use semantic roles/labels where applicable.
483
+
484
+ Naming/structure: Use clear story names that describe the scenario ("Error state after failed submit"). Group related variants logically; don't duplicate.
485
+
486
+ Imports/format: Import Meta/StoryObj from the framework package. Keep stories minimal\u2014only what's needed to demonstrate behavior.
487
+
488
+ ## React Native Storybook Essentials
489
+
490
+ ### Framework and Renderer
491
+
492
+ React Native Storybook uses \`@storybook/react-native\` as the framework. Stories use the same CSF (Component Story Format) as web Storybook.
493
+
494
+ ### Meta and StoryObj imports
495
+
496
+ \`\`\`ts
497
+ import type { Meta, StoryObj } from '@storybook/react-native';
498
+ \`\`\`
499
+
500
+ ### Story file structure
501
+
502
+ \`\`\`tsx
503
+ import type { Meta, StoryObj } from '@storybook/react-native';
504
+ import { MyComponent } from './MyComponent';
505
+
506
+ const meta: Meta<typeof MyComponent> = {
507
+ title: 'Components/MyComponent',
508
+ component: MyComponent,
509
+ args: {
510
+ // default args
511
+ },
512
+ };
513
+
514
+ export default meta;
515
+
516
+ type Story = StoryObj<typeof meta>;
517
+
518
+ export const Default: Story = {};
519
+
520
+ export const WithCustomProps: Story = {
521
+ args: {
522
+ label: 'Custom Label',
523
+ variant: 'secondary',
524
+ },
525
+ };
526
+ \`\`\`
527
+
528
+ ### Global State Changes
529
+
530
+ The \`globals\` annotation has been renamed to \`initialGlobals\`:
531
+
532
+ \`\`\`diff
533
+ // .rnstorybook/preview.js
534
+ export default {
535
+ - globals: { theme: 'light' }
536
+ + initialGlobals: { theme: 'light' }
537
+ };
538
+ \`\`\`
539
+
540
+ ### React Native Specific Considerations
541
+
542
+ - The config directory is \`.rnstorybook\` (not \`.storybook\`)
543
+ - Stories run on-device (iOS/Android), not in a browser
544
+ - Use React Native components (\`View\`, \`Text\`, \`Pressable\`, etc.), not HTML elements
545
+ - \`StyleSheet\` or inline styles instead of CSS
546
+ - No DOM APIs \u2014 use React Native's layout system (Flexbox)
547
+ - For navigation-dependent components, mock the navigation context
548
+ - For platform-specific stories, use \`Platform.OS\` checks or separate story files
549
+ - Test on both iOS and Android when possible
550
+
551
+ ### Key Requirements
552
+
553
+ - **Node.js 20+**, **TypeScript 4.9+**
554
+ - React Native 0.72+
555
+ - Storybook 10+
556
+ `;
557
+ }
558
+ });
559
+
560
+ // src/metro/withStorybook.ts
561
+ var withStorybook_exports = {};
562
+ __export(withStorybook_exports, {
563
+ withStorybook: () => withStorybook
564
+ });
565
+ module.exports = __toCommonJS(withStorybook_exports);
566
+ var path2 = __toESM(require("path"));
567
+ var import_generate = __toESM(require_generate());
568
+ var import_common3 = require("storybook/internal/common");
569
+ var import_telemetry = require("storybook/internal/telemetry");
570
+
571
+ // src/metro/channelServer.ts
572
+ var import_ws = require("ws");
573
+ var import_node_http = require("http");
574
+ init_buildIndex();
575
+
576
+ // src/metro/mcpServer.ts
577
+ var import_consumers = require("stream/consumers");
578
+ function toHeaderEntries(nodeHeaders) {
579
+ const entries = [];
580
+ for (const [key, value] of Object.entries(nodeHeaders)) {
581
+ if (value === void 0) continue;
582
+ entries.push([key, Array.isArray(value) ? value.join(", ") : value]);
583
+ }
584
+ return entries;
585
+ }
586
+ async function incomingMessageToWebRequest(req) {
587
+ const host = req.headers.host || "localhost";
588
+ const isTLS = "encrypted" in req.socket && req.socket.encrypted;
589
+ const protocol = isTLS ? "https" : "http";
590
+ const url = new URL(req.url || "/", `${protocol}://${host}`);
591
+ const bodyBuffer = await (0, import_consumers.buffer)(req);
592
+ return new Request(url, {
593
+ method: req.method,
594
+ headers: toHeaderEntries(req.headers),
595
+ body: bodyBuffer.length > 0 ? new Uint8Array(bodyBuffer) : void 0
596
+ });
597
+ }
598
+ async function webResponseToServerResponse(webResponse, nodeResponse) {
599
+ nodeResponse.statusCode = webResponse.status;
600
+ webResponse.headers.forEach((value, key) => {
601
+ nodeResponse.setHeader(key, value);
602
+ });
603
+ if (webResponse.body) {
604
+ const reader = webResponse.body.getReader();
605
+ try {
606
+ while (true) {
607
+ const { done, value } = await reader.read();
608
+ if (done) break;
609
+ nodeResponse.write(value);
610
+ }
611
+ } finally {
612
+ reader.releaseLock();
613
+ }
614
+ }
615
+ nodeResponse.end();
616
+ }
617
+ function createMcpHandler(configPath, wss) {
618
+ let handler = null;
619
+ let initPromise = null;
620
+ async function init() {
621
+ if (handler) return;
622
+ if (initPromise) {
623
+ await initPromise;
624
+ return;
625
+ }
626
+ initPromise = (async () => {
627
+ try {
628
+ const [
629
+ { McpServer },
630
+ { ValibotJsonSchemaAdapter },
631
+ { HttpTransport },
632
+ {
633
+ addListAllDocumentationTool,
634
+ addGetDocumentationTool,
635
+ addGetComponentStoryDocumentationTool
636
+ },
637
+ { storyInstructions: storyInstructions2 },
638
+ { buildIndex: buildIndex2 },
639
+ valibot,
640
+ { experimental_manifests }
641
+ ] = await Promise.all([
642
+ import("tmcp"),
643
+ import("@tmcp/adapter-valibot"),
644
+ import("@tmcp/transport-http"),
645
+ import("@storybook/mcp"),
646
+ Promise.resolve().then(() => (init_storyInstructions(), storyInstructions_exports)),
647
+ Promise.resolve().then(() => (init_buildIndex(), buildIndex_exports)),
648
+ import("valibot"),
649
+ import("@storybook/react/preset")
650
+ ]);
651
+ const manifestProvider = async (_request, manifestPath) => {
652
+ if (manifestPath.includes("docs.json")) {
653
+ throw new Error("Docs manifest not available in React Native Storybook");
654
+ }
655
+ const index = await buildIndex2({ configPath });
656
+ const entries = Object.values(index.entries);
657
+ const manifest = await experimental_manifests({}, { manifestEntries: entries });
658
+ return JSON.stringify(manifest.components);
659
+ };
660
+ const server = new McpServer(
661
+ {
662
+ name: "@storybook/react-native",
663
+ version: "1.0.0",
664
+ description: "Storybook React Native MCP server"
665
+ },
666
+ {
667
+ adapter: new ValibotJsonSchemaAdapter(),
668
+ capabilities: {
669
+ tools: { listChanged: true }
670
+ }
671
+ }
672
+ ).withContext();
673
+ addListAllDocumentationTool(server);
674
+ addGetDocumentationTool(server);
675
+ addGetComponentStoryDocumentationTool(server);
676
+ server.tool(
677
+ {
678
+ name: "get-storybook-story-instructions",
679
+ title: "React Native Storybook Story Instructions",
680
+ description: "Get instructions for writing React Native Storybook stories. Call this before creating or modifying story files (.stories.tsx, .stories.ts)."
681
+ },
682
+ async () => ({
683
+ content: [{ type: "text", text: storyInstructions2 }]
684
+ })
685
+ );
686
+ if (wss) {
687
+ const broadcastEvent = (event) => {
688
+ const message = JSON.stringify(event);
689
+ wss.clients.forEach((client) => {
690
+ if (client.readyState === 1) {
691
+ client.send(message);
692
+ }
693
+ });
694
+ };
695
+ server.tool(
696
+ {
697
+ name: "select-story",
698
+ title: "Select Story",
699
+ description: 'Select and display a story on the connected device. Use the story ID in the format "title--name" (e.g. "button--primary"). Use the list-all-documentation tool to discover available components and stories.',
700
+ schema: valibot.object({ storyId: valibot.string() })
701
+ },
702
+ async ({ storyId }) => {
703
+ try {
704
+ const index = await buildIndex2({ configPath });
705
+ if (!index.entries[storyId]) {
706
+ const availableIds = Object.keys(index.entries).slice(0, 10);
707
+ return {
708
+ content: [
709
+ {
710
+ type: "text",
711
+ text: `Story "${storyId}" not found. Available stories include: ${availableIds.join(", ")}` + (Object.keys(index.entries).length > 10 ? ", ..." : "")
712
+ }
713
+ ],
714
+ isError: true
715
+ };
716
+ }
717
+ broadcastEvent({
718
+ type: "setCurrentStory",
719
+ args: [{ storyId, viewMode: "story" }]
720
+ });
721
+ const entry = index.entries[storyId];
722
+ return {
723
+ content: [
724
+ {
725
+ type: "text",
726
+ text: `Selected story "${entry.name}" (${entry.title}) on connected devices.`
727
+ }
728
+ ]
729
+ };
730
+ } catch (error) {
731
+ return {
732
+ content: [
733
+ {
734
+ type: "text",
735
+ text: `Failed to select story: ${error instanceof Error ? error.message : String(error)}`
736
+ }
737
+ ],
738
+ isError: true
739
+ };
740
+ }
741
+ }
742
+ );
743
+ }
744
+ const transport = new HttpTransport(server, { path: null });
745
+ handler = (req) => transport.respond(req, {
746
+ request: req,
747
+ manifestProvider
748
+ });
749
+ console.log("[Storybook] MCP server initialized");
750
+ } catch (error) {
751
+ initPromise = null;
752
+ console.error("[Storybook] Failed to initialize MCP server:", error);
753
+ throw error;
754
+ }
755
+ })();
756
+ await initPromise;
757
+ }
758
+ async function handleMcpRequest(req, res) {
759
+ try {
760
+ await init();
761
+ if (!handler) {
762
+ res.writeHead(500, { "Content-Type": "application/json" });
763
+ res.end(JSON.stringify({ error: "MCP handler not initialized" }));
764
+ return;
765
+ }
766
+ const webRequest = await incomingMessageToWebRequest(req);
767
+ const webResponse = await handler(webRequest);
768
+ await webResponseToServerResponse(webResponse, res);
769
+ } catch (error) {
770
+ console.error("[Storybook] MCP request failed:", error);
771
+ res.writeHead(500, { "Content-Type": "application/json" });
772
+ res.end(JSON.stringify({ error: "MCP request failed" }));
773
+ }
774
+ }
775
+ function preInit() {
776
+ init().catch(
777
+ (e) => console.warn("[Storybook] MCP pre-initialization failed (will retry on first request):", e)
778
+ );
779
+ }
780
+ return { handleMcpRequest, preInit };
781
+ }
462
782
 
463
783
  // src/metro/channelServer.ts
464
784
  function createChannelServer({
465
785
  port = 7007,
466
786
  host = void 0,
467
- configPath
787
+ configPath,
788
+ experimental_mcp = false,
789
+ websockets = true
468
790
  }) {
469
- const httpServer = (0, import_node_http.createServer)(async (req, res) => {
791
+ const httpServer = (0, import_node_http.createServer)();
792
+ const wss = websockets ? new import_ws.WebSocketServer({ server: httpServer }) : null;
793
+ const mcpServer = experimental_mcp ? createMcpHandler(configPath, wss ?? void 0) : null;
794
+ httpServer.on("request", async (req, res) => {
470
795
  if (req.method === "OPTIONS") {
471
796
  res.writeHead(204);
472
797
  res.end();
@@ -485,6 +810,11 @@ function createChannelServer({
485
810
  return;
486
811
  }
487
812
  if (req.method === "POST" && req.url === "/send-event") {
813
+ if (!wss) {
814
+ res.writeHead(503, { "Content-Type": "application/json" });
815
+ res.end(JSON.stringify({ success: false, error: "WebSockets are disabled" }));
816
+ return;
817
+ }
488
818
  let body = "";
489
819
  req.on("data", (chunk) => {
490
820
  body += chunk.toString();
@@ -503,31 +833,36 @@ function createChannelServer({
503
833
  });
504
834
  return;
505
835
  }
836
+ if (mcpServer && req.url === "/mcp" && (req.method === "POST" || req.method === "GET")) {
837
+ await mcpServer.handleMcpRequest(req, res);
838
+ return;
839
+ }
506
840
  res.writeHead(404, { "Content-Type": "application/json" });
507
841
  res.end(JSON.stringify({ error: "Not found" }));
508
842
  });
509
- const wss = new import_ws.WebSocketServer({ server: httpServer });
510
- wss.on("error", () => {
511
- });
512
- setInterval(function ping() {
513
- wss.clients.forEach(function each(client) {
514
- if (client.readyState === import_ws.WebSocket.OPEN) {
515
- client.send(JSON.stringify({ type: "ping", args: [] }));
516
- }
843
+ if (wss) {
844
+ wss.on("error", () => {
517
845
  });
518
- }, 1e4);
519
- wss.on("connection", function connection(ws) {
520
- console.log("WebSocket connection established");
521
- ws.on("error", console.error);
522
- ws.on("message", function message(data) {
523
- try {
524
- const json = JSON.parse(data.toString());
525
- wss.clients.forEach((wsClient) => wsClient.send(JSON.stringify(json)));
526
- } catch (error) {
527
- console.error(error);
528
- }
846
+ setInterval(function ping() {
847
+ wss.clients.forEach(function each(client) {
848
+ if (client.readyState === import_ws.WebSocket.OPEN) {
849
+ client.send(JSON.stringify({ type: "ping", args: [] }));
850
+ }
851
+ });
852
+ }, 1e4);
853
+ wss.on("connection", function connection(ws) {
854
+ console.log("WebSocket connection established");
855
+ ws.on("error", console.error);
856
+ ws.on("message", function message(data) {
857
+ try {
858
+ const json = JSON.parse(data.toString());
859
+ wss.clients.forEach((wsClient) => wsClient.send(JSON.stringify(json)));
860
+ } catch (error) {
861
+ console.error(error);
862
+ }
863
+ });
529
864
  });
530
- });
865
+ }
531
866
  httpServer.on("error", (error) => {
532
867
  if (error.code === "EADDRINUSE") {
533
868
  console.warn(
@@ -538,8 +873,10 @@ function createChannelServer({
538
873
  }
539
874
  });
540
875
  httpServer.listen(port, host, () => {
541
- console.log(`WebSocket server listening on ${host ?? "localhost"}:${port}`);
876
+ const protocol = wss ? "WebSocket" : "HTTP";
877
+ console.log(`${protocol} server listening on ${host ?? "localhost"}:${port}`);
542
878
  });
879
+ mcpServer?.preInit();
543
880
  return wss;
544
881
  }
545
882
 
@@ -557,7 +894,8 @@ function withStorybook(config, options = {
557
894
  useJs = false,
558
895
  enabled = true,
559
896
  docTools = true,
560
- liteMode = false
897
+ liteMode = false,
898
+ experimental_mcp = false
561
899
  } = options;
562
900
  const disableTelemetry = (0, import_common3.optionalEnvToBoolean)(process.env.STORYBOOK_DISABLE_TELEMETRY);
563
901
  if (!disableTelemetry && enabled) {
@@ -598,17 +936,31 @@ function withStorybook(config, options = {
598
936
  }
599
937
  };
600
938
  }
601
- if (websockets) {
602
- const port = websockets === "auto" ? 7007 : websockets.port ?? 7007;
603
- const host = websockets === "auto" ? "auto" : websockets.host;
604
- createChannelServer({ port, host: host === "auto" ? void 0 : host, configPath });
605
- (0, import_generate.generate)({
939
+ if (websockets || experimental_mcp) {
940
+ const port = websockets === "auto" ? 7007 : websockets?.port ?? 7007;
941
+ const host = websockets === "auto" ? "auto" : websockets?.host;
942
+ createChannelServer({
943
+ port,
944
+ host: host === "auto" ? void 0 : host,
606
945
  configPath,
607
- useJs,
608
- docTools,
609
- host,
610
- port
946
+ experimental_mcp,
947
+ websockets: Boolean(websockets)
611
948
  });
949
+ if (websockets) {
950
+ (0, import_generate.generate)({
951
+ configPath,
952
+ useJs,
953
+ docTools,
954
+ host,
955
+ port
956
+ });
957
+ } else {
958
+ (0, import_generate.generate)({
959
+ configPath,
960
+ useJs,
961
+ docTools
962
+ });
963
+ }
612
964
  } else {
613
965
  (0, import_generate.generate)({
614
966
  configPath,
package/dist/node.d.ts CHANGED
@@ -17,6 +17,16 @@ interface ChannelServerOptions {
17
17
  * The path to the Storybook config folder.
18
18
  */
19
19
  configPath: string;
20
+ /**
21
+ * Whether to enable MCP (Model Context Protocol) server support.
22
+ * When enabled, adds an /mcp endpoint.
23
+ */
24
+ experimental_mcp?: boolean;
25
+ /**
26
+ * Whether to enable WebSocket support.
27
+ * When false, starts only the HTTP server endpoints.
28
+ */
29
+ websockets?: boolean;
20
30
  }
21
31
  /**
22
32
  * Creates a channel server for syncing storybook instances and sending events.
@@ -24,14 +34,17 @@ interface ChannelServerOptions {
24
34
  * - WebSocket: broadcasts all received messages to all connected clients
25
35
  * - POST /send-event: sends an event to all WebSocket clients
26
36
  * - GET /index.json: returns the story index built from story files
37
+ * - POST /mcp: MCP endpoint for AI agent integration (when experimental_mcp option is enabled)
27
38
  *
28
39
  * @param options - Configuration options for the channel server.
29
40
  * @param options.port - The port to listen on.
30
41
  * @param options.host - The host to bind to.
31
42
  * @param options.configPath - The path to the Storybook config folder.
32
- * @returns The created WebSocketServer instance.
43
+ * @param options.experimental_mcp - Whether to enable MCP server support.
44
+ * @param options.websockets - Whether to enable WebSocket server support.
45
+ * @returns The created WebSocketServer instance, or null when websockets are disabled.
33
46
  */
34
- declare function createChannelServer({ port, host, configPath, }: ChannelServerOptions): WebSocketServer;
47
+ declare function createChannelServer({ port, host, configPath, experimental_mcp, websockets, }: ChannelServerOptions): WebSocketServer | null;
35
48
 
36
49
  declare function buildIndex({ configPath }: {
37
50
  configPath: string;