Skip to content

Add Slides MCP - AI-powered presentation builder#154

Open
vibegui wants to merge 20 commits intomainfrom
feat/slides-mcp
Open

Add Slides MCP - AI-powered presentation builder#154
vibegui wants to merge 20 commits intomainfrom
feat/slides-mcp

Conversation

@vibegui
Copy link
Contributor

@vibegui vibegui commented Jan 22, 2026

Browser-based presentation system with real JSX (via Babel Standalone), no build step.

Architecture:

  • brands/ folder: Reusable design systems (source of truth)
  • decks/ folder: Presentations that reference brands

Tools:

  • DECK_INIT: Create brand or deck (with brand reference)
  • DECK_BUNDLE: Bundle deck into standalone HTML
  • SLIDE_CREATE/UPDATE/DELETE/REORDER: Manage slides

Prompts:

  • SLIDES_SETUP_BRAND: Create new brand design system
  • SLIDES_NEW_DECK: Create deck from existing brand
  • SLIDES_ADD_CONTENT: Add slides to deck
  • SLIDES_QUICK_START: Fast one-off presentations

Features:

  • CDN-based: React 18, GSAP, Babel Standalone
  • JSX components without build step
  • Design system viewer at /design.html
  • Keyboard navigation (arrows, F for fullscreen)
  • Bundling for portable single-file presentations

Summary by cubic

Adds Slides MCP, a browser-based presentation builder with CDN React + GSAP and no build step, and introduces Brand MCP for brand research and design system generation. Slides integrates with Brand MCP via a binding for brand-aware decks and includes MCP Apps UIs for interactive previews.

  • New Features

    • Slides workspace with brand/deck architecture (brands as design systems, decks referencing brands).
    • Tools: DECK_INIT, DECK_INFO, DECK_BUNDLE, SLIDES_PREVIEW, SLIDE_CREATE/UPDATE/DELETE/REORDER, STYLE_GET/UPDATE; Brand integration via BRAND_CREATE binding.
    • Prompts: SLIDES_SETUP_BRAND, SLIDES_NEW_DECK, SLIDES_ADD_CONTENT, SLIDES_QUICK_START.
    • MCP Apps resources: slides viewer, design system preview, single-slide preview; Brand preview and brand list; example deck in slides/test-presentation.
  • Migration

    • Start servers: bun run --hot server/main.ts from slides/ and brand/.
    • Use Brand MCP (BRAND_CREATE) to create a brand, then init a deck with DECK_INIT; add content via SLIDES_* prompts; export with DECK_BUNDLE.
    • Optionally configure Perplexity and Firecrawl bindings in Brand MCP for research.
    • Root package.json and bun.lock updated to include slides and brand workspaces.

Written for commit 7a4ad92. Summary will update on new commits.

Browser-based presentation system with real JSX (via Babel Standalone), no build step.

Architecture:
- brands/ folder: Reusable design systems (source of truth)
- decks/ folder: Presentations that reference brands

Tools:
- DECK_INIT: Create brand or deck (with brand reference)
- DECK_BUNDLE: Bundle deck into standalone HTML
- SLIDE_CREATE/UPDATE/DELETE/REORDER: Manage slides

Prompts:
- SLIDES_SETUP_BRAND: Create new brand design system
- SLIDES_NEW_DECK: Create deck from existing brand
- SLIDES_ADD_CONTENT: Add slides to deck
- SLIDES_QUICK_START: Fast one-off presentations

Features:
- CDN-based: React 18, GSAP, Babel Standalone
- JSX components without build step
- Design system viewer at /design.html
- Keyboard navigation (arrows, F for fullscreen)
- Bundling for portable single-file presentations
@github-actions
Copy link

github-actions bot commented Jan 22, 2026

🚀 Preview Deployments Ready!

Your changes have been deployed to preview environments:

📦 slides

🔗 View Preview

These previews will be automatically updated with new commits to this PR.


Deployed from commit: 26d6874

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 27 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="slides/server/main.ts">

<violation number="1" location="slides/server/main.ts:63">
P2: Guard the `pbcopy` clipboard command so the server doesn't crash on non-macOS environments.</violation>
</file>

<file name="slides/test-presentation/engine.js">

<violation number="1" location="slides/test-presentation/engine.js:828">
P1: Rendering `slide.customHtml` with `dangerouslySetInnerHTML` allows XSS if slide JSON content is user-provided. Sanitize or restrict HTML before injecting into the DOM.</violation>
</file>

<file name="slides/server/prompts.ts">

<violation number="1" location="slides/server/prompts.ts:214">
P1: Sanitize `deckName` before building `deckPath`. Right now `deckName` is used verbatim, allowing path traversal or unsafe shell interpolation in the generated command snippets.</violation>
</file>

<file name="slides/server/tools/slides.ts">

<violation number="1" location="slides/server/tools/slides.ts:151">
P1: Generating the slide number from array length can reuse an existing filename after deletions or gaps, causing filename collisions in the manifest. Derive the next number from the max existing numeric prefix instead.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const renderCustomSlide = (slide, bgClass, textColorClass) =>
React.createElement("div", {
className: `w-full h-full ${bgClass} ${textColorClass}`,
dangerouslySetInnerHTML: { __html: slide.customHtml || "" },
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rendering slide.customHtml with dangerouslySetInnerHTML allows XSS if slide JSON content is user-provided. Sanitize or restrict HTML before injecting into the DOM.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At slides/test-presentation/engine.js, line 828:

<comment>Rendering `slide.customHtml` with `dangerouslySetInnerHTML` allows XSS if slide JSON content is user-provided. Sanitize or restrict HTML before injecting into the DOM.</comment>

<file context>
@@ -0,0 +1,1154 @@
+  const renderCustomSlide = (slide, bgClass, textColorClass) =>
+    React.createElement("div", {
+      className: `w-full h-full ${bgClass} ${textColorClass}`,
+      dangerouslySetInnerHTML: { __html: slide.customHtml || "" },
+    });
+
</file context>
Fix with Cubic


// Generate slide ID and filename
const slideIndex = manifest.slides?.length || 0;
const slideNum = String(slideIndex + 1).padStart(3, "0");
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Generating the slide number from array length can reuse an existing filename after deletions or gaps, causing filename collisions in the manifest. Derive the next number from the max existing numeric prefix instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At slides/server/tools/slides.ts, line 151:

<comment>Generating the slide number from array length can reuse an existing filename after deletions or gaps, causing filename collisions in the manifest. Derive the next number from the max existing numeric prefix instead.</comment>

<file context>
@@ -0,0 +1,540 @@
+
+      // Generate slide ID and filename
+      const slideIndex = manifest.slides?.length || 0;
+      const slideNum = String(slideIndex + 1).padStart(3, "0");
+      const slideId = `slide-${slideNum}-${Date.now()}`;
+      const slugTitle = slideData.title
</file context>
Fix with Cubic

- Add optional Perplexity and Firecrawl bindings for automatic brand research
- Implement MCP Apps resources for interactive slide previews:
  - ui://slides-viewer - Full presentation viewer with navigation
  - ui://design-system - Brand design system preview
  - ui://slide - Single slide preview
- Add BRAND_RESEARCH and BRAND_RESEARCH_STATUS tools
- Add SLIDES_PREVIEW tool for multi-slide preview
- Support image-based logos (primary, light, dark variants)
- Add comprehensive README with workflow documentation
- Fix runtime version to 1.2.0 (1.2.2 has SSE response bug)
A testbed MCP server for testing MCP Apps (SEP-1865) in Mesh.
Includes 10 elegant, responsive UI widgets that demonstrate
the MCP Apps specification with adaptive layouts.

Widgets: counter, metric, progress, greeting, chart, timer,
status, quote, sparkline, code snippet.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 19 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="mcp-apps-testbed/README.md">

<violation number="1" location="mcp-apps-testbed/README.md:133">
P3: The README example references `ui://counter`, but the actual resource URI is `ui://counter-app`. This will mislead users and fail if they copy the sample.</violation>
</file>

<file name="slides/server/tools/deck.ts">

<violation number="1" location="slides/server/tools/deck.ts:140">
P2: Escape brand/asset values before embedding them into the generated JSX. As written, unescaped quotes or backslashes in brandName/tagline/logo URLs can break design-system.jsx and prevent decks from loading.</violation>
</file>

<file name="mcp-apps-testbed/server/lib/resources.ts">

<violation number="1" location="mcp-apps-testbed/server/lib/resources.ts:529">
P2: Progress widget’s demo interval keeps overwriting initialized values. Stop the demo timer once real input is received, or only run it when no `toolInput` is provided.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +140 to +146
name: "${brandName}",
tagline: "${tagline}",
logos: {
primary: "${primaryLogo}",
light: "${lightLogo}", // For dark backgrounds
dark: "${darkLogo}", // For light backgrounds
icon: "${icon}", // Square icon
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Escape brand/asset values before embedding them into the generated JSX. As written, unescaped quotes or backslashes in brandName/tagline/logo URLs can break design-system.jsx and prevent decks from loading.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At slides/server/tools/deck.ts, line 140:

<comment>Escape brand/asset values before embedding them into the generated JSX. As written, unescaped quotes or backslashes in brandName/tagline/logo URLs can break design-system.jsx and prevent decks from loading.</comment>

<file context>
@@ -92,25 +92,118 @@ Edit \`design-system.jsx\` to customize components. Key components:
 
-  function BrandLogo({ size = "normal", className = "" }) {
+  const BRAND = {
+    name: "${brandName}",
+    tagline: "${tagline}",
+    logos: {
</file context>
Suggested change
name: "${brandName}",
tagline: "${tagline}",
logos: {
primary: "${primaryLogo}",
light: "${lightLogo}", // For dark backgrounds
dark: "${darkLogo}", // For light backgrounds
icon: "${icon}", // Square icon
name: ${JSON.stringify(brandName)},
tagline: ${JSON.stringify(tagline)},
logos: {
primary: ${JSON.stringify(primaryLogo)},
light: ${JSON.stringify(lightLogo)}, // For dark backgrounds
dark: ${JSON.stringify(darkLogo)}, // For light backgrounds
icon: ${JSON.stringify(icon)}, // Square icon
},
Fix with Cubic

…ator

A complete toolset for brand discovery and design system creation:

## Tools
- BRAND_SCRAPE - Extract brand identity from websites using Firecrawl
- BRAND_RESEARCH - Deep research using Perplexity AI
- BRAND_DISCOVER - Combined scraping + research for complete identity
- BRAND_STATUS - Check available research capabilities
- BRAND_GENERATE - Generate CSS, JSX, and style guide from identity
- BRAND_CREATE - Full workflow: discover + generate in one step

## MCP Apps UIs
- ui://brand-preview - Interactive brand identity preview
- ui://brand-list - Grid view of created brands

## Output Formats
- CSS variables file with full color palette
- JSX design system with brand components
- Markdown style guide with complete documentation

## Dependencies
- Requires FIRECRAWL and/or PERPLEXITY bindings
- Uses @decocms/runtime 1.2.0 (same as slides)
- Add detailed startup logs with tool and resource counts
- Log incoming requests (tool calls, MCP methods)
- Log response times for slow requests
- Display available tools and MCP Apps resources
- Copy MCP URL to clipboard on startup
- Set default port to 8003
- Add detailed startup logs with tool, prompt, and resource counts
- Log incoming requests (tool calls, MCP methods)
- Log response times for slow requests
- Display available tools and MCP Apps resources
- Copy MCP URL to clipboard on startup
The bindings now include __binding property with tool definitions,
allowing Mesh admin to correctly match connections by tool names:

- PERPLEXITY: Matches perplexity_research, perplexity_ask, perplexity_search
- FIRECRAWL: Matches firecrawl_scrape, firecrawl_crawl, firecrawl_search

This fixes the "No connections found" issue in the Mesh admin
binding selectors.
- Remove brand-research.ts and BRAND_RESEARCH/BRAND_RESEARCH_STATUS tools
- Remove PERPLEXITY and FIRECRAWL direct bindings
- Add BRAND binding to connect to Brand MCP
- Brand research is now handled by the separate Brand MCP

Slides MCP now focuses on presentation creation while Brand MCP
handles all brand discovery and design system generation.

To use brand research with Slides:
1. Install Brand MCP
2. Configure the BRAND binding in Slides settings
3. Use Brand MCP tools to discover brand, then pass to Slides
Brand MCP:
- Rename FIRECRAWL binding to SCRAPER (matches content-scraper MCP)
- Update Perplexity binding to use ASK/CHAT tool names (not perplexity_*)
- Update all tool references from firecrawl to scraper

Slides MCP:
- Update Brand binding schema with correct BRAND_* tool names

The __binding property now contains the actual tool names and schemas
that the Mesh admin uses to match available connections.
Brand MCP:
- PERPLEXITY binding: ASK tool with prompt (required)
- SCRAPER binding: scrape_content with empty input

Slides MCP:
- BRAND binding: BRAND_CREATE with brandName and websiteUrl (both required)

The schemas now exactly match what the target MCPs expose.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 23 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="brand/server/types/env.ts">

<violation number="1" location="brand/server/types/env.ts:34">
P2: The SCRAPER binding is defined for @deco/content-scraper with an empty input schema, but brand tools call scrape_content with url/formats. The content-scraper implementation ignores input, so the requested URL won’t be used and scraping will fail or target the wrong data. Use a scraper binding that accepts url/formats (e.g., Firecrawl) or update the binding schema/tool to accept those inputs.</violation>
</file>

<file name="brand/server/tools/generator-utils.ts">

<violation number="1" location="brand/server/tools/generator-utils.ts:111">
P2: Escape or serialize brand identity strings before embedding them in the generated JSX. Unescaped quotes/backticks in name or tagline will break the generated design system output.</violation>
</file>

<file name="brand/server/main.ts">

<violation number="1" location="brand/server/main.ts:26">
P2: Default port doesn�t match the documented Brand MCP port (8001). This will make the quick start instructions incorrect unless PORT is set.</violation>
</file>

<file name="brand/server/tools/generator.ts">

<violation number="1" location="brand/server/tools/generator.ts:97">
P2: Unescaped brand fields in the generated JSX can break the output or allow injected content when names/taglines/logos contain quotes or newlines. Escape interpolated values (e.g., JSON.stringify) before embedding them in string literals.</violation>
</file>

<file name="brand/server/tools/research.ts">

<violation number="1" location="brand/server/tools/research.ts:434">
P2: createBrandDiscoverTool can return a BrandIdentity without required colors.primary, which violates the schema and can cause runtime validation or consumer failures when no colors are extracted. Initialize a default colors object to satisfy the schema.</violation>
</file>

<file name="slides/server/tools/index.ts">

<violation number="1" location="slides/server/tools/index.ts:21">
P2: BRAND_RESEARCH tools are removed from the tool registry but prompts still require them, so the SLIDES_SETUP_BRAND workflow will instruct calls to tools that no longer exist.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

*/

const BRAND = {
name: "${identity.name}",
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Escape or serialize brand identity strings before embedding them in the generated JSX. Unescaped quotes/backticks in name or tagline will break the generated design system output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At brand/server/tools/generator-utils.ts, line 111:

<comment>Escape or serialize brand identity strings before embedding them in the generated JSX. Unescaped quotes/backticks in name or tagline will break the generated design system output.</comment>

<file context>
@@ -0,0 +1,266 @@
+ */
+
+const BRAND = {
+  name: "${identity.name}",
+  tagline: "${identity.tagline || ""}",
+  
</file context>
Fix with Cubic


export { StateSchema };

const PORT = process.env.PORT || 8003;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Default port doesn�t match the documented Brand MCP port (8001). This will make the quick start instructions incorrect unless PORT is set.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At brand/server/main.ts, line 26:

<comment>Default port doesn�t match the documented Brand MCP port (8001). This will make the quick start instructions incorrect unless PORT is set.</comment>

<file context>
@@ -0,0 +1,119 @@
+
+export { StateSchema };
+
+const PORT = process.env.PORT || 8003;
+
+console.log("[brand-mcp] Starting server...");
</file context>
Fix with Cubic


// Brand configuration
const BRAND = {
name: "${identity.name}",
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Unescaped brand fields in the generated JSX can break the output or allow injected content when names/taglines/logos contain quotes or newlines. Escape interpolated values (e.g., JSON.stringify) before embedding them in string literals.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At brand/server/tools/generator.ts, line 97:

<comment>Unescaped brand fields in the generated JSX can break the output or allow injected content when names/taglines/logos contain quotes or newlines. Escape interpolated values (e.g., JSON.stringify) before embedding them in string literals.</comment>

<file context>
@@ -0,0 +1,816 @@
+
+// Brand configuration
+const BRAND = {
+  name: "${identity.name}",
+  tagline: "${identity.tagline || ""}",
+  
</file context>
Fix with Cubic

};
}

const identity: Partial<BrandIdentity> = {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: createBrandDiscoverTool can return a BrandIdentity without required colors.primary, which violates the schema and can cause runtime validation or consumer failures when no colors are extracted. Initialize a default colors object to satisfy the schema.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At brand/server/tools/research.ts, line 434:

<comment>createBrandDiscoverTool can return a BrandIdentity without required colors.primary, which violates the schema and can cause runtime validation or consumer failures when no colors are extracted. Initialize a default colors object to satisfy the schema.</comment>

<file context>
@@ -0,0 +1,663 @@
+        };
+      }
+
+      const identity: Partial<BrandIdentity> = {
+        name: brandName,
+        sources: [],
</file context>
Fix with Cubic

* Each factory takes env and returns a tool definition.
* The runtime will call these with the environment.
*/
export const tools = [...deckTools, ...styleTools, ...slideTools];
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: BRAND_RESEARCH tools are removed from the tool registry but prompts still require them, so the SLIDES_SETUP_BRAND workflow will instruct calls to tools that no longer exist.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At slides/server/tools/index.ts, line 21:

<comment>BRAND_RESEARCH tools are removed from the tool registry but prompts still require them, so the SLIDES_SETUP_BRAND workflow will instruct calls to tools that no longer exist.</comment>

<file context>
@@ -5,27 +5,22 @@
-  ...slideTools,
-  ...brandResearchTools,
-];
+export const tools = [...deckTools, ...styleTools, ...slideTools];
 
 // Re-export individual tool modules for direct access
</file context>
Fix with Cubic

- Changed from ASK to perplexity_research (actual tool name)
- Added perplexity_ask as second element so Zod serializes as enum array
- Input schema now matches actual tool: messages array required
Now matches both:
- Official @perplexity-ai/mcp-server (STDIO)
- Custom HTTP implementations

Just checks for tool existence (perplexity_ask, perplexity_research),
not strict input schema validation.
- Changed __type from "@deco/perplexity" to "binding"
- Bindings now match ANY MCP with the required tools
- No longer tied to specific MCP implementations

Brand expects: perplexity_ask, perplexity_research, scrape_content/firecrawl_scrape
Slides expects: BRAND_CREATE, BRAND_DISCOVER
- Define Registry interface with tool signatures for each binding type
- Use BindingOf<Registry, "@deco/..."> for type-safe bindings
- Follows same pattern as mcp-studio

Brand Registry:
- @deco/perplexity: perplexity_ask, perplexity_research (opt)
- @deco/scraper: scrape_content (opt), firecrawl_scrape (opt)

Slides Registry:
- @deco/brand: BRAND_CREATE, BRAND_DISCOVER (opt)
Adds perplexity binding matching official @perplexity-ai/mcp-server:
- perplexity_ask: messages array input
- perplexity_research: messages array input (opt)
- perplexity_reason: messages array input (opt)
Updated Brand MCP to use Firecrawl binding:
- Renamed SCRAPER to FIRECRAWL in StateSchema
- Updated scopes from SCRAPER::* to FIRECRAWL::*
- Changed tool calls from scrape_content to firecrawl_scrape
- Updated all messages and documentation

Also added @deco/firecrawl to shared registry.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant