@olo-studio/studio-mcp 1.0.0 → 1.0.1

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 CHANGED
@@ -1,4 +1,4 @@
1
- # `@olomni/studio-mcp`
1
+ # `@olo-studio/studio-mcp`
2
2
 
3
3
  Local-first MCP server and CLI for **Olomni Studio** — compile `olomni.studio.scene.v1` scenes offline, generate 9:16 video scripts, render deterministic `.mp4` / `.webm` / alpha / chroma videos via headless Chrome (`window.OloStudioBridge.exportSceneBuffer`), and publish `<olo-presence>` 3D brand mascot embeds.
4
4
 
@@ -11,7 +11,7 @@ Add to Claude Desktop, Cursor, Windsurf, or any MCP-compatible host:
11
11
  "mcpServers": {
12
12
  "olomni-studio": {
13
13
  "command": "npx",
14
- "args": ["-y", "@olomni/studio-mcp"],
14
+ "args": ["-y", "@olo-studio/studio-mcp"],
15
15
  "env": {
16
16
  "OLO_STUDIO_API_KEY": "olo_pat_your_token_here"
17
17
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @olomni/studio-mcp CLI & Stdio MCP Server Entrypoint
3
+ * @olo-studio/studio-mcp CLI & Stdio MCP Server Entrypoint
4
4
  * Usage:
5
5
  * olo-studio-mcp [serve] Start JSON-RPC 2.0 stdio MCP server
6
6
  * olo-studio-mcp compile [opts] Compile SceneSpec v1 JSON offline
@@ -8,12 +8,17 @@
8
8
  * olo-studio-mcp inspect-glb <src> Inspect .glb header & JSON chunk
9
9
  * olo-studio-mcp --help Show help
10
10
  */
11
+ import fs from 'node:fs'
11
12
  import fsp from 'node:fs/promises'
12
13
  import path from 'node:path'
13
14
  import { compileSceneSpec } from '../src/compiler.js'
14
15
  import { inspectGlb, renderVideoWithBrowser } from '../src/renderer.js'
15
16
  import { startStdioServer } from '../src/server.js'
16
17
 
18
+ const PKG_VERSION = JSON.parse(
19
+ fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
20
+ ).version
21
+
17
22
  function parseFlags(argv) {
18
23
  const flags = {}
19
24
  const positional = []
@@ -38,7 +43,7 @@ function parseFlags(argv) {
38
43
  }
39
44
 
40
45
  function printHelp() {
41
- console.log(`@olomni/studio-mcp v1.0.0 — Local-First MCP Server & CLI for Olomni Studio
46
+ console.log(`@olo-studio/studio-mcp v${PKG_VERSION} — Local-First MCP Server & CLI for Olomni Studio
42
47
 
43
48
  Usage:
44
49
  olo-studio-mcp [serve]
@@ -47,7 +52,7 @@ Usage:
47
52
  olo-studio-mcp compile [--script "..."] [--file scene.json] [--title "..."] [--out out.json]
48
53
  Compile and validate an olomni.studio.scene.v1 SceneSpec without a browser.
49
54
 
50
- olo-studio-mcp render [--script "..."] [--file scene.json] [--out out.mp4] [--format mp4|webm|alpha|chroma] [--glb path-or-url]
55
+ olo-studio-mcp render [--script "..."] [--file scene.json] [--out out.mp4] [--format mp4|webm|alpha|chroma] [--fps 24|30|60] [--quality fast|social|studio] [--glb path-or-url]
51
56
  Render a deterministic 3D video via headless Chrome + OloStudioBridge.
52
57
 
53
58
  olo-studio-mcp inspect-glb <path-or-url>
@@ -113,6 +118,8 @@ async function main() {
113
118
  sceneSpec: input,
114
119
  format: flags.format ? String(flags.format) : 'mp4',
115
120
  outputPath: flags.out ? String(flags.out) : './olo-render.mp4',
121
+ ...(flags.fps !== undefined ? { fps: Number(flags.fps) } : {}),
122
+ ...(flags.quality ? { quality: String(flags.quality) } : {}),
116
123
  ...(isUrl ? { glbUrl: glbArg } : glbArg ? { glbPath: glbArg } : {}),
117
124
  ...(flags.studioUrl ? { studioUrl: String(flags.studioUrl) } : {}),
118
125
  })
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@olo-studio/studio-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Local-first MCP server and CLI for Olomni Studio 3D video rendering, SceneSpec v1 compilation, AI scripting, and <olo-presence> embeds.",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
7
+ "scripts": {
8
+ "prepack": "node -e \"const fs=require('fs'),p='../../public/studio/engine/director.js';if(fs.existsSync(p))fs.copyFileSync(p,'./src/director.js')\""
9
+ },
7
10
  "exports": {
8
11
  ".": "./src/server.js",
9
12
  "./compiler": "./src/compiler.js",
@@ -32,7 +35,7 @@
32
35
  },
33
36
  "repository": {
34
37
  "type": "git",
35
- "url": "git+https://github.com/olomni/olomni-studio-mcp.git",
38
+ "url": "git+https://github.com/faustodariopenamondragon-web/OlomniAI.git",
36
39
  "directory": "packages/studio-mcp"
37
40
  },
38
41
  "engines": {
package/src/compiler.js CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import fs from 'node:fs'
6
6
  import path from 'node:path'
7
+ import vm from 'node:vm'
7
8
  import { createRequire } from 'node:module'
8
9
  import { fileURLToPath } from 'node:url'
9
10
 
@@ -205,18 +206,55 @@ export function findPublicStudioDir(overrideDir) {
205
206
  return null
206
207
  }
207
208
 
209
+ function loadUmdModule(filePath) {
210
+ try {
211
+ const mod = require(filePath)
212
+ if (mod && (typeof mod.compileSceneFromScript === 'function' || typeof mod.compileScene === 'function')) {
213
+ return mod
214
+ }
215
+ } catch {
216
+ // Package "type": "module" prevents require() on .js CJS/UMD files in Node 20
217
+ }
218
+
219
+ // Node 22+ require(esm) returns {} for UMD scripts under "type": "module"; evaluate as CJS cleanly.
220
+ const code = fs.readFileSync(filePath, 'utf8')
221
+ const mod = { exports: {} }
222
+ const fn = vm.compileFunction(
223
+ code,
224
+ ['module', 'exports', 'require', '__filename', '__dirname'],
225
+ { filename: filePath },
226
+ )
227
+ fn(mod, mod.exports, require, filePath, path.dirname(filePath))
228
+ const exported = mod.exports
229
+ if (exported && (typeof exported.compileSceneFromScript === 'function' || typeof exported.compileScene === 'function')) {
230
+ return exported
231
+ }
232
+ return null
233
+ }
234
+
208
235
  export function loadDirector(overridePublicDir) {
209
236
  if (cachedDirector) return cachedDirector
237
+ const candidates = []
210
238
  const publicDir = findPublicStudioDir(overridePublicDir)
211
- if (!publicDir) return null
212
- const directorPath = path.join(publicDir, 'studio', 'engine', 'director.js')
213
- if (!fs.existsSync(directorPath)) return null
214
- try {
215
- cachedDirector = require(directorPath)
216
- return cachedDirector
217
- } catch {
218
- return null
239
+ if (publicDir) {
240
+ candidates.push(path.join(publicDir, 'studio', 'engine', 'director.js'))
241
+ }
242
+ // Bundled copy for external npx installs outside the repository
243
+ candidates.push(path.join(__dirname, 'director.js'))
244
+
245
+ for (const directorPath of candidates) {
246
+ if (!fs.existsSync(directorPath)) continue
247
+ try {
248
+ const loaded = loadUmdModule(directorPath)
249
+ if (loaded) {
250
+ cachedDirector = loaded
251
+ return cachedDirector
252
+ }
253
+ } catch {
254
+ // try next candidate
255
+ }
219
256
  }
257
+ return null
220
258
  }
221
259
 
222
260
  function fnv1aHex(str) {
@@ -434,10 +472,14 @@ function parseCsvLine(line) {
434
472
  return cells
435
473
  }
436
474
 
437
- export function parseBatchInput({ specs, csv }) {
475
+ export function parseBatchInput({ specs, csv, defaults = {} }) {
476
+ const cleanDefaults = Object.fromEntries(
477
+ Object.entries(defaults || {}).filter(([, v]) => v !== undefined),
478
+ )
479
+
438
480
  if (Array.isArray(specs) && specs.length > 0) {
439
481
  return specs.map((item, idx) => {
440
- const compiled = compileSceneSpec(item)
482
+ const compiled = compileSceneSpec({ ...cleanDefaults, ...item })
441
483
  return {
442
484
  index: idx,
443
485
  outputName: item.outputName || item.fileName || `variant-${idx + 1}-${compiled.sceneHash}`,
@@ -461,6 +503,7 @@ export function parseBatchInput({ specs, csv }) {
461
503
  }
462
504
  })
463
505
  const compiled = compileSceneSpec({
506
+ ...cleanDefaults,
464
507
  title: record.title || `Variant ${idx + 1}`,
465
508
  script: record.script || record.hook || record.text || '',
466
509
  wear: record.wear,
@@ -468,11 +511,15 @@ export function parseBatchInput({ specs, csv }) {
468
511
  finish: record.finish,
469
512
  scenario: record.scenario,
470
513
  vibe: record.vibe,
471
- voice: record.voice,
472
- voiceId: record.voiceid || record.voice_id,
514
+ voice: record.voice ?? cleanDefaults.voice,
515
+ voiceId: record.voiceid || record.voice_id || cleanDefaults.voiceId,
473
516
  emotion: record.emotion,
474
517
  stateOverride: record.state || record.stateoverride,
475
- captions: record.captions !== undefined ? record.captions !== 'false' && record.captions !== '0' : true,
518
+ captions: record.captions !== undefined
519
+ ? record.captions !== 'false' && record.captions !== '0'
520
+ : (cleanDefaults.captions !== undefined ? cleanDefaults.captions : true),
521
+ pipPath: record.pippath || record.pip_path || cleanDefaults.pipPath,
522
+ pipUrl: record.pipurl || record.pip_url || cleanDefaults.pipUrl,
476
523
  category: record.category || record.language || 'Show 9:16',
477
524
  })
478
525
  const safeName = (record.outputname || record.output_name || record.id || `variant-${idx + 1}-${compiled.sceneHash}`)