@slidev/cli 52.16.0 → 52.17.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/README.md CHANGED
@@ -19,7 +19,7 @@ Presentation <b>slide</b>s for <b>dev</b>elopers 🧑‍💻👩‍💻👨‍
19
19
  </p>
20
20
 
21
21
  <p align="center">
22
- <a href="https://twitter.com/antfu7/status/1389604687502995457">Video Preview</a> | <a href="https://sli.dev">Documentation</a>
22
+ <a href="https://x.com/antfu7/status/1389604687502995457">Video Preview</a> | <a href="https://sli.dev">Documentation</a>
23
23
  </p>
24
24
 
25
25
  <div align="center">
@@ -1,4 +1,4 @@
1
- import { n as resolveViteConfigs } from "./serve-bnzg299W.mjs";
1
+ import { n as resolveViteConfigs } from "./serve-CgOXLPUI.mjs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { build as build$1 } from "vite";
4
4
  import { existsSync } from "node:fs";
@@ -51,7 +51,7 @@ async function build(options, viteConfig = {}, args) {
51
51
  dev: true
52
52
  }));
53
53
  server.listen(port);
54
- const { exportSlides } = await import("./export-BHxAO7pf.mjs");
54
+ const { exportSlides } = await import("./export-CU2GL9dm.mjs");
55
55
  const tempDir = resolve(outDir, "temp");
56
56
  await fs$1.mkdir(tempDir, { recursive: true });
57
57
  await exportSlides({
@@ -64,7 +64,7 @@ async function build(options, viteConfig = {}, args) {
64
64
  range: "1",
65
65
  width: options.data.config.canvasWidth,
66
66
  height: Math.round(options.data.config.canvasWidth / options.data.config.aspectRatio),
67
- routerMode: options.data.config.routerMode,
67
+ routerMode: options.data.config.routerMode === "memory" ? "history" : options.data.config.routerMode,
68
68
  waitUntil: "networkidle",
69
69
  timeout: args.timeout || 3e4,
70
70
  perSlide: true,
@@ -92,7 +92,7 @@ async function build(options, viteConfig = {}, args) {
92
92
  "true",
93
93
  "auto"
94
94
  ].includes(options.data.config.download)) {
95
- const { exportSlides, getExportOptions } = await import("./export-BHxAO7pf.mjs");
95
+ const { exportSlides, getExportOptions } = await import("./export-CU2GL9dm.mjs");
96
96
  const port = 12445;
97
97
  const app = connect();
98
98
  const server = http.createServer(app);
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { i as resolveEntry, r as isInstalledGlobally } from "./resolver-CZx9LFrt.mjs";
2
- import { a as resolveOptions, c as resolveTheme, d as updateFrontmatterPatch, l as version, o as parser, s as getThemeMeta, t as createServer, u as loadSetups } from "./serve-bnzg299W.mjs";
1
+ import { i as resolveEntry, r as isInstalledGlobally } from "./resolver-BBT539iz.mjs";
2
+ import { c as getThemeMeta, d as loadSetups, f as updateFrontmatterPatch, l as resolveTheme, o as resolveOptions, s as parser, t as createServer, u as version } from "./serve-CgOXLPUI.mjs";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
5
  import fs from "node:fs/promises";
@@ -288,15 +288,19 @@ cli.command("build [entry..]", "Build hostable SPA", (args) => exportOptions(com
288
288
  describe: "exclude speaker notes from the built output"
289
289
  }).option("router-mode", {
290
290
  type: "string",
291
- choices: ["hash", "history"],
292
- describe: "override routerMode in the built output (hash for subdirectory deploys like GitHub Pages)"
291
+ choices: [
292
+ "hash",
293
+ "history",
294
+ "memory"
295
+ ],
296
+ describe: "override routerMode in the built output (hash for subdirectory deploys like GitHub Pages; memory keeps the slide number out of the URL, for kiosk/follower decks)"
293
297
  }).option("inspect", {
294
298
  default: false,
295
299
  type: "boolean",
296
300
  describe: "enable the inspect plugin for debugging"
297
301
  }).strict().help(), async (args) => {
298
302
  const { entry, theme, base, download, out, inspect, "without-notes": withoutNotes, "router-mode": routerMode } = args;
299
- const { build } = await import("./build-BtXN8hyb.mjs");
303
+ const { build } = await import("./build-Cszi5hDI.mjs");
300
304
  for (const entryFile of entry) {
301
305
  const options = await resolveOptions({
302
306
  entry: entryFile,
@@ -324,6 +328,10 @@ cli.command("format [entry..]", "Format the markdown file", (args) => commonOpti
324
328
  await parser.save(md);
325
329
  }
326
330
  });
331
+ cli.command("mcp [entry]", "Start an MCP (Model Context Protocol) server over stdio for AI agents to inspect and edit the slides", (args) => commonOptions(args).strict().help(), async ({ entry }) => {
332
+ const { startMcpStdioServer } = await import("./stdio-LsewwOTO.mjs");
333
+ await startMcpStdioServer(await resolveEntry(entry));
334
+ });
327
335
  cli.command("theme [subcommand]", "Theme related operations", (command) => {
328
336
  return command.command("eject", "Eject current theme into local file system", (args) => commonOptions(args).option("dir", {
329
337
  type: "string",
@@ -361,7 +369,7 @@ cli.command("theme [subcommand]", "Theme related operations", (command) => {
361
369
  });
362
370
  cli.command("export [entry..]", "Export slides to PDF", (args) => exportOptions(commonOptions(args)).strict().help(), async (args) => {
363
371
  const { entry, theme } = args;
364
- const { exportSlides, getExportOptions } = await import("./export-BHxAO7pf.mjs");
372
+ const { exportSlides, getExportOptions } = await import("./export-CU2GL9dm.mjs");
365
373
  const candidatePort = await getPort(12445);
366
374
  let warned = false;
367
375
  for (const entryFile of entry) {
@@ -413,7 +421,7 @@ cli.command("export-notes [entry..]", "Export slide notes to PDF", (args) => arg
413
421
  type: "number",
414
422
  describe: "wait for the specified ms before exporting"
415
423
  }).strict().help(), async ({ entry, output, timeout, wait }) => {
416
- const { exportNotes } = await import("./export-BHxAO7pf.mjs");
424
+ const { exportNotes } = await import("./export-CU2GL9dm.mjs");
417
425
  const candidatePort = await getPort(12445);
418
426
  for (const entryFile of entry) {
419
427
  const options = await resolveOptions({ entry: entryFile }, "export");
@@ -539,6 +547,7 @@ function printInfo(options, port, base, remote, tunnelUrl, publicIp) {
539
547
  if (options.utils.define.__SLIDEV_FEATURE_PRESENTER__) console.log(`${dim(" presenter mode ")} > ${blue(`${baseUrl}${presenterPath}`)}`);
540
548
  console.log(`${dim(" slides overview ")} > ${blue(`${baseUrl}${overviewPath}`)}`);
541
549
  if (options.utils.define.__SLIDEV_FEATURE_BROWSER_EXPORTER__) console.log(`${dim(" export slides")} > ${blue(`${baseUrl}/export/`)}`);
550
+ if (options.mode === "dev" && options.data.config.mcp !== false) console.log(`${dim(" mcp server (ai)")} > ${blue(`http://localhost:${bold(port)}/__mcp`)}`);
542
551
  if (options.inspect) console.log(`${dim(" vite inspector")} > ${yellow(`${baseUrl}/__inspect/`)}`);
543
552
  let lastRemoteUrl = "";
544
553
  if (remote !== void 0) {
@@ -1,4 +1,4 @@
1
- import { n as getRoots } from "./resolver-CZx9LFrt.mjs";
1
+ import { n as getRoots } from "./resolver-BBT539iz.mjs";
2
2
  import path, { dirname, relative } from "node:path";
3
3
  import process from "node:process";
4
4
  import fs from "node:fs/promises";
@@ -387,7 +387,7 @@ function getExportOptions(args, options, outFilename) {
387
387
  wait: wait ?? 0,
388
388
  waitUntil: waitUntil === "none" ? void 0 : waitUntil ?? "networkidle",
389
389
  dark: dark || options.data.config.colorSchema === "dark",
390
- routerMode: options.data.config.routerMode,
390
+ routerMode: options.data.config.routerMode === "memory" ? "history" : options.data.config.routerMode,
391
391
  width: options.data.config.canvasWidth,
392
392
  height: Math.round(options.data.config.canvasWidth / options.data.config.aspectRatio),
393
393
  withClicks: withClicks ?? format === "pptx",
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { InlineConfig, PluginOption } from "vite";
2
2
  import * as parser from "@slidev/parser/fs";
3
3
  import { ResolvedSlidevOptions, ResolvedSlidevUtils, SlidevEntryOptions, SlidevPluginOptions, SlidevServerOptions } from "@slidev/types";
4
-
5
4
  //#region node/commands/serve.d.ts
6
5
  declare function createServer(options: ResolvedSlidevOptions, viteConfig?: InlineConfig, serverOptions?: SlidevServerOptions): Promise<import("vite").ViteDevServer>;
7
6
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as resolveOptions, i as createDataUtils, o as parser, r as ViteSlidevPlugin, t as createServer } from "./serve-bnzg299W.mjs";
1
+ import { a as createDataUtils, o as resolveOptions, r as ViteSlidevPlugin, s as parser, t as createServer } from "./serve-CgOXLPUI.mjs";
2
2
  export { ViteSlidevPlugin, createDataUtils, createServer, parser, resolveOptions };
@@ -229,12 +229,14 @@ async function getRoots(entry) {
229
229
  isInstalledGlobally.value = slash(relative(userRoot, process.argv[1])).includes("/.pnpm/") || invocationNodeModules != null && slash(relative(userRoot, invocationNodeModules)).startsWith("..") || (await import("is-installed-globally")).default;
230
230
  const clientRoot = await findPkgRoot("@slidev/client", cliRoot, true);
231
231
  const closestPkgRoot = dirname(await findClosestPkgJsonPath(userRoot) || userRoot);
232
+ const userPkgJson = await getUserPkgJson(closestPkgRoot);
233
+ const userWorkspaceRoot = await searchForWorkspaceRoot(closestPkgRoot);
232
234
  rootsInfo = {
233
235
  cliRoot,
234
236
  clientRoot,
235
237
  userRoot,
236
- userPkgJson: await getUserPkgJson(closestPkgRoot),
237
- userWorkspaceRoot: await searchForWorkspaceRoot(closestPkgRoot)
238
+ userPkgJson,
239
+ userWorkspaceRoot
238
240
  };
239
241
  return rootsInfo;
240
242
  }
@@ -1,4 +1,4 @@
1
- import { a as resolveImportPath, c as toAtFS, i as resolveEntry, n as getRoots, o as resolveImportUrl, r as isInstalledGlobally, s as resolveSourceFiles, t as createResolver } from "./resolver-CZx9LFrt.mjs";
1
+ import { a as resolveImportPath, c as toAtFS, i as resolveEntry, n as getRoots, o as resolveImportUrl, r as isInstalledGlobally, s as resolveSourceFiles, t as createResolver } from "./resolver-BBT539iz.mjs";
2
2
  import { builtinModules } from "node:module";
3
3
  import path, { basename, dirname, join, relative, resolve, win32 } from "node:path";
4
4
  import process from "node:process";
@@ -40,7 +40,11 @@ import lz from "lz-string";
40
40
  import { encode } from "plantuml-encoder";
41
41
  import { SourceMapConsumer } from "source-map-js";
42
42
  import { fromAsyncCodeToHtml } from "@shikijs/markdown-it/async";
43
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
44
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
45
+ import { z } from "zod";
43
46
  import ServerRef from "vite-plugin-vue-server-ref";
47
+ import { VitePWA } from "vite-plugin-pwa";
44
48
  import UnoCSS from "unocss/vite";
45
49
  import { mergeConfigs, presetIcons } from "unocss";
46
50
  import Vue from "@vitejs/plugin-vue";
@@ -307,6 +311,7 @@ const RE_SLIDEV_CLIENT_SUBPATH = /^@slidev\/client\/(.*)/;
307
311
  const RE_SLIDEV_VIRTUAL = /^#slidev\/(.*)/;
308
312
  const RE_MONACO_EDITOR = /\/monaco-editor(?:-core)?\//;
309
313
  const INCLUDE_GLOBAL = [
314
+ "@fix-webm-duration/fix",
310
315
  "@typescript/ata",
311
316
  "file-saver",
312
317
  "lz-string",
@@ -528,7 +533,7 @@ const templateTitleRenderer = {
528
533
  };
529
534
  //#endregion
530
535
  //#region node/vite/importGuard.ts
531
- const virtualSlideMarkdownIds = new Set([templateTitleRendererMd.id, templateLegacyTitles.id]);
536
+ const virtualSlideMarkdownIds = /* @__PURE__ */ new Set([templateTitleRendererMd.id, templateLegacyTitles.id]);
532
537
  function createSlideImportGuardPlugin() {
533
538
  let config;
534
539
  let allowRoots = [];
@@ -685,7 +690,7 @@ function createLayoutWrapperPlugin({ data, utils }) {
685
690
  }
686
691
  //#endregion
687
692
  //#region package.json
688
- var version = "52.16.0";
693
+ var version = "52.17.0";
689
694
  //#endregion
690
695
  //#region node/integrations/addons.ts
691
696
  async function resolveAddons(addonsInConfig) {
@@ -901,7 +906,7 @@ function resolveShikiOptions(options) {
901
906
  if (mergedOptions.themes) {
902
907
  for (const theme of Object.values(mergedOptions.themes)) if (typeof theme === "object" && theme?.name) themeInput[theme.name] = theme;
903
908
  }
904
- const languageNames = new Set([
909
+ const languageNames = /* @__PURE__ */ new Set([
905
910
  "markdown",
906
911
  "vue",
907
912
  "javascript",
@@ -1071,6 +1076,7 @@ function getDefine(options) {
1071
1076
  __DEV__: options.mode === "dev",
1072
1077
  __SLIDEV_CLIENT_ROOT__: toAtFS(options.clientRoot),
1073
1078
  __SLIDEV_HASH_ROUTE__: options.data.config.routerMode === "hash",
1079
+ __SLIDEV_MEMORY_ROUTE__: options.data.config.routerMode === "memory" && options.mode !== "export",
1074
1080
  __SLIDEV_FEATURE_DRAWINGS__: matchMode(options.data.config.drawings.enabled),
1075
1081
  __SLIDEV_FEATURE_EDITOR__: options.mode === "dev" && options.data.config.editor !== false,
1076
1082
  __SLIDEV_FEATURE_DRAWINGS_PERSIST__: !!options.data.config.drawings.persist,
@@ -1083,6 +1089,7 @@ function getDefine(options) {
1083
1089
  ].includes(options.data.config.download),
1084
1090
  __SLIDEV_FEATURE_BROWSER_EXPORTER__: matchMode(options.data.config.browserExporter),
1085
1091
  __SLIDEV_FEATURE_WAKE_LOCK__: matchMode(options.data.config.wakeLock),
1092
+ __SLIDEV_FEATURE_PWA__: matchMode(options.data.config.pwa),
1086
1093
  __SLIDEV_HAS_SERVER__: options.mode !== "build"
1087
1094
  }, (v, k) => [v, JSON.stringify(k)]);
1088
1095
  }
@@ -1658,6 +1665,7 @@ function createSlidesLoader(options, serverOptions) {
1658
1665
  frontmatter: void 0,
1659
1666
  source: void 0,
1660
1667
  importChain: void 0,
1668
+ images: slide.images ?? slide.source?.images,
1661
1669
  ...mode === "build" ? {
1662
1670
  raw: "",
1663
1671
  content: "",
@@ -2579,6 +2587,491 @@ async function createMarkdownPlugin(options, { markdown: mdOptions }) {
2579
2587
  });
2580
2588
  }
2581
2589
  //#endregion
2590
+ //#region node/mcp/operations.ts
2591
+ /**
2592
+ * Resolve a rendered slide (1-based, as displayed in the presentation) or
2593
+ * throw a descriptive error.
2594
+ */
2595
+ function resolveSlide(data, no) {
2596
+ const slide = data.slides[no - 1];
2597
+ if (!slide) throw new Error(`Slide ${no} does not exist. The deck has ${data.slides.length} slides (1-${data.slides.length}).`);
2598
+ return slide;
2599
+ }
2600
+ function getMarkdown(data, source) {
2601
+ const md = data.markdownFiles[source.filepath];
2602
+ if (!md) throw new Error(`Markdown file not loaded: ${source.filepath}`);
2603
+ return md;
2604
+ }
2605
+ function assertNotEntryHeadmatter(data, source, action) {
2606
+ if (source.filepath === data.entry.filepath && data.entry.slides.indexOf(source) === 0) throw new Error(`Cannot ${action} the first slide of the entry file: its frontmatter is the deck headmatter (global configuration). Edit its content with the update tool instead, or operate on the following slides.`);
2607
+ }
2608
+ /**
2609
+ * Apply a `SlidePatch` to the slide source and save the markdown file.
2610
+ *
2611
+ * Note this only mutates the *source* slide (`slide.source`), not the
2612
+ * rendered `SlideInfo`, so a running dev server will pick up the change from
2613
+ * disk like an external edit and push HMR updates to connected clients.
2614
+ */
2615
+ async function applySlidePatch(data, no, patch) {
2616
+ const slide = resolveSlide(data, no);
2617
+ const source = slide.source;
2618
+ if (patch.content != null) source.content = patch.content;
2619
+ if (patch.frontmatterRaw != null) if (patch.frontmatterRaw.trim() === "") source.frontmatterDoc = source.frontmatterStyle = void 0;
2620
+ else {
2621
+ const parsed = YAML.parseDocument(patch.frontmatterRaw);
2622
+ if (parsed.errors.length) throw new Error(`Invalid YAML frontmatter: ${parsed.errors.map((e) => e.message).join("; ")}`);
2623
+ source.frontmatterDoc = parsed;
2624
+ }
2625
+ if (patch.note != null) source.note = patch.note;
2626
+ if (patch.frontmatter) updateFrontmatterPatch(source, patch.frontmatter);
2627
+ parser.prettifySlide(source);
2628
+ return {
2629
+ slide,
2630
+ fileContent: await parser.save(getMarkdown(data, source))
2631
+ };
2632
+ }
2633
+ /**
2634
+ * Insert a new slide after an existing one, in the same markdown file.
2635
+ */
2636
+ async function insertSlide(data, options) {
2637
+ const anchor = resolveSlide(data, options.after);
2638
+ const md = getMarkdown(data, anchor.source);
2639
+ const frontmatter = options.frontmatter ?? {};
2640
+ const doc = Object.keys(frontmatter).length > 0 ? new YAML.Document(frontmatter) : void 0;
2641
+ const source = {
2642
+ filepath: anchor.source.filepath,
2643
+ index: 0,
2644
+ start: 0,
2645
+ contentStart: 0,
2646
+ end: 0,
2647
+ raw: "",
2648
+ revision: "",
2649
+ content: options.content,
2650
+ contentRaw: options.content,
2651
+ frontmatter,
2652
+ frontmatterDoc: doc,
2653
+ frontmatterStyle: doc ? "frontmatter" : void 0,
2654
+ frontmatterRaw: doc?.toString(),
2655
+ note: options.note?.trim() || void 0
2656
+ };
2657
+ parser.prettifySlide(source);
2658
+ const anchorIdx = md.slides.indexOf(anchor.source);
2659
+ md.slides.splice(anchorIdx + 1, 0, source);
2660
+ const fileContent = await parser.save(md);
2661
+ return {
2662
+ source,
2663
+ filepath: md.filepath,
2664
+ fileContent
2665
+ };
2666
+ }
2667
+ /**
2668
+ * Remove a slide from its source markdown file.
2669
+ */
2670
+ async function removeSlide(data, no) {
2671
+ const slide = resolveSlide(data, no);
2672
+ assertNotEntryHeadmatter(data, slide.source, "remove");
2673
+ const md = getMarkdown(data, slide.source);
2674
+ const idx = md.slides.indexOf(slide.source);
2675
+ if (idx < 0) throw new Error(`Slide ${no} is out of sync with its source file. Try again.`);
2676
+ md.slides.splice(idx, 1);
2677
+ const fileContent = await parser.save(md);
2678
+ return {
2679
+ removed: slide,
2680
+ filepath: md.filepath,
2681
+ fileContent
2682
+ };
2683
+ }
2684
+ /**
2685
+ * Move a slide before or after another slide within the same markdown file.
2686
+ */
2687
+ async function moveSlide(data, options) {
2688
+ const { from, before, after } = options;
2689
+ if (before == null === (after == null)) throw new Error("Specify exactly one of `before` or `after`.");
2690
+ const anchorNo = before ?? after;
2691
+ if (anchorNo === from) throw new Error("The `before`/`after` anchor must be a different slide than `from`.");
2692
+ const slide = resolveSlide(data, from);
2693
+ const anchor = resolveSlide(data, anchorNo);
2694
+ if (slide.source.filepath !== anchor.source.filepath) throw new Error(`Cannot move a slide across markdown files: slide ${from} is in "${slide.source.filepath}" but slide ${anchorNo} is in "${anchor.source.filepath}" (imported with \`src:\`). Move it within its own file, or edit the \`src:\` imports in the entry file manually.`);
2695
+ assertNotEntryHeadmatter(data, slide.source, "move");
2696
+ if (before != null) assertNotEntryHeadmatter(data, anchor.source, "insert a slide before");
2697
+ const md = getMarkdown(data, slide.source);
2698
+ const fromIdx = md.slides.indexOf(slide.source);
2699
+ if (fromIdx < 0) throw new Error(`Slide ${from} is out of sync with its source file. Try again.`);
2700
+ md.slides.splice(fromIdx, 1);
2701
+ const anchorIdx = md.slides.indexOf(anchor.source);
2702
+ if (anchorIdx < 0) throw new Error(`Slide ${anchorNo} is out of sync with its source file. Try again.`);
2703
+ md.slides.splice(before != null ? anchorIdx : anchorIdx + 1, 0, slide.source);
2704
+ const fileContent = await parser.save(md);
2705
+ return {
2706
+ moved: slide,
2707
+ anchor,
2708
+ filepath: md.filepath,
2709
+ fileContent
2710
+ };
2711
+ }
2712
+ //#endregion
2713
+ //#region node/mcp/server.ts
2714
+ function result(data) {
2715
+ return { content: [{
2716
+ type: "text",
2717
+ text: typeof data === "string" ? data : JSON.stringify(data, null, 2)
2718
+ }] };
2719
+ }
2720
+ function slideSummary(slide) {
2721
+ return {
2722
+ no: slide.index + 1,
2723
+ title: slide.title ?? null,
2724
+ ...slide.frontmatter.layout ? { layout: slide.frontmatter.layout } : {},
2725
+ file: slide.source.filepath,
2726
+ hasNote: !!slide.source.note,
2727
+ ...slide.importChain?.length ? { importedBySrcDirective: true } : {}
2728
+ };
2729
+ }
2730
+ const noSchema = z.number().int().min(1).describe("Slide number (1-based, as displayed in the presentation)");
2731
+ const frontmatterSchema = z.record(z.any()).optional().describe("Slide frontmatter (YAML headmatter of the slide) as an object, e.g. { \"layout\": \"two-cols\" }");
2732
+ /**
2733
+ * Create the Slidev MCP server exposing tools for agents to inspect, edit,
2734
+ * and (with a running dev server) navigate a slides deck.
2735
+ *
2736
+ * The same tool set is served over the dev server HTTP endpoint (`/__mcp`)
2737
+ * and the `slidev mcp` stdio command.
2738
+ */
2739
+ function createSlidevMcpServer(ctx) {
2740
+ const server = new McpServer({
2741
+ name: "slidev",
2742
+ version: ctx.version
2743
+ }, { instructions: [
2744
+ "Tools for working with a Slidev (https://sli.dev) slides deck.",
2745
+ "A deck is a Markdown file where slides are separated by `---`; each slide can have YAML frontmatter, Markdown/Vue content, and a speaker note (trailing HTML comment).",
2746
+ "Slides are addressed by their rendered 1-based number, matching the slide numbers shown in the presentation.",
2747
+ "After editing tools run, a running dev server hot-reloads the presentation automatically."
2748
+ ].join("\n") });
2749
+ server.registerTool("slidev-get-info", {
2750
+ title: "Get deck info",
2751
+ description: "Get an overview of the Slidev deck: entry file, title, slide count, markdown files, and (when a dev server is running) the server URL and current position of the live presentation.",
2752
+ annotations: { readOnlyHint: true }
2753
+ }, async () => {
2754
+ const data = await ctx.getData();
2755
+ const nav = ctx.nav?.getState();
2756
+ return result({
2757
+ slidevVersion: ctx.version,
2758
+ entry: ctx.entry,
2759
+ title: data.headmatter.title ?? data.slides[0]?.title ?? null,
2760
+ theme: data.config?.theme ?? data.headmatter.theme ?? null,
2761
+ totalSlides: data.slides.length,
2762
+ markdownFiles: Object.keys(data.markdownFiles),
2763
+ ...ctx.getServerUrl?.() ? { server: {
2764
+ url: ctx.getServerUrl(),
2765
+ currentPage: nav?.page || null,
2766
+ currentClicks: nav?.page ? nav.clicks : null
2767
+ } } : {}
2768
+ });
2769
+ });
2770
+ server.registerTool("slidev-list-slides", {
2771
+ title: "List slides",
2772
+ description: "List all slides of the deck with their number, title, layout, and source file. Slides hidden with `hide`/`disabled` frontmatter are not included.",
2773
+ annotations: { readOnlyHint: true }
2774
+ }, async () => {
2775
+ return result((await ctx.getData()).slides.map(slideSummary));
2776
+ });
2777
+ server.registerTool("slidev-get-slide", {
2778
+ title: "Get slide",
2779
+ description: "Get the full source of one slide: frontmatter, Markdown content, and speaker note.",
2780
+ inputSchema: { no: noSchema },
2781
+ annotations: { readOnlyHint: true }
2782
+ }, async ({ no }) => {
2783
+ const slide = resolveSlide(await ctx.getData(), no);
2784
+ return result({
2785
+ ...slideSummary(slide),
2786
+ frontmatter: slide.source.frontmatter,
2787
+ content: slide.source.content.trim(),
2788
+ note: slide.source.note ?? null,
2789
+ ...slide.importChain?.length ? { importedBy: slide.importChain.map((s) => `${s.filepath}#${s.index + 1}`) } : {}
2790
+ });
2791
+ });
2792
+ server.registerTool("slidev-update-slide", {
2793
+ title: "Update slide",
2794
+ description: "Update the content, speaker note, and/or frontmatter of a slide. Only the provided fields are changed. Pass an empty string to clear the content or note. In `frontmatter`, only the given keys are patched; pass `null` as a value to delete that key.",
2795
+ inputSchema: {
2796
+ no: noSchema,
2797
+ content: z.string().optional().describe("New Markdown content of the slide (without frontmatter and note)"),
2798
+ note: z.string().optional().describe("New speaker note (Markdown, stored as a trailing HTML comment)"),
2799
+ frontmatter: frontmatterSchema
2800
+ }
2801
+ }, async ({ no, content, note, frontmatter }) => {
2802
+ if (content == null && note == null && frontmatter == null) throw new Error("Nothing to update: provide at least one of `content`, `note`, or `frontmatter`.");
2803
+ const { slide } = await applySlidePatch(await ctx.getData(), no, {
2804
+ content,
2805
+ note,
2806
+ frontmatter
2807
+ });
2808
+ return result(`Updated slide ${no} in ${slide.source.filepath}.`);
2809
+ });
2810
+ server.registerTool("slidev-insert-slide", {
2811
+ title: "Insert slide",
2812
+ description: "Insert a new slide after an existing slide (into the same markdown file). To add a slide at the very end, pass the last slide number.",
2813
+ inputSchema: {
2814
+ after: z.number().int().min(1).describe("Slide number (1-based) after which the new slide is inserted"),
2815
+ content: z.string().describe("Markdown content of the new slide"),
2816
+ frontmatter: frontmatterSchema,
2817
+ note: z.string().optional().describe("Speaker note of the new slide")
2818
+ }
2819
+ }, async ({ after, content, frontmatter, note }) => {
2820
+ const { filepath } = await insertSlide(await ctx.getData(), {
2821
+ after,
2822
+ content,
2823
+ frontmatter,
2824
+ note
2825
+ });
2826
+ return result(`Inserted a new slide after slide ${after} in ${filepath}. Slide numbers after it have shifted; list the slides again if needed.`);
2827
+ });
2828
+ server.registerTool("slidev-remove-slide", {
2829
+ title: "Remove slide",
2830
+ description: "Remove a slide from the deck (deletes it from its source markdown file).",
2831
+ inputSchema: { no: noSchema },
2832
+ annotations: { destructiveHint: true }
2833
+ }, async ({ no }) => {
2834
+ const { removed, filepath } = await removeSlide(await ctx.getData(), no);
2835
+ return result(`Removed slide ${no}${removed.title ? ` ("${removed.title}")` : ""} from ${filepath}. Slide numbers after it have shifted; list the slides again if needed.`);
2836
+ });
2837
+ server.registerTool("slidev-move-slide", {
2838
+ title: "Move slide",
2839
+ description: "Move a slide before or after another slide to reorder the deck. Both slides must be in the same markdown file. To swap two adjacent slides, move one after the other.",
2840
+ inputSchema: {
2841
+ from: z.number().int().min(1).describe("Slide number (1-based) of the slide to move"),
2842
+ before: z.number().int().min(1).optional().describe("Move the slide right before this slide number"),
2843
+ after: z.number().int().min(1).optional().describe("Move the slide right after this slide number")
2844
+ }
2845
+ }, async ({ from, before, after }) => {
2846
+ const { filepath } = await moveSlide(await ctx.getData(), {
2847
+ from,
2848
+ before,
2849
+ after
2850
+ });
2851
+ return result(`Moved slide ${from} ${before != null ? `before slide ${before}` : `after slide ${after}`} in ${filepath}. Slide numbers have shifted; list the slides again if needed.`);
2852
+ });
2853
+ if (ctx.nav) {
2854
+ const nav = ctx.nav;
2855
+ server.registerTool("slidev-goto-slide", {
2856
+ title: "Go to slide",
2857
+ description: "Navigate the live presentation (all connected browsers) to a given slide, e.g. to visually verify a slide after editing it.",
2858
+ inputSchema: {
2859
+ no: noSchema,
2860
+ clicks: z.number().int().min(0).optional().describe("Click animation step to reveal (defaults to 0)")
2861
+ },
2862
+ annotations: { idempotentHint: true }
2863
+ }, async ({ no, clicks }) => {
2864
+ resolveSlide(await ctx.getData(), no);
2865
+ nav.go(no, clicks ?? 0);
2866
+ return result(`Navigated the presentation to slide ${no}${clicks ? ` (click ${clicks})` : ""}.`);
2867
+ });
2868
+ }
2869
+ return server;
2870
+ }
2871
+ //#endregion
2872
+ //#region node/integrations/drawings.ts
2873
+ function resolveDrawingsDir(options) {
2874
+ return options.data.config.drawings.persist ? resolve(dirname(options.entry), options.data.config.drawings.persist) : void 0;
2875
+ }
2876
+ async function loadDrawings(options) {
2877
+ const dir = resolveDrawingsDir(options);
2878
+ if (!dir || !existsSync(dir)) return {};
2879
+ const files = await fg("*.svg", {
2880
+ onlyFiles: true,
2881
+ cwd: dir,
2882
+ absolute: true,
2883
+ suppressErrors: true
2884
+ });
2885
+ const obj = {};
2886
+ await Promise.all(files.map(async (path) => {
2887
+ const num = +basename(path, ".svg");
2888
+ if (Number.isNaN(num)) return;
2889
+ const lines = (await fs$1.readFile(path, "utf8")).split(/\n/g);
2890
+ obj[num.toString()] = lines.slice(1, -1).join("\n");
2891
+ }));
2892
+ return obj;
2893
+ }
2894
+ async function writeDrawings(options, drawing) {
2895
+ const dir = resolveDrawingsDir(options);
2896
+ if (!dir) return;
2897
+ const width = options.data.config.canvasWidth;
2898
+ const SVG_HEAD = `<svg width="${width}" height="${Math.round(width / options.data.config.aspectRatio)}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">`;
2899
+ await fs$1.mkdir(dir, { recursive: true });
2900
+ return Promise.all(Object.entries(drawing).map(async ([key, value]) => {
2901
+ if (!value) return;
2902
+ const svg = `${SVG_HEAD}\n${value}\n</svg>`;
2903
+ await fs$1.writeFile(join(dir, `${key}.svg`), svg, "utf-8");
2904
+ }));
2905
+ }
2906
+ //#endregion
2907
+ //#region node/integrations/snapshots.ts
2908
+ function resolveSnapshotsDir(options) {
2909
+ return resolve(dirname(options.entry), ".slidev/snapshots");
2910
+ }
2911
+ async function loadSnapshots(options) {
2912
+ const dir = resolveSnapshotsDir(options);
2913
+ const file = join(dir, "snapshots.json");
2914
+ if (!dir || !existsSync(file)) return {};
2915
+ return JSON.parse(await fs$1.readFile(file, "utf8"));
2916
+ }
2917
+ async function writeSnapshots(options, data) {
2918
+ const dir = resolveSnapshotsDir(options);
2919
+ if (!dir) return;
2920
+ await fs$1.mkdir(dir, { recursive: true });
2921
+ await fs$1.writeFile(join(dir, "snapshots.json"), JSON.stringify(data, null, 2), "utf-8");
2922
+ }
2923
+ //#endregion
2924
+ //#region node/vite/serverRef.ts
2925
+ const serverRefStates = /* @__PURE__ */ new WeakMap();
2926
+ /**
2927
+ * Access the server-side state shared with the clients via
2928
+ * `vite-plugin-vue-server-ref` (e.g. `nav` holds the current position of the
2929
+ * live presentation).
2930
+ */
2931
+ function getServerRefState(options) {
2932
+ return serverRefStates.get(options);
2933
+ }
2934
+ async function createServerRefPlugin(options, pluginOptions) {
2935
+ const state = {
2936
+ sync: false,
2937
+ nav: {
2938
+ page: 0,
2939
+ clicks: 0,
2940
+ timer: {
2941
+ status: "stopped",
2942
+ slides: {},
2943
+ startedAt: 0,
2944
+ pausedAt: 0
2945
+ }
2946
+ },
2947
+ drawings: await loadDrawings(options),
2948
+ snapshots: await loadSnapshots(options),
2949
+ ...pluginOptions.serverRef?.state
2950
+ };
2951
+ serverRefStates.set(options, state);
2952
+ return ServerRef({
2953
+ debug: false,
2954
+ state,
2955
+ onChanged(key, data, patch, timestamp) {
2956
+ pluginOptions.serverRef?.onChanged?.(key, data, patch, timestamp);
2957
+ if (options.data.config.drawings.persist && key === "drawings") writeDrawings(options, patch ?? data);
2958
+ if (key === "snapshots") writeSnapshots(options, data);
2959
+ }
2960
+ });
2961
+ }
2962
+ const SERVER_REF_WS_EVENT = "vue-server-ref";
2963
+ const SERVER_REF_NAV_MODULE_IDS = ["/@server-reactive/nav", "/@server-ref/nav"];
2964
+ /**
2965
+ * Serves the Slidev MCP (Model Context Protocol) server on the dev server at
2966
+ * `/__mcp` (streamable HTTP transport), so AI agents can inspect, edit, and
2967
+ * navigate the slides. Can be disabled with `mcp: false` in the headmatter.
2968
+ */
2969
+ function createMcpPlugin(options) {
2970
+ return {
2971
+ name: "slidev:mcp",
2972
+ apply: "serve",
2973
+ configureServer(server) {
2974
+ if (options.mode !== "dev") return;
2975
+ const ctx = {
2976
+ version,
2977
+ entry: options.entry,
2978
+ getData: () => options.data,
2979
+ getServerUrl: () => server.resolvedUrls?.local[0],
2980
+ nav: {
2981
+ getState: () => {
2982
+ const nav = getServerRefState(options)?.nav;
2983
+ return {
2984
+ page: nav?.page ?? 0,
2985
+ clicks: nav?.clicks ?? 0
2986
+ };
2987
+ },
2988
+ go: (page, clicks) => navigateClients(server, options, page, clicks)
2989
+ }
2990
+ };
2991
+ server.middlewares.use(async (req, res, next) => {
2992
+ if (req.url?.split("?")[0]?.replace(/\/$/, "") !== "/__mcp") return next();
2993
+ if (options.data.config.mcp === false) {
2994
+ res.statusCode = 403;
2995
+ res.setHeader("Content-Type", "application/json");
2996
+ res.end(JSON.stringify({
2997
+ jsonrpc: "2.0",
2998
+ error: {
2999
+ code: -32e3,
3000
+ message: "The Slidev MCP server is disabled (`mcp: false` in the headmatter)"
3001
+ },
3002
+ id: null
3003
+ }));
3004
+ return;
3005
+ }
3006
+ try {
3007
+ const mcp = createSlidevMcpServer(ctx);
3008
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
3009
+ res.on("close", () => {
3010
+ transport.close();
3011
+ mcp.close();
3012
+ });
3013
+ await mcp.connect(transport);
3014
+ await transport.handleRequest(req, res);
3015
+ } catch (e) {
3016
+ console.error("[slidev] Error handling MCP request:", e);
3017
+ if (!res.headersSent) {
3018
+ res.statusCode = 500;
3019
+ res.setHeader("Content-Type", "application/json");
3020
+ res.end(JSON.stringify({
3021
+ jsonrpc: "2.0",
3022
+ error: {
3023
+ code: -32603,
3024
+ message: "Internal server error"
3025
+ },
3026
+ id: null
3027
+ }));
3028
+ }
3029
+ }
3030
+ });
3031
+ }
3032
+ };
3033
+ }
3034
+ /**
3035
+ * Drive all connected clients to the given slide by patching the shared
3036
+ * `nav` state, mimicking what `vite-plugin-vue-server-ref` does when a
3037
+ * presenter or remote-control client navigates.
3038
+ */
3039
+ function navigateClients(server, options, page, clicks) {
3040
+ const time = Date.now();
3041
+ const nav = getServerRefState(options)?.nav;
3042
+ if (nav) {
3043
+ nav.page = page;
3044
+ nav.clicks = clicks;
3045
+ nav.lastUpdate = {
3046
+ id: "slidev-mcp",
3047
+ type: "presenter",
3048
+ time
3049
+ };
3050
+ }
3051
+ for (const id of SERVER_REF_NAV_MODULE_IDS) {
3052
+ const module = server.moduleGraph.getModuleById(id);
3053
+ if (module) server.moduleGraph.invalidateModule(module);
3054
+ }
3055
+ for (const type of ["presenter", "viewer"]) server.hot.send({
3056
+ type: "custom",
3057
+ event: SERVER_REF_WS_EVENT,
3058
+ data: {
3059
+ key: "nav",
3060
+ source: "slidev-mcp",
3061
+ patch: {
3062
+ page,
3063
+ clicks,
3064
+ lastUpdate: {
3065
+ id: "slidev-mcp",
3066
+ type,
3067
+ time
3068
+ }
3069
+ },
3070
+ timestamp: time
3071
+ }
3072
+ });
3073
+ }
3074
+ //#endregion
2582
3075
  //#region node/vite/monacoTypes.ts
2583
3076
  function createMonacoTypesLoader({ userRoot, utils }) {
2584
3077
  return {
@@ -2648,6 +3141,34 @@ function createPatchMonacoSourceMapPlugin() {
2648
3141
  };
2649
3142
  }
2650
3143
  //#endregion
3144
+ //#region node/vite/pwa.ts
3145
+ function createPWAPlugin(options) {
3146
+ const matchMode = (mode) => mode === true || mode === options.mode;
3147
+ const enabled = matchMode(options.data.config.pwa);
3148
+ const base = options.base ?? "/";
3149
+ const title = options.data.config.title || "Slidev";
3150
+ return VitePWA({
3151
+ disable: !enabled,
3152
+ registerType: "autoUpdate",
3153
+ injectRegister: null,
3154
+ base,
3155
+ workbox: {
3156
+ maximumFileSizeToCacheInBytes: 100 * 1024 * 1024,
3157
+ globPatterns: ["**/*.{js,css,html,woff,woff2,ttf,png,jpg,jpeg,svg,gif,webp,avif,ico,mp4,webm,ogv,mov,m4v,mp3,wav,ogg,oga,m4a,aac,flac,opus,weba}"],
3158
+ navigateFallback: "index.html",
3159
+ navigateFallbackDenylist: [/^\/(presenter|notes|overview|print|export)/]
3160
+ },
3161
+ manifest: {
3162
+ name: title,
3163
+ short_name: "Slidev",
3164
+ start_url: base,
3165
+ display: "fullscreen",
3166
+ theme_color: "#121212",
3167
+ background_color: "#121212"
3168
+ }
3169
+ });
3170
+ }
3171
+ //#endregion
2651
3172
  //#region node/vite/remoteAssets.ts
2652
3173
  async function createRemoteAssetsPlugin({ data: { config }, mode }, pluginOptions) {
2653
3174
  if (!(config.remoteAssets === true || config.remoteAssets === mode)) return;
@@ -2667,86 +3188,6 @@ async function createRemoteAssetsPlugin({ data: { config }, mode }, pluginOption
2667
3188
  });
2668
3189
  }
2669
3190
  //#endregion
2670
- //#region node/integrations/drawings.ts
2671
- function resolveDrawingsDir(options) {
2672
- return options.data.config.drawings.persist ? resolve(dirname(options.entry), options.data.config.drawings.persist) : void 0;
2673
- }
2674
- async function loadDrawings(options) {
2675
- const dir = resolveDrawingsDir(options);
2676
- if (!dir || !existsSync(dir)) return {};
2677
- const files = await fg("*.svg", {
2678
- onlyFiles: true,
2679
- cwd: dir,
2680
- absolute: true,
2681
- suppressErrors: true
2682
- });
2683
- const obj = {};
2684
- await Promise.all(files.map(async (path) => {
2685
- const num = +basename(path, ".svg");
2686
- if (Number.isNaN(num)) return;
2687
- const lines = (await fs$1.readFile(path, "utf8")).split(/\n/g);
2688
- obj[num.toString()] = lines.slice(1, -1).join("\n");
2689
- }));
2690
- return obj;
2691
- }
2692
- async function writeDrawings(options, drawing) {
2693
- const dir = resolveDrawingsDir(options);
2694
- if (!dir) return;
2695
- const width = options.data.config.canvasWidth;
2696
- const SVG_HEAD = `<svg width="${width}" height="${Math.round(width / options.data.config.aspectRatio)}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">`;
2697
- await fs$1.mkdir(dir, { recursive: true });
2698
- return Promise.all(Object.entries(drawing).map(async ([key, value]) => {
2699
- if (!value) return;
2700
- const svg = `${SVG_HEAD}\n${value}\n</svg>`;
2701
- await fs$1.writeFile(join(dir, `${key}.svg`), svg, "utf-8");
2702
- }));
2703
- }
2704
- //#endregion
2705
- //#region node/integrations/snapshots.ts
2706
- function resolveSnapshotsDir(options) {
2707
- return resolve(dirname(options.entry), ".slidev/snapshots");
2708
- }
2709
- async function loadSnapshots(options) {
2710
- const dir = resolveSnapshotsDir(options);
2711
- const file = join(dir, "snapshots.json");
2712
- if (!dir || !existsSync(file)) return {};
2713
- return JSON.parse(await fs$1.readFile(file, "utf8"));
2714
- }
2715
- async function writeSnapshots(options, data) {
2716
- const dir = resolveSnapshotsDir(options);
2717
- if (!dir) return;
2718
- await fs$1.mkdir(dir, { recursive: true });
2719
- await fs$1.writeFile(join(dir, "snapshots.json"), JSON.stringify(data, null, 2), "utf-8");
2720
- }
2721
- //#endregion
2722
- //#region node/vite/serverRef.ts
2723
- async function createServerRefPlugin(options, pluginOptions) {
2724
- return ServerRef({
2725
- debug: false,
2726
- state: {
2727
- sync: false,
2728
- nav: {
2729
- page: 0,
2730
- clicks: 0,
2731
- timer: {
2732
- status: "stopped",
2733
- slides: {},
2734
- startedAt: 0,
2735
- pausedAt: 0
2736
- }
2737
- },
2738
- drawings: await loadDrawings(options),
2739
- snapshots: await loadSnapshots(options),
2740
- ...pluginOptions.serverRef?.state
2741
- },
2742
- onChanged(key, data, patch, timestamp) {
2743
- pluginOptions.serverRef?.onChanged?.(key, data, patch, timestamp);
2744
- if (options.data.config.drawings.persist && key === "drawings") writeDrawings(options, patch ?? data);
2745
- if (key === "snapshots") writeSnapshots(options, data);
2746
- }
2747
- });
2748
- }
2749
- //#endregion
2750
3191
  //#region node/vite/staticCopy.ts
2751
3192
  async function createStaticCopyPlugin({ themeRoots, addonRoots }, pluginOptions) {
2752
3193
  const publicDirs = [...themeRoots, ...addonRoots].map((i) => join(i, "public")).filter(existsSync);
@@ -2805,7 +3246,7 @@ const RE_VUE_QUERY_VUE = /\.vue\?vue/;
2805
3246
  const RE_VUE_QUERY_V = /\.vue\?v=/;
2806
3247
  const RE_MD_EXT = /\.md$/;
2807
3248
  const RE_MD_QUERY_VUE = /\.md\?vue/;
2808
- const customElements = new Set([
3249
+ const customElements = /* @__PURE__ */ new Set([
2809
3250
  "annotation",
2810
3251
  "math",
2811
3252
  "menclose",
@@ -2863,6 +3304,7 @@ async function createVuePlugin(_options, pluginOptions) {
2863
3304
  function ViteSlidevPlugin(options, pluginOptions = {}, serverOptions = {}) {
2864
3305
  return Promise.all([
2865
3306
  createSlidesLoader(options, serverOptions),
3307
+ createMcpPlugin(options),
2866
3308
  createMarkdownPlugin(options, pluginOptions),
2867
3309
  createLayoutWrapperPlugin(options),
2868
3310
  createContextInjectionPlugin(),
@@ -2881,6 +3323,7 @@ function ViteSlidevPlugin(options, pluginOptions = {}, serverOptions = {}) {
2881
3323
  createStaticCopyPlugin(options, pluginOptions),
2882
3324
  createInspectPlugin(options, pluginOptions),
2883
3325
  createPatchMonacoSourceMapPlugin(),
3326
+ createPWAPlugin(options),
2884
3327
  setupVitePlugins(options)
2885
3328
  ]);
2886
3329
  }
@@ -2920,4 +3363,4 @@ async function createServer$1(options, viteConfig = {}, serverOptions) {
2920
3363
  return await createServer(await resolveViteConfigs(options, { optimizeDeps: { entries: [join(options.clientRoot, "main.ts")] } }, viteConfig, "serve", serverOptions));
2921
3364
  }
2922
3365
  //#endregion
2923
- export { resolveOptions as a, resolveTheme as c, updateFrontmatterPatch as d, createDataUtils as i, version as l, resolveViteConfigs as n, parser as o, ViteSlidevPlugin as r, getThemeMeta as s, createServer$1 as t, loadSetups as u };
3366
+ export { createDataUtils as a, getThemeMeta as c, loadSetups as d, updateFrontmatterPatch as f, createSlidevMcpServer as i, resolveTheme as l, resolveViteConfigs as n, resolveOptions as o, ViteSlidevPlugin as r, parser as s, createServer$1 as t, version as u };
@@ -0,0 +1,24 @@
1
+ import { n as getRoots } from "./resolver-BBT539iz.mjs";
2
+ import { i as createSlidevMcpServer, s as parser, u as version } from "./serve-CgOXLPUI.mjs";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ //#region node/mcp/stdio.ts
5
+ /**
6
+ * Start a Slidev MCP server over stdio, operating on the markdown files
7
+ * directly (no dev server required, so no live-navigation tool).
8
+ *
9
+ * Note: in stdio mode, stdout is reserved for the MCP protocol — nothing
10
+ * else may be printed to it.
11
+ */
12
+ async function startMcpStdioServer(entry) {
13
+ const { userRoot } = await getRoots(entry);
14
+ await createSlidevMcpServer({
15
+ version,
16
+ entry,
17
+ getData: () => parser.load({
18
+ userRoot,
19
+ roots: [userRoot]
20
+ }, entry)
21
+ }).connect(new StdioServerTransport());
22
+ }
23
+ //#endregion
24
+ export { startMcpStdioServer };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/cli",
3
3
  "type": "module",
4
- "version": "52.16.0",
4
+ "version": "52.17.0",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -43,21 +43,22 @@
43
43
  }
44
44
  },
45
45
  "dependencies": {
46
- "@antfu/ni": "^30.1.0",
46
+ "@antfu/ni": "^30.2.0",
47
47
  "@antfu/utils": "^9.3.0",
48
48
  "@comark/markdown-it": "^0.3.4",
49
- "@iconify-json/carbon": "^1.2.22",
49
+ "@iconify-json/carbon": "^1.2.24",
50
50
  "@iconify-json/ph": "^1.2.2",
51
51
  "@iconify-json/svg-spinners": "^1.2.4",
52
52
  "@lillallol/outline-pdf": "^4.0.0",
53
- "@shikijs/magic-move": "^4.2.0",
54
- "@shikijs/markdown-it": "^4.2.0",
55
- "@shikijs/twoslash": "^4.2.0",
56
- "@shikijs/vitepress-twoslash": "^4.2.0",
57
- "@unocss/extractor-mdc": "^66.7.0",
58
- "@unocss/reset": "^66.7.0",
53
+ "@modelcontextprotocol/sdk": "^1.29.0",
54
+ "@shikijs/magic-move": "^4.3.1",
55
+ "@shikijs/markdown-it": "^4.3.1",
56
+ "@shikijs/twoslash": "^4.3.1",
57
+ "@shikijs/vitepress-twoslash": "^4.3.1",
58
+ "@unocss/extractor-mdc": "^66.7.5",
59
+ "@unocss/reset": "^66.7.5",
59
60
  "@vitejs/plugin-vue": "^6.0.7",
60
- "@vitejs/plugin-vue-jsx": "^5.1.5",
61
+ "@vitejs/plugin-vue-jsx": "^5.1.6",
61
62
  "ansis": "^4.3.1",
62
63
  "chokidar": "^5.0.0",
63
64
  "cli-progress": "^3.12.0",
@@ -74,15 +75,15 @@
74
75
  "lz-string": "^1.5.0",
75
76
  "magic-string": "^0.30.21",
76
77
  "magic-string-stack": "^1.1.0",
77
- "markdown-exit": "^1.0.0-beta.9",
78
+ "markdown-exit": "^1.1.0-beta.2",
78
79
  "markdown-it-footnote": "^4.0.0",
79
80
  "markdown-it-github-alerts": "^1.0.1",
80
81
  "mlly": "^1.8.2",
81
82
  "monaco-editor": "^0.55.1",
82
- "obug": "^2.1.1",
83
+ "obug": "^2.1.3",
83
84
  "open": "^11.0.0",
84
85
  "pdf-lib": "^1.17.1",
85
- "picomatch": "^4.0.4",
86
+ "picomatch": "^4.0.5",
86
87
  "plantuml-encoder": "^1.4.0",
87
88
  "postcss-nested": "^7.0.2",
88
89
  "pptxgenjs": "^4.0.1",
@@ -90,30 +91,32 @@
90
91
  "public-ip": "^8.0.0",
91
92
  "resolve-from": "^5.0.0",
92
93
  "resolve-global": "^2.0.0",
93
- "semver": "^7.8.1",
94
- "shiki": "^4.2.0",
94
+ "semver": "^7.8.5",
95
+ "shiki": "^4.3.1",
95
96
  "sirv": "^3.0.2",
96
97
  "source-map-js": "^1.2.1",
97
98
  "typescript": "^6.0.3",
98
- "unhead": "^3.1.1",
99
- "unocss": "^66.7.0",
99
+ "unhead": "^3.1.7",
100
+ "unocss": "^66.7.5",
100
101
  "unplugin-icons": "^23.0.1",
101
102
  "unplugin-vue-components": "^32.1.0",
102
103
  "unplugin-vue-markdown": "^32.0.0",
103
104
  "untun": "^0.1.3",
104
105
  "uqr": "^0.1.3",
105
- "vite": "^8.0.16",
106
+ "vite": "^8.1.3",
106
107
  "vite-plugin-inspect": "^11.4.1",
108
+ "vite-plugin-pwa": "^1.3.0",
107
109
  "vite-plugin-remote-assets": "^2.1.0",
108
- "vite-plugin-static-copy": "^4.1.0",
110
+ "vite-plugin-static-copy": "^4.1.1",
109
111
  "vite-plugin-vue-server-ref": "^1.0.0",
110
112
  "vitefu": "^1.1.3",
111
- "vue": "^3.5.35",
113
+ "vue": "^3.5.39",
112
114
  "yaml": "^2.9.0",
113
115
  "yargs": "^18.0.0",
114
- "@slidev/client": "52.16.0",
115
- "@slidev/types": "52.16.0",
116
- "@slidev/parser": "52.16.0"
116
+ "zod": "^3.25.76",
117
+ "@slidev/parser": "52.17.0",
118
+ "@slidev/client": "52.17.0",
119
+ "@slidev/types": "52.17.0"
117
120
  },
118
121
  "devDependencies": {
119
122
  "@hedgedoc/markdown-it-plugins": "^2.1.4",
@@ -158,6 +158,7 @@ Presenter notes go here
158
158
  | VS Code extension | Install `antfu.slidev` | [editor-vscode](references/editor-vscode.md) |
159
159
  | Prettier | `prettier-plugin-slidev` | [editor-prettier](references/editor-prettier.md) |
160
160
  | Eject theme | `slidev theme eject` | [tool-eject-theme](references/tool-eject-theme.md) |
161
+ | MCP server (AI agents) | `http://localhost:<port>/__mcp` or `slidev mcp` | [tool-mcp](references/tool-mcp.md) |
161
162
 
162
163
  ### Lifecycle & API
163
164
 
@@ -93,6 +93,14 @@ slidev format [entry]
93
93
 
94
94
  Formats the slides markdown file.
95
95
 
96
+ ## MCP Server
97
+
98
+ ```bash
99
+ slidev mcp [entry]
100
+ ```
101
+
102
+ Starts an MCP (Model Context Protocol) server over stdio for AI agents to inspect and edit the slides. When a dev server is running, an HTTP MCP endpoint is also available at `http://localhost:<port>/__mcp`. See [tool-mcp](tool-mcp.md).
103
+
96
104
  ## Theme Eject
97
105
 
98
106
  ```bash
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: mcp
3
+ description: Built-in MCP server for AI agents to inspect, edit, reorder, and navigate slides
4
+ ---
5
+
6
+ # MCP Server
7
+
8
+ Slidev ships a built-in MCP (Model Context Protocol) server exposing structured tools to read, edit, reorder, and navigate a deck. Prefer these tools over raw text edits for slide-level operations (update/insert/remove/move) — they handle Slidev's compound separators correctly and hot-reload the presentation.
9
+
10
+ ## Usage
11
+
12
+ With a running dev server (streamable HTTP):
13
+
14
+ ```
15
+ http://localhost:<port>/__mcp
16
+ ```
17
+
18
+ ```bash
19
+ # e.g. for Claude Code
20
+ claude mcp add --transport http slidev http://localhost:3030/__mcp
21
+ ```
22
+
23
+ Standalone over stdio (no dev server, operates on files directly):
24
+
25
+ ```bash
26
+ slidev mcp [entry]
27
+ ```
28
+
29
+ ## Tools
30
+
31
+ | Tool | Description |
32
+ | --- | --- |
33
+ | `slidev-get-info` | Deck overview: entry, title, slide count, markdown files, server URL, current position |
34
+ | `slidev-list-slides` | All slides with number, title, layout, source file |
35
+ | `slidev-get-slide` | Full source of one slide: frontmatter, content, note |
36
+ | `slidev-update-slide` | Update content, note, and/or frontmatter of a slide |
37
+ | `slidev-insert-slide` | Insert a new slide after an existing one |
38
+ | `slidev-remove-slide` | Remove a slide |
39
+ | `slidev-move-slide` | Move a slide before/after another to reorder the deck |
40
+ | `slidev-goto-slide` | Navigate the live presentation to a slide (dev server only) |
41
+
42
+ ## Behavior
43
+
44
+ - Slides are addressed by rendered 1-based numbers (as shown in the presentation)
45
+ - Edits are saved to the markdown files; a running dev server hot-reloads instantly
46
+ - The first slide of the entry file cannot be removed/moved (its frontmatter is the deck headmatter)
47
+ - Slides imported via `src:` are edited in their own file; moves cannot cross files
48
+ - Disable with `mcp: false` in the headmatter