-
Notifications
You must be signed in to change notification settings - Fork 0
Add two MCP focused Remotion videos: client setup and agent in task #167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8a7955a
Add two MCP focused Remotion videos: client setup and agent in task
b9196dc
Drop the read write caption from the tools scene
12b91ba
Rewrite the setup video hook as a direct imperative
63871cd
Rework the agent video: light stage, one continuous session, typewriter
e951f55
Add a spot cut of the agent video as its own composition
6fa55f9
Open the card into the skill file and give the skill five instructions
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| /** | ||
| * Every string the two MCP videos put on screen. | ||
| * | ||
| * The facts come out of this repository rather than out of a script: the config | ||
| * block is `plugin/mcp.json` verbatim, the endpoint is the one | ||
| * `getMcpResource()` returns in production, the scopes are the ones | ||
| * `app/api/[transport]/route.ts` checks before a write, and every tool name is | ||
| * registered in that route and listed on `/connect`. Only the example team, the | ||
| * teammate, and the example skill are invented, and none of them carries a | ||
| * product claim. | ||
| */ | ||
|
|
||
| export const brand = { | ||
| wordmark: "Skills Board", | ||
| /** The category phrase, verbatim, as the marketing context pins it. */ | ||
| category: "the agent-native skills registry for teams", | ||
| domain: "www.skillsboard.sh", | ||
| licence: "Free forever. MIT.", | ||
| } as const; | ||
|
|
||
| /** `plugin/mcp.json`, line for line. */ | ||
| export const configLines = [ | ||
| "{", | ||
| ' "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",', | ||
| ' "mcpServers": {', | ||
| ' "skills-board": {', | ||
| ' "type": "streamable-http",', | ||
| ' "url": "https://www.skillsboard.sh/api/mcp"', | ||
| " }", | ||
| " }", | ||
| "}", | ||
| ] as const; | ||
|
|
||
| /** The line the highlight lands on once the block is on screen. */ | ||
| export const CONFIG_URL_LINE = 5; | ||
|
|
||
| export const setup = { | ||
| hook: { | ||
| lines: [ | ||
| ["Give", "your", "agent"], | ||
| ["your", "team's", "AI", "skills."], | ||
| ], | ||
| accentWord: "teams", | ||
| sub: "Paste the config. Approve access.", | ||
| }, | ||
| config: { | ||
| step: "Step 1", | ||
| heading: "Add the MCP server to your client", | ||
| file: "mcp.json", | ||
| caption: "Streamable HTTP, and no API key to copy.", | ||
| }, | ||
| consent: { | ||
| step: "Step 2", | ||
| heading: "Approve access in the browser", | ||
| client: "Claude Code", | ||
| title: "Claude Code wants to reach your team libraries", | ||
| scopes: [ | ||
| { name: "skills:read", detail: "List and search team skills and collections" }, | ||
| { name: "skills:write", detail: "Save skills and organize collections" }, | ||
| ], | ||
| approve: "Approve", | ||
| granted: "Access granted", | ||
| footer: "You sign in through your browser.", | ||
| }, | ||
| tools: { | ||
| step: "Done", | ||
| heading: "13 tools in your client", | ||
| writeBadge: "write", | ||
| }, | ||
| } as const; | ||
|
|
||
| /** The 13 tools `app/api/[transport]/route.ts` registers, in registration order. */ | ||
| export const tools = [ | ||
| { name: "list_skills", write: false }, | ||
| { name: "search_skills", write: false }, | ||
| { name: "get_skill_command", write: false }, | ||
| { name: "discover_skills", write: false }, | ||
| { name: "discover_repository_skills", write: false }, | ||
| { name: "add_skill", write: true }, | ||
| { name: "list_collections", write: false }, | ||
| { name: "search_collections", write: false }, | ||
| { name: "get_collection_skills", write: false }, | ||
| { name: "get_collection_install_command", write: false }, | ||
| { name: "create_collection", write: true }, | ||
| { name: "add_skill_to_collection", write: true }, | ||
| { name: "remove_skill_from_collection", write: true }, | ||
| ] as const; | ||
|
|
||
| /** | ||
| * The example skill, its team, and the teammate who saved it are invented. The | ||
| * tool name, its argument shape, and the endpoint printed in the client header | ||
| * are the real ones. | ||
| */ | ||
| export const agentRun = { | ||
| client: "Claude Code", | ||
| server: "skills-board", | ||
| endpoint: "www.skillsboard.sh/api/mcp", | ||
| prompt: "draft the release notes for v2.4 the way our team does them", | ||
| call: 'search_skills({ query: "release notes" })', | ||
| hit: { | ||
| source: "github.com/northwind/agent-skills", | ||
| title: "release-notes", | ||
| description: "Turn merged pull requests into release notes grouped by user impact.", | ||
| tags: ["release", "docs"], | ||
| savedBy: "Marta R.", | ||
| team: "Northwind", | ||
| }, | ||
| note: "Group by user impact. Link the pull request on every entry.", | ||
| applyLine: "Skills Board carries the note your teammate saved.", | ||
| output: { | ||
| title: "## v2.4", | ||
| rows: [ | ||
| "Faster team library search · #1841", | ||
| "Collections in the sidebar · #1836", | ||
| "ZIP download fixed on Safari · #1852", | ||
| ], | ||
| done: "release notes drafted", | ||
| }, | ||
| reach: { | ||
| line: "The same saved skill, from every MCP client.", | ||
| clients: ["Claude Code", "Claude Desktop", "Cursor", "VS Code"], | ||
| }, | ||
| } as const; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import { AbsoluteFill } from "remotion"; | ||
| import { linearTiming, TransitionSeries } from "@remotion/transitions"; | ||
| import { fade } from "@remotion/transitions/fade"; | ||
| import { focusPull } from "@/components/remocn/focus-pull"; | ||
| import { pushThrough } from "@/components/remocn/push-through"; | ||
| import { zoomBlur } from "@/components/remocn/zoom-blur"; | ||
| import "../product-demo/fonts"; | ||
| import { light } from "../product-demo/theme"; | ||
| import { ConfigScene } from "./scenes/config"; | ||
| import { ConsentScene } from "./scenes/consent"; | ||
| import { EndCardScene } from "./scenes/end-card"; | ||
| import { HookScene } from "./scenes/hook"; | ||
| import { ReachScene } from "./scenes/reach"; | ||
| import { SessionScene } from "./scenes/session"; | ||
| import { ToolsScene } from "./scenes/tools"; | ||
|
|
||
| /** The thirty second cut of the agent video, built as a spot. */ | ||
| export { | ||
| MCP_AGENT_SPOT_DURATION, | ||
| MCP_AGENT_SPOT_POSTER_FRAME, | ||
| SkillsboardMcpAgentSpot, | ||
| } from "./spot"; | ||
|
|
||
| export const MCP_FPS = 30; | ||
|
|
||
| /** 1110 frames of scene, 88 of overlap. */ | ||
| export const MCP_SETUP_DURATION = 1022; | ||
| /** 1180 frames of scene, 44 of overlap. */ | ||
| export const MCP_AGENT_DURATION = 1136; | ||
|
|
||
| /** Frames the two posters are cut from: the endpoint, and the finished session. */ | ||
| export const MCP_SETUP_POSTER_FRAME = 306; | ||
| export const MCP_AGENT_POSTER_FRAME = 700; | ||
|
|
||
| function timing(durationInFrames: number) { | ||
| return linearTiming({ durationInFrames }); | ||
| } | ||
|
|
||
| /** | ||
| * Connecting a client: the problem, the config, the authorization, the tools. | ||
| * Every fact on screen is read out of the repository, so the video and the | ||
| * product cannot drift apart without the source drifting first. | ||
| */ | ||
| export function SkillsboardMcpSetup() { | ||
| return ( | ||
| <AbsoluteFill style={{ background: light.background }}> | ||
| <TransitionSeries> | ||
| <TransitionSeries.Sequence durationInFrames={130}> | ||
| <HookScene durationInFrames={130} /> | ||
| </TransitionSeries.Sequence> | ||
| <TransitionSeries.Transition presentation={focusPull()} timing={timing(24)} /> | ||
|
|
||
| <TransitionSeries.Sequence durationInFrames={300}> | ||
| <ConfigScene durationInFrames={300} /> | ||
| </TransitionSeries.Sequence> | ||
| <TransitionSeries.Transition presentation={pushThrough()} timing={timing(22)} /> | ||
|
|
||
| <TransitionSeries.Sequence durationInFrames={260}> | ||
| <ConsentScene durationInFrames={260} /> | ||
| </TransitionSeries.Sequence> | ||
| <TransitionSeries.Transition presentation={zoomBlur()} timing={timing(20)} /> | ||
|
|
||
| <TransitionSeries.Sequence durationInFrames={260}> | ||
| <ToolsScene durationInFrames={260} /> | ||
| </TransitionSeries.Sequence> | ||
| <TransitionSeries.Transition presentation={fade()} timing={timing(22)} /> | ||
|
|
||
| <TransitionSeries.Sequence durationInFrames={160}> | ||
| <EndCardScene durationInFrames={160} /> | ||
| </TransitionSeries.Sequence> | ||
| </TransitionSeries> | ||
| </AbsoluteFill> | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * The connection at work: one task, one search against the team library, and | ||
| * the teammate's own note carried into the answer. It runs as a single session | ||
| * in a single window, on the same light stage as the setup video, so the search | ||
| * and the answer read as one job rather than as two examples. | ||
| */ | ||
| export function SkillsboardMcpAgent() { | ||
| return ( | ||
| <AbsoluteFill style={{ background: light.background }}> | ||
| <TransitionSeries> | ||
| <TransitionSeries.Sequence durationInFrames={770}> | ||
| <SessionScene durationInFrames={770} /> | ||
| </TransitionSeries.Sequence> | ||
| <TransitionSeries.Transition presentation={pushThrough()} timing={timing(22)} /> | ||
|
|
||
| <TransitionSeries.Sequence durationInFrames={250}> | ||
| <ReachScene durationInFrames={250} /> | ||
| </TransitionSeries.Sequence> | ||
| <TransitionSeries.Transition presentation={fade()} timing={timing(22)} /> | ||
|
|
||
| <TransitionSeries.Sequence durationInFrames={160}> | ||
| <EndCardScene durationInFrames={160} /> | ||
| </TransitionSeries.Sequence> | ||
| </TransitionSeries> | ||
| </AbsoluteFill> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { interpolate } from "remotion"; | ||
| import { clamp, Scene, useSceneFrame } from "../../product-demo/stage"; | ||
| import { light } from "../../product-demo/theme"; | ||
| import { CONFIG_URL_LINE, setup } from "../content"; | ||
| import { Rise, SceneHead, t } from "../ui/chrome"; | ||
| import { ConfigBlock } from "../ui/code-block"; | ||
|
|
||
| const REF = 300; | ||
| const { config } = setup; | ||
|
|
||
| /** Step one: the block from `plugin/mcp.json`, pasted into the client. */ | ||
| export function ConfigScene({ durationInFrames }: { durationInFrames: number }) { | ||
| const frame = useSceneFrame(REF, durationInFrames); | ||
|
|
||
| return ( | ||
| <Scene> | ||
| <div | ||
| style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 44 }} | ||
| > | ||
| <SceneHead frame={frame} heading={config.heading} step={config.step} /> | ||
|
|
||
| <Rise at={24} distance={22} frame={frame}> | ||
| <ConfigBlock at={34} frame={frame} highlightAt={132} highlightLine={CONFIG_URL_LINE} /> | ||
| </Rise> | ||
|
|
||
| <div | ||
| style={{ | ||
| ...t.caption, | ||
| color: light.mutedForeground, | ||
| opacity: interpolate(frame, [176, 196], [0, 1], clamp), | ||
| }} | ||
| > | ||
| {config.caption} | ||
| </div> | ||
| </div> | ||
| </Scene> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { Scene, useSceneFrame } from "../../product-demo/stage"; | ||
| import { setup } from "../content"; | ||
| import { Rise, SceneHead } from "../ui/chrome"; | ||
| import { ConsentCard } from "../ui/consent-card"; | ||
|
|
||
| const REF = 260; | ||
| const { consent } = setup; | ||
|
|
||
| /** Step two: the browser authorization, on Skills Board's own tokens. */ | ||
| export function ConsentScene({ durationInFrames }: { durationInFrames: number }) { | ||
| const frame = useSceneFrame(REF, durationInFrames); | ||
|
|
||
| return ( | ||
| <Scene> | ||
| <div | ||
| style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 44 }} | ||
| > | ||
| <SceneHead frame={frame} heading={consent.heading} step={consent.step} /> | ||
| <Rise at={22} distance={22} frame={frame}> | ||
| <ConsentCard at={34} frame={frame} pressAt={148} /> | ||
| </Rise> | ||
| </div> | ||
| </Scene> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { interpolate } from "remotion"; | ||
| import { clamp, outEasing, Scene, useSceneFrame } from "../../product-demo/stage"; | ||
| import { displayTracking, light, MONO } from "../../product-demo/theme"; | ||
| import { BrandMark } from "../../product-demo/ui/atoms"; | ||
| import { brand } from "../content"; | ||
|
|
||
| const REF = 160; | ||
|
|
||
| /** The same close on both videos: mark, category, domain, licence. */ | ||
| export function EndCardScene({ durationInFrames }: { durationInFrames: number }) { | ||
| const frame = useSceneFrame(REF, durationInFrames); | ||
|
|
||
| return ( | ||
| <Scene> | ||
| <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 26 }}> | ||
| <div | ||
| style={{ | ||
| display: "flex", | ||
| alignItems: "center", | ||
| gap: 22, | ||
| opacity: interpolate(frame, [0, 18], [0, 1], clamp), | ||
| translate: `0 ${interpolate(frame, [0, 24], [18, 0], outEasing)}px`, | ||
| }} | ||
| > | ||
| <BrandMark size={72} /> | ||
| <span style={{ fontSize: 70, fontWeight: 600, letterSpacing: displayTracking }}> | ||
| {brand.wordmark} | ||
| </span> | ||
| </div> | ||
|
|
||
| <div | ||
| style={{ | ||
| fontSize: 36, | ||
| color: light.foreground, | ||
| opacity: interpolate(frame, [22, 42], [0, 1], clamp), | ||
| translate: `0 ${interpolate(frame, [22, 46], [12, 0], outEasing)}px`, | ||
| }} | ||
| > | ||
| {brand.category} | ||
| </div> | ||
|
|
||
| <div | ||
| style={{ | ||
| marginTop: 10, | ||
| fontFamily: MONO, | ||
| fontSize: 32, | ||
| color: light.primary, | ||
| opacity: interpolate(frame, [46, 66], [0, 1], clamp), | ||
| }} | ||
| > | ||
| {brand.domain} | ||
| </div> | ||
|
|
||
| <div | ||
| style={{ | ||
| fontSize: 28, | ||
| color: light.mutedForeground, | ||
| opacity: interpolate(frame, [66, 86], [0, 1], clamp), | ||
| }} | ||
| > | ||
| {brand.licence} | ||
| </div> | ||
| </div> | ||
| </Scene> | ||
| ); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a viewer follows the video's “Paste the config” instruction for the subsequently shown Claude Code flow—or for Cursor—they receive the portable agent-plugin descriptor rather than a valid client config. The repository's Claude manifest (
plugin/.mcp.json) and the manual configuration generated inapp/(app)/connect/page.tsxuse"type": "http", while Claude Code's documented setup incomponents/mcp-setup-guide.tsxusesclaude mcp add --transport http; the shown"streamable-http"value belongs toplugin/mcp.json's plugin schema. Use the client-facing config or show the plugin installation flow so the advertised setup can actually connect.Useful? React with 👍 / 👎.