diff --git a/components/chat/lobby-hero.tsx b/components/chat/lobby-hero.tsx
index 2a4e863f..998d39c6 100644
--- a/components/chat/lobby-hero.tsx
+++ b/components/chat/lobby-hero.tsx
@@ -5,6 +5,7 @@ import { getAssetUrl } from "@/lib/base-path"
import { cn } from "@/lib/utils"
import { useChatEngine } from "./chat-engine"
import { Composer } from "./composer"
+import { McpNotice } from "./mcp-notice"
import { SessionMenu } from "./session-menu"
interface Example {
@@ -78,6 +79,7 @@ export function LobbyHero({
const dict = useDictionary()
const t = dict.lobby
const engine = useChatEngine()
+ const isSelfHosted = process.env.NEXT_PUBLIC_SELFHOSTED === "true"
// Prompts and file names must stay as they are: cached responses match them
const examples: Example[] = [
@@ -173,6 +175,15 @@ export function LobbyHero({
{t.lede}
+ {/* MCP server notice, hidden on self-hosted deployments. The lede's
+ margin already spans 24 px (20 px compact); pull the card up so
+ 16 px remain above it */}
+ {!isSelfHosted && (
+
+ )}
+
{
+ setClosed(localStorage.getItem(STORAGE_KEYS.mcpVideoClosed) === "true")
+ }, [])
+ const close = () => {
+ localStorage.setItem(STORAGE_KEYS.mcpVideoClosed, "true")
+ setClosed(true)
+ }
+ return [closed, close] as const
+}
+
+function PromoVideo({ className }: { className?: string }) {
+ return (
+
+ )
+}
+
+function CloseButton({ onClick }: { onClick: () => void }) {
+ const dict = useDictionary()
+ return (
+
+ )
+}
+
+/** Icon, title and tagline of the MCP server, linking to its README */
+function NoticeLink({ className }: { className?: string }) {
+ const dict = useDictionary()
+ return (
+
+
+
+
+
+
+ {dict.examples.mcpServer}
+
+
+ {dict.examples.mcpTagline}
+
+
+
+
+ )
+}
+
+const cardClass =
+ "w-full rounded-xl border border-purple-500/20 bg-gradient-to-r from-purple-500/10 to-blue-500/10"
+
+/**
+ * MCP server notice on the start screen. Until the user closes it, the card
+ * also plays the promo video; after that it is the plain card for good.
+ */
+export function McpNotice({ className }: { className?: string }) {
+ const [closed, close] = useVideoClosed()
+ if (closed) {
+ return (
+
+
+
+ )
+ }
+ return (
+
+ )
+}
diff --git a/lib/i18n/dictionaries/en.json b/lib/i18n/dictionaries/en.json
index 57d2700a..f05c6d2b 100644
--- a/lib/i18n/dictionaries/en.json
+++ b/lib/i18n/dictionaries/en.json
@@ -51,7 +51,8 @@
"creativeDescription": "Draw something fun and creative",
"cachedNote": "Examples use saved answers: they appear instantly and don't call the AI.",
"mcpServer": "MCP Server",
- "mcpDescription": "Use in Claude Desktop, VS Code & Cursor"
+ "mcpDescription": "Use in Claude Desktop, VS Code & Cursor",
+ "mcpTagline": "Let Claude Code, Cursor or VS Code draw from your local files and code. You get the same canvas as here: live preview, outlined changes, undo."
},
"settings": {
"title": "Settings",
diff --git a/lib/i18n/dictionaries/ja.json b/lib/i18n/dictionaries/ja.json
index 8eff38d9..7b1f32f4 100644
--- a/lib/i18n/dictionaries/ja.json
+++ b/lib/i18n/dictionaries/ja.json
@@ -51,7 +51,8 @@
"creativeDescription": "楽しくてクリエイティブなものを描く",
"cachedNote": "例は保存済みの回答を使うので、すぐに表示され、AI を呼び出しません。",
"mcpServer": "MCP サーバー",
- "mcpDescription": "Claude Desktop、VS Code、Cursor で使用"
+ "mcpDescription": "Claude Desktop、VS Code、Cursor で使用",
+ "mcpTagline": "Claude Code、Cursor、VS Code がローカルのファイルやコードを読んで図を描きます。キャンバスはここと同じで、リアルタイム表示、変更のハイライト、取り消しができます。"
},
"settings": {
"title": "設定",
diff --git a/lib/i18n/dictionaries/zh-Hant.json b/lib/i18n/dictionaries/zh-Hant.json
index 6f151cba..92a63d03 100644
--- a/lib/i18n/dictionaries/zh-Hant.json
+++ b/lib/i18n/dictionaries/zh-Hant.json
@@ -51,7 +51,8 @@
"creativeDescription": "繪製有趣且富有創意的內容",
"cachedNote": "範例使用保存好的答案,立即顯示,不呼叫 AI。",
"mcpServer": "MCP 伺服器",
- "mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用"
+ "mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用",
+ "mcpTagline": "讓 Claude Code、Cursor 或 VS Code 直接讀取本機檔案和程式碼來繪圖。畫布和這裡一樣:即時預覽、標示改動、一鍵復原。"
},
"settings": {
"title": "設定",
diff --git a/lib/i18n/dictionaries/zh.json b/lib/i18n/dictionaries/zh.json
index 42e9385e..a3798f6c 100644
--- a/lib/i18n/dictionaries/zh.json
+++ b/lib/i18n/dictionaries/zh.json
@@ -51,7 +51,8 @@
"creativeDescription": "绘制有趣且富有创意的内容",
"cachedNote": "示例使用保存好的答案,立即显示,不调用 AI。",
"mcpServer": "MCP 服务器",
- "mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用"
+ "mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用",
+ "mcpTagline": "让 Claude Code、Cursor 或 VS Code 直接读取本地文件和代码来画图。画布和这里一样:实时预览、标出改动、一键撤销。"
},
"settings": {
"title": "设置",
diff --git a/lib/storage.ts b/lib/storage.ts
index 524eec53..fbf6ca44 100644
--- a/lib/storage.ts
+++ b/lib/storage.ts
@@ -29,4 +29,7 @@ export const STORAGE_KEYS = {
// Langfuse session id
sessionId: "next-ai-draw-io-session-id",
+
+ // The start screen's MCP video was closed (it does not come back)
+ mcpVideoClosed: "next-ai-draw-io-mcp-video-closed",
} as const
diff --git a/public/mcp-promo-poster.jpg b/public/mcp-promo-poster.jpg
new file mode 100644
index 00000000..542e1e46
Binary files /dev/null and b/public/mcp-promo-poster.jpg differ
diff --git a/public/mcp-promo.mp4 b/public/mcp-promo.mp4
new file mode 100644
index 00000000..e32cc50e
Binary files /dev/null and b/public/mcp-promo.mp4 differ
diff --git a/tests/e2e/workspace.spec.ts b/tests/e2e/workspace.spec.ts
index 13f1838a..262cc7cd 100644
--- a/tests/e2e/workspace.spec.ts
+++ b/tests/e2e/workspace.spec.ts
@@ -170,6 +170,20 @@ test.describe("Workspace", () => {
await expect(getChatInput(page)).toHaveValue("Draw a cat for me")
})
+ test("the MCP card's video stays away once closed", async ({ page }) => {
+ await openApp(page)
+ const card = page.locator('[data-testid="mcp-notice"]')
+ const video = page.locator('[data-testid="mcp-video"]')
+ await expect(card.locator('[data-testid="mcp-video"]')).toBeVisible()
+ await page.locator('[data-testid="mcp-video-close"]').click()
+ await expect(video).toHaveCount(0)
+ await expect(card).toBeVisible()
+ await page.reload({ waitUntil: "networkidle" })
+ await expect(page.locator('[data-testid="lobby-hero"]')).toBeVisible()
+ await expect(card).toBeVisible()
+ await expect(video).toHaveCount(0)
+ })
+
test("each AI change becomes a version card; the latest undoes and redoes it", async ({
page,
}) => {