@vibeframe/mcp-server 0.108.0 → 0.109.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 +24 -1
- package/dist/index.js +111 -12
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -41,7 +41,26 @@ args = ["-y", "@vibeframe/mcp-server"]
|
|
|
41
41
|
enabled = true
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
### Claude Desktop
|
|
44
|
+
### Claude Desktop (recommended: extension)
|
|
45
|
+
|
|
46
|
+
Install the prebuilt Desktop Extension — no Node, npx, or JSON editing needed:
|
|
47
|
+
|
|
48
|
+
1. Download `vibeframe-<version>.mcpb` from the
|
|
49
|
+
[latest GitHub Release](https://github.com/vericontext/vibeframe/releases/latest).
|
|
50
|
+
2. In Claude Desktop open **Settings → Extensions** and drag the `.mcpb` file in
|
|
51
|
+
(or double-click the file).
|
|
52
|
+
3. In the extension's settings pick your **Workspace folder** (projects are
|
|
53
|
+
created there; a `.env` file in that folder is loaded for API keys) and
|
|
54
|
+
optionally paste provider keys.
|
|
55
|
+
|
|
56
|
+
Rendering still needs Google Chrome and `ffmpeg` on the machine. For free local
|
|
57
|
+
Kokoro TTS, run `npm i kokoro-js` once inside the workspace folder. To update,
|
|
58
|
+
download the new release's `.mcpb` and install it over the old one.
|
|
59
|
+
|
|
60
|
+
To build the bundle from source: `pnpm -F @vibeframe/mcp-server build:mcpb`
|
|
61
|
+
(output in `packages/mcp-server/dist-mcpb/`).
|
|
62
|
+
|
|
63
|
+
#### Claude Desktop (manual JSON config)
|
|
45
64
|
|
|
46
65
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
47
66
|
|
|
@@ -64,6 +83,10 @@ config and may not preserve a raw `cwd` field. It anchors relative project paths
|
|
|
64
83
|
so prompts like "create a project named launch" create `launch/` under your
|
|
65
84
|
workspace instead of a temporary directory.
|
|
66
85
|
|
|
86
|
+
Note: some Claude Desktop builds rewrite this file from memory and can mangle
|
|
87
|
+
hand-edited entries (the entry comes back as `"command": "custom"`). The
|
|
88
|
+
extension install above avoids that entirely and is the recommended path.
|
|
89
|
+
|
|
67
90
|
### Cursor
|
|
68
91
|
|
|
69
92
|
Add to `.cursor/mcp.json`:
|
package/dist/index.js
CHANGED
|
@@ -4730,13 +4730,32 @@ async function loadKokoroFactory() {
|
|
|
4730
4730
|
const mod = await import("kokoro-js");
|
|
4731
4731
|
return mod.KokoroTTS;
|
|
4732
4732
|
} catch (err) {
|
|
4733
|
+
const fallback2 = await loadKokoroFromWorkspace();
|
|
4734
|
+
if (fallback2)
|
|
4735
|
+
return fallback2;
|
|
4733
4736
|
throw mapKokoroImportError(err);
|
|
4734
4737
|
}
|
|
4735
4738
|
}
|
|
4739
|
+
async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
|
|
4740
|
+
try {
|
|
4741
|
+
const { readFile: readFile45 } = await import("node:fs/promises");
|
|
4742
|
+
const { join: join44, resolve: resolve79 } = await import("node:path");
|
|
4743
|
+
const { pathToFileURL } = await import("node:url");
|
|
4744
|
+
const pkgDir = resolve79(baseDir, "node_modules", "kokoro-js");
|
|
4745
|
+
const pkg = JSON.parse(await readFile45(join44(pkgDir, "package.json"), "utf-8"));
|
|
4746
|
+
const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
|
|
4747
|
+
if (!entry)
|
|
4748
|
+
return null;
|
|
4749
|
+
const mod = await import(pathToFileURL(join44(pkgDir, entry)).href);
|
|
4750
|
+
return mod.KokoroTTS ?? null;
|
|
4751
|
+
} catch {
|
|
4752
|
+
return null;
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4736
4755
|
function mapKokoroImportError(err) {
|
|
4737
4756
|
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
4738
4757
|
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
4739
|
-
return new Error("Local Kokoro TTS is unavailable: optional dependency kokoro-js is not installed (its native dependencies may have failed to build on this platform). Install it with `npm i kokoro-js
|
|
4758
|
+
return new Error("Local Kokoro TTS is unavailable: optional dependency kokoro-js is not installed (its native dependencies may have failed to build on this platform). Install it with `npm i kokoro-js` \u2014 run it inside your workspace folder when using the Claude Desktop extension \u2014 or set ELEVENLABS_API_KEY to use the elevenlabs TTS provider instead.");
|
|
4740
4759
|
}
|
|
4741
4760
|
return err instanceof Error ? err : new Error(String(err));
|
|
4742
4761
|
}
|
|
@@ -25267,6 +25286,8 @@ imageProvider: openai
|
|
|
25267
25286
|
|
|
25268
25287
|
Edit these beats before running \`vibe build\`. Each beat starts with
|
|
25269
25288
|
YAML cues that drive narration, backdrop generation, and timing.
|
|
25289
|
+
Pacing: keep beats 6-15 seconds (split longer ones); a 90s video should
|
|
25290
|
+
have 6-8 beats, not 3 long ones.
|
|
25270
25291
|
|
|
25271
25292
|
## Beat hook \u2014 Hook
|
|
25272
25293
|
|
|
@@ -450075,6 +450096,29 @@ function filterSubCompFalsePositives(findings, isSubComposition) {
|
|
|
450075
450096
|
if (!isSubComposition) return noEpsilon;
|
|
450076
450097
|
return noEpsilon.filter((f2) => !SUB_COMP_FALSE_POSITIVES.has(f2.code));
|
|
450077
450098
|
}
|
|
450099
|
+
function findInternalPhaseClipFindings(html) {
|
|
450100
|
+
const findings = [];
|
|
450101
|
+
for (const tagMatch of html.matchAll(/<[a-z][a-z0-9-]*\s[^>]*>/gi)) {
|
|
450102
|
+
const tag = tagMatch[0];
|
|
450103
|
+
if (!/class="[^"]*\bclip\b[^"]*"/i.test(tag)) continue;
|
|
450104
|
+
const startAttr = tag.match(/data-start="([^"]+)"/i);
|
|
450105
|
+
if (!startAttr) continue;
|
|
450106
|
+
const start = Number(startAttr[1]);
|
|
450107
|
+
if (!Number.isFinite(start) || start === 0) continue;
|
|
450108
|
+
findings.push({
|
|
450109
|
+
code: "internal_phase_clip_unsupported",
|
|
450110
|
+
severity: "error",
|
|
450111
|
+
message: `Inner .clip has data-start="${startAttr[1]}". The renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render ALL phases at once (overlapping text).`,
|
|
450112
|
+
fixHint: 'Give every inner .clip data-start="0" spanning the full beat, and drive phase changes with GSAP autoAlpha instead (animate the previous phase out, the next one in).',
|
|
450113
|
+
snippet: tag.slice(0, 120)
|
|
450114
|
+
});
|
|
450115
|
+
}
|
|
450116
|
+
return findings;
|
|
450117
|
+
}
|
|
450118
|
+
function withVibeframeSubCompFindings(findings, html, isSubComposition) {
|
|
450119
|
+
if (!isSubComposition) return findings;
|
|
450120
|
+
return [...findings, ...findInternalPhaseClipFindings(html)];
|
|
450121
|
+
}
|
|
450078
450122
|
function applyMechanicalFixes(html, findings) {
|
|
450079
450123
|
const fixedCodes = /* @__PURE__ */ new Set();
|
|
450080
450124
|
let updated = html;
|
|
@@ -450120,7 +450164,11 @@ async function runProjectLint(opts) {
|
|
|
450120
450164
|
source: "projectDir"
|
|
450121
450165
|
};
|
|
450122
450166
|
const raw2 = runHyperframeLint(prepared);
|
|
450123
|
-
const findings =
|
|
450167
|
+
const findings = withVibeframeSubCompFindings(
|
|
450168
|
+
filterSubCompFalsePositives(raw2.findings, isSub),
|
|
450169
|
+
html,
|
|
450170
|
+
isSub
|
|
450171
|
+
);
|
|
450124
450172
|
if (opts.fix && findings.length > 0) {
|
|
450125
450173
|
const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
|
|
450126
450174
|
if (fixedCodes.length > 0) {
|
|
@@ -450130,7 +450178,11 @@ async function runProjectLint(opts) {
|
|
|
450130
450178
|
files.push({
|
|
450131
450179
|
file: rel,
|
|
450132
450180
|
isSubComposition: isSub,
|
|
450133
|
-
findings:
|
|
450181
|
+
findings: withVibeframeSubCompFindings(
|
|
450182
|
+
filterSubCompFalsePositives(reLinted.findings, isSub),
|
|
450183
|
+
nextHtml,
|
|
450184
|
+
isSub
|
|
450185
|
+
)
|
|
450134
450186
|
});
|
|
450135
450187
|
continue;
|
|
450136
450188
|
}
|
|
@@ -469633,8 +469685,14 @@ ${finalDurationBullet}
|
|
|
469633
469685
|
Animate the backdrop/media plane instead; let text enter briefly, then hold
|
|
469634
469686
|
still at its final CSS position. Continuous transforms on text ancestors can
|
|
469635
469687
|
create subpixel shimmer in screenshot-captured renders.
|
|
469636
|
-
-
|
|
469637
|
-
\`data-
|
|
469688
|
+
- Inner \`.clip\` elements carry \`data-start\`, \`data-duration\`,
|
|
469689
|
+
\`data-track-index\` \u2014 but **every inner clip MUST be full-window:
|
|
469690
|
+
\`data-start="0"\` spanning the whole beat.** The renderer does NOT toggle
|
|
469691
|
+
internal clip visibility inside sub-compositions, so "phase clips" with a
|
|
469692
|
+
non-zero \`data-start\` render ALL phases at once and text stacks on top of
|
|
469693
|
+
itself. For multi-phase beats, drive phase changes with GSAP \`autoAlpha\`
|
|
469694
|
+
inside full-window clips: animate phase A out (\`autoAlpha: 0\`), then
|
|
469695
|
+
phase B in, on the same timeline.
|
|
469638
469696
|
- If \`assets/backdrop-${ctx.beat.id}.png\` exists, use that local file as the
|
|
469639
469697
|
full-frame visual backdrop. The exact path string is
|
|
469640
469698
|
\`assets/backdrop-${ctx.beat.id}.png\`; do NOT prefix it with \`../\` or \`./\`
|
|
@@ -469648,7 +469706,9 @@ ${finalDurationBullet}
|
|
|
469648
469706
|
narration.
|
|
469649
469707
|
- Keep a meaningful visual progression across the full beat duration: line
|
|
469650
469708
|
tracing, gentle camera drift, staged labels, value cards, or parallax. Avoid
|
|
469651
|
-
a completed static frame holding silently for most of the beat.
|
|
469709
|
+
a completed static frame holding silently for most of the beat. For longer
|
|
469710
|
+
beats, progress through content phases with GSAP autoAlpha (old phase out,
|
|
469711
|
+
new phase in) \u2014 never with inner clip windows.
|
|
469652
469712
|
- Do not import external font or image URLs. Use the project DESIGN.md font
|
|
469653
469713
|
choice when explicit; otherwise use \`Inter\`, which is available in the
|
|
469654
469714
|
deterministic render font map.
|
|
@@ -469823,7 +469883,11 @@ function lintBeatHtml(html, beatId) {
|
|
|
469823
469883
|
source: "projectDir"
|
|
469824
469884
|
};
|
|
469825
469885
|
const raw2 = runHyperframeLint(prepared);
|
|
469826
|
-
const findings =
|
|
469886
|
+
const findings = withVibeframeSubCompFindings(
|
|
469887
|
+
filterSubCompFalsePositives(raw2.findings, true),
|
|
469888
|
+
html,
|
|
469889
|
+
true
|
|
469890
|
+
);
|
|
469827
469891
|
return {
|
|
469828
469892
|
errorCount: findings.filter((f2) => f2.severity === "error").length,
|
|
469829
469893
|
warningCount: findings.filter((f2) => f2.severity === "warning").length,
|
|
@@ -470262,7 +470326,8 @@ function buildInstructions(args) {
|
|
|
470262
470326
|
lines.push(`2. Read \`DESIGN.md\` for project-specific palette, typography, motion signature.`);
|
|
470263
470327
|
lines.push(`3. For each beat in the \`beats\` array below, author HTML at \`outputPath\` matching the \`userPrompt\`. The beat \`body\` carries the narrative + visual + animation intent; \`cues\` carries machine-readable per-beat overrides (narration, duration, backdrop, voice).`);
|
|
470264
470328
|
lines.push(`3b. Use each beat's \`finalDurationSec\` (narration-synced) for \`data-duration\` and timeline anchors when present \u2014 NOT the storyboard \`duration\`, which is only the minimum. Scenes composed at the storyboard duration end early and render black tails.`);
|
|
470265
|
-
lines.push(`3c.
|
|
470329
|
+
lines.push(`3c. Never give inner \`.clip\` elements a non-zero \`data-start\` \u2014 the renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render all phases at once (overlapping text). Use full-window clips and GSAP autoAlpha phase transitions instead. Also keep beats 6-15s; split anything longer in the storyboard first.`);
|
|
470330
|
+
lines.push(`3d. If your environment cannot write files (e.g. Claude Desktop / MCP-only hosts), author each beat's HTML and submit it with the \`scene_submit\` tool (beat id + html). It validates with the same Hyperframes lint and writes the file for you; on lint errors it returns the findings without writing \u2014 fix and resubmit.`);
|
|
470266
470331
|
if (args.beatCount > 1) {
|
|
470267
470332
|
lines.push(`4. After authoring all ${args.beatCount} beat(s), run \`vibe scene lint --fix\` to validate. Fix any remaining errors by editing the HTML directly.`);
|
|
470268
470333
|
} else if (args.filtered) {
|
|
@@ -470584,7 +470649,11 @@ function lintHtml(html, rel, isSub) {
|
|
|
470584
470649
|
source: "projectDir"
|
|
470585
470650
|
};
|
|
470586
470651
|
const raw2 = runHyperframeLint(prepared);
|
|
470587
|
-
return
|
|
470652
|
+
return withVibeframeSubCompFindings(
|
|
470653
|
+
filterSubCompFalsePositives(raw2.findings, isSub),
|
|
470654
|
+
html,
|
|
470655
|
+
isSub
|
|
470656
|
+
);
|
|
470588
470657
|
}
|
|
470589
470658
|
function lintFilesToIssues(files) {
|
|
470590
470659
|
const issues = [];
|
|
@@ -471655,6 +471724,16 @@ function validateStoryboardMarkdown(markdown) {
|
|
|
471655
471724
|
}
|
|
471656
471725
|
}
|
|
471657
471726
|
}
|
|
471727
|
+
for (const beat of parsed.beats) {
|
|
471728
|
+
if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
|
|
471729
|
+
issues.push({
|
|
471730
|
+
severity: "warning",
|
|
471731
|
+
code: "BEAT_DURATION_TOO_LONG",
|
|
471732
|
+
beatId: beat.id,
|
|
471733
|
+
message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
|
|
471734
|
+
});
|
|
471735
|
+
}
|
|
471736
|
+
}
|
|
471658
471737
|
return {
|
|
471659
471738
|
ok: !issues.some((i2) => i2.severity === "error"),
|
|
471660
471739
|
beats: parsed.beats,
|
|
@@ -471753,7 +471832,7 @@ function readLeadingCueBlock(body) {
|
|
|
471753
471832
|
return { full: match2[0], error: error instanceof Error ? error.message : String(error) };
|
|
471754
471833
|
}
|
|
471755
471834
|
}
|
|
471756
|
-
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS;
|
|
471835
|
+
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
|
|
471757
471836
|
var init_storyboard_edit = __esm({
|
|
471758
471837
|
"../cli/src/commands/_shared/storyboard-edit.ts"() {
|
|
471759
471838
|
"use strict";
|
|
@@ -471773,6 +471852,7 @@ var init_storyboard_edit = __esm({
|
|
|
471773
471852
|
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
471774
471853
|
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
471775
471854
|
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
471855
|
+
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
471776
471856
|
}
|
|
471777
471857
|
});
|
|
471778
471858
|
|
|
@@ -491942,7 +492022,8 @@ function revisionMessages(opts) {
|
|
|
491942
492022
|
"The JSON object must have: storyboardMd string, summary string, changedBeats string[], warnings string[].",
|
|
491943
492023
|
"Preserve existing frontmatter, beat ids, cue YAML keys, and useful prose unless the user asks to change structure.",
|
|
491944
492024
|
"Keep cue YAML valid. Allowed cue keys are duration, narration, backdrop, video, motion, voice, music, asset.",
|
|
491945
|
-
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target."
|
|
492025
|
+
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target.",
|
|
492026
|
+
"Aim for 6-15 seconds per beat and never exceed ~15s \u2014 long beats render as static, overstuffed scenes. A 90-second video should have 6-8 beats."
|
|
491946
492027
|
].join("\n");
|
|
491947
492028
|
const repairBlock = opts.invalidStoryboard ? [
|
|
491948
492029
|
"The previous revision failed validation. Repair it.",
|
|
@@ -497625,7 +497706,23 @@ This creates a music video-style edit where cuts happen in rhythm with the music
|
|
|
497625
497706
|
}
|
|
497626
497707
|
|
|
497627
497708
|
// src/instructions.ts
|
|
497709
|
+
import { mkdirSync as mkdirSync13 } from "node:fs";
|
|
497628
497710
|
import { isAbsolute as isAbsolute9, resolve as resolve78 } from "node:path";
|
|
497711
|
+
function applyWorkspaceEnv(env4 = process.env, chdir = (dir) => process.chdir(dir)) {
|
|
497712
|
+
const raw2 = env4.VIBE_MCP_WORKSPACE?.trim();
|
|
497713
|
+
if (!raw2) return null;
|
|
497714
|
+
const dir = resolve78(raw2);
|
|
497715
|
+
try {
|
|
497716
|
+
mkdirSync13(dir, { recursive: true });
|
|
497717
|
+
chdir(dir);
|
|
497718
|
+
return dir;
|
|
497719
|
+
} catch (error) {
|
|
497720
|
+
console.error(
|
|
497721
|
+
`[vibeframe] Could not use VIBE_MCP_WORKSPACE=${raw2} (${error instanceof Error ? error.message : String(error)}); staying in ${process.cwd()}`
|
|
497722
|
+
);
|
|
497723
|
+
return null;
|
|
497724
|
+
}
|
|
497725
|
+
}
|
|
497629
497726
|
function resolveServerWorkspaceRoot(env4 = process.env, cwd = process.cwd()) {
|
|
497630
497727
|
const initCwd = env4.INIT_CWD?.trim();
|
|
497631
497728
|
if (!initCwd) return cwd;
|
|
@@ -497645,10 +497742,12 @@ function buildServerInstructions(cwd = resolveServerWorkspaceRoot()) {
|
|
|
497645
497742
|
console.log = (...args) => console.error(...args);
|
|
497646
497743
|
console.info = (...args) => console.error(...args);
|
|
497647
497744
|
console.debug = (...args) => console.error(...args);
|
|
497745
|
+
applyWorkspaceEnv();
|
|
497648
497746
|
var server2 = new Server(
|
|
497649
497747
|
{
|
|
497650
497748
|
name: "vibeframe",
|
|
497651
|
-
version
|
|
497749
|
+
// Stamped with the package version by build.js at bundle time.
|
|
497750
|
+
version: "0.109.0"
|
|
497652
497751
|
},
|
|
497653
497752
|
{
|
|
497654
497753
|
capabilities: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.109.0",
|
|
4
4
|
"description": "VibeFrame MCP Server - AI-native video editing via Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -60,14 +60,15 @@
|
|
|
60
60
|
"tsx": "^4.21.0",
|
|
61
61
|
"typescript": "^5.3.3",
|
|
62
62
|
"vitest": "^1.2.2",
|
|
63
|
-
"@vibeframe/
|
|
64
|
-
"@vibeframe/
|
|
63
|
+
"@vibeframe/core": "0.109.0",
|
|
64
|
+
"@vibeframe/cli": "0.109.0"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">=20"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "node build.js",
|
|
71
|
+
"build:mcpb": "node build-mcpb.js",
|
|
71
72
|
"build:types": "tsc --emitDeclarationOnly",
|
|
72
73
|
"dev": "tsx src/index.ts",
|
|
73
74
|
"start": "node dist/index.js",
|