Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@
"video:render:product-hunt": "remotion render remotion/index.ts skillsboard-launch-product-hunt public/launch/skills-board-product-demo-product-hunt.mp4 --codec=h264 --crf=18 --pixel-format=yuv420p --gl=swangle --timeout=240000 --concurrency=2 --muted",
"video:render:social": "remotion render remotion/index.ts skillsboard-launch-social public/launch/skills-board-product-demo.mp4 --codec=h264 --crf=18 --pixel-format=yuv420p --gl=swangle --timeout=240000 --concurrency=2 --muted",
"video:render:team-loop": "remotion render remotion/index.ts skillsboard-team-loop public/launch/skills-board-team-loop.mp4 --codec=h264 --crf=18 --pixel-format=yuv420p --gl=swangle --timeout=240000 --concurrency=2 --muted",
"video:still:product-demo-poster": "remotion still remotion/index.ts skillsboard-launch-product-hunt public/launch/skills-board-product-demo-poster.jpg --frame=580 --image-format=jpeg --gl=swangle --timeout=240000",
"video:render:launch": "pnpm video:render:product-hunt && pnpm video:render:social && pnpm video:render:team-loop && pnpm video:still:product-demo-poster",
"video:render:mcp-setup": "remotion render remotion/index.ts skillsboard-mcp-setup public/launch/skills-board-mcp-setup.mp4 --codec=h264 --crf=18 --pixel-format=yuv420p --gl=swangle --timeout=240000 --concurrency=2 --muted",
"video:render:mcp-agent": "remotion render remotion/index.ts skillsboard-mcp-agent public/launch/skills-board-mcp-agent.mp4 --codec=h264 --crf=18 --pixel-format=yuv420p --gl=swangle --timeout=240000 --concurrency=2 --muted",
"video:still:product-demo-poster": "remotion still remotion/index.ts skillsboard-launch-product-hunt public/launch/skills-board-product-demo-poster.jpg --frame=580 --image-format=jpeg --gl=swangle --timeout=240000",
"video:still:mcp-setup-poster": "remotion still remotion/index.ts skillsboard-mcp-setup public/launch/skills-board-mcp-setup-poster.jpg --frame=306 --image-format=jpeg --gl=swangle --timeout=240000",
"video:still:mcp-agent-poster": "remotion still remotion/index.ts skillsboard-mcp-agent public/launch/skills-board-mcp-agent-poster.jpg --frame=250 --image-format=jpeg --gl=swangle --timeout=240000",
"video:render:launch": "pnpm video:render:product-hunt && pnpm video:render:social && pnpm video:render:team-loop && pnpm video:still:product-demo-poster",
"db:generate": "drizzle-kit generate",
"db:check": "drizzle-kit check && node scripts/check-schema-migrations.mjs",
"db:migrate": "drizzle-kit migrate",
Expand Down
124 changes: 124 additions & 0 deletions remotion/mcp/content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* 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",',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use the actual client configuration in the setup video

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 in app/(app)/connect/page.tsx use "type": "http", while Claude Code's documented setup in components/mcp-setup-guide.tsx uses claude mcp add --transport http; the shown "streamable-http" value belongs to plugin/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 👍 / 👎.

' "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: [
["Your", "team's", "AI", "skills"],
["are", "not", "in", "your", "agent."],
],
accentWord: "not",
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",
caption: "Nine read. Four write, once skills:write is granted.",
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;
100 changes: 100 additions & 0 deletions remotion/mcp/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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 { ApplyScene } from "./scenes/apply";
import { AskScene } from "./scenes/ask";
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 { ToolsScene } from "./scenes/tools";

export const MCP_FPS = 30;

/** 1110 frames of scene, 88 of overlap. */
export const MCP_SETUP_DURATION = 1022;
/** 1130 frames of scene, 64 of overlap. */
export const MCP_AGENT_DURATION = 1066;

/** Frames the two posters are cut from: the endpoint, and the skill it returns. */
export const MCP_SETUP_POSTER_FRAME = 306;
export const MCP_AGENT_POSTER_FRAME = 250;

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. The dark scenes lift to the
* page background on the last beat so the shared end card cuts in clean.
*/
export function SkillsboardMcpAgent() {
return (
<AbsoluteFill style={{ background: light.background }}>
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={360}>
<AskScene durationInFrames={360} />
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={fade()} timing={timing(20)} />

<TransitionSeries.Sequence durationInFrames={360}>
<ApplyScene durationInFrames={360} />
</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>
);
}
36 changes: 36 additions & 0 deletions remotion/mcp/scenes/apply.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { interpolate } from "remotion";
import { clamp, Scene, useSceneFrame } from "../../product-demo/stage";
import { ink } from "../../product-demo/theme";
import { agentRun } from "../content";
import { Console, NoteRow, OutputBlock } from "../ui/agent-console";

const REF = 360;

/** The agent follows the note the teammate saved, then finishes the task. */
export function ApplyScene({ durationInFrames }: { durationInFrames: number }) {
const frame = useSceneFrame(REF, durationInFrames);

return (
<Scene
background={ink.background}
drift={{ from: 1, to: 1.025 }}
durationInFrames={durationInFrames}
>
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 34 }}>
<Console frame={frame}>
<NoteRow at={12} frame={frame} />
<OutputBlock at={80} frame={frame} />
</Console>
<div
style={{
fontSize: 32,
color: ink.mutedForeground,
opacity: interpolate(frame, [200, 224], [0, 1], clamp),
}}
>
{agentRun.applyLine}
</div>
</div>
</Scene>
);
}
25 changes: 25 additions & 0 deletions remotion/mcp/scenes/ask.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Scene, useSceneFrame } from "../../product-demo/stage";
import { ink } from "../../product-demo/theme";
import { agentRun } from "../content";
import { CallRow, Console, HitCard, PromptRow } from "../ui/agent-console";

const REF = 360;

/** A real task, and the search that answers it from the team library. */
export function AskScene({ durationInFrames }: { durationInFrames: number }) {
const frame = useSceneFrame(REF, durationInFrames);

return (
<Scene
background={ink.background}
drift={{ from: 1, to: 1.025 }}
durationInFrames={durationInFrames}
>
<Console frame={frame}>
<PromptRow at={20} frame={frame} />
<CallRow at={110} call={agentRun.call} frame={frame} />
<HitCard at={180} frame={frame} />
</Console>
</Scene>
);
}
38 changes: 38 additions & 0 deletions remotion/mcp/scenes/config.tsx
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>
);
}
25 changes: 25 additions & 0 deletions remotion/mcp/scenes/consent.tsx
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>
);
}
Loading
Loading