@marlinjai/studio-mcp 0.1.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.
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ # Register the Lumitra Studio MCP server with Claude Code.
3
+ # Pick ONE of the blocks below. Never paste the key into a chat; set it in your
4
+ # shell (or let cc.sh inject LUMITRA_STUDIO_SERVICE_TOKEN) and pass it by reference.
5
+ set -euo pipefail
6
+
7
+ # 1) stdio via npx (published package), key from the current shell
8
+ claude mcp add lumitra-studio \
9
+ --env "LUMITRA_STUDIO_API_KEY=${LUMITRA_STUDIO_API_KEY:-${LUMITRA_STUDIO_SERVICE_TOKEN:?set LUMITRA_STUDIO_API_KEY}}" \
10
+ -- npx -y @marlinjai/studio-mcp
11
+
12
+ # 2) stdio from a local monorepo build
13
+ # pnpm --filter @marlinjai/studio-mcp build
14
+ # claude mcp add lumitra-studio -- node "$(pwd)/packages/studio-mcp/dist/cli.js"
15
+
16
+ # 3) Streamable HTTP (server started elsewhere with: studio-mcp --http --port 3939)
17
+ # claude mcp add --transport http lumitra-studio http://127.0.0.1:3939/mcp
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "lumitra-studio": {
4
+ "command": "npx",
5
+ "args": ["-y", "@marlinjai/studio-mcp"],
6
+ "env": {
7
+ "LUMITRA_STUDIO_API_KEY": "sk_live_REPLACE_ME",
8
+ "LUMITRA_STUDIO_BASE_URL": "https://studio.lumitra.co"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ // Smoke test for the Streamable HTTP transport: starts the server on a random
3
+ // port with a throwaway key, lists tools over JSON-RPC, and exits non-zero if
4
+ // the tool list does not match. Hits no real Studio endpoint.
5
+ //
6
+ // node examples/smoke-http.mjs
7
+ import { spawn } from 'node:child_process';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { dirname, join } from 'node:path';
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const cli = join(here, '..', 'dist', 'cli.js');
13
+
14
+ const child = spawn(process.execPath, [cli, '--http', '--port', '0'], {
15
+ env: { ...process.env, LUMITRA_STUDIO_API_KEY: 'smoke-test-key-not-real' },
16
+ stdio: ['ignore', 'ignore', 'pipe'],
17
+ });
18
+
19
+ const port = await new Promise((resolve, reject) => {
20
+ let buf = '';
21
+ child.stderr.on('data', (d) => {
22
+ buf += String(d);
23
+ const m = buf.match(/http:\/\/127\.0\.0\.1:(\d+)\/mcp/);
24
+ if (m) resolve(Number(m[1]));
25
+ });
26
+ child.on('exit', (code) => reject(new Error(`server exited early (${code}): ${buf}`)));
27
+ setTimeout(() => reject(new Error(`server did not start: ${buf}`)), 5000);
28
+ });
29
+
30
+ try {
31
+ const health = await fetch(`http://127.0.0.1:${port}/healthz`).then((r) => r.json());
32
+ if (!health.ok) throw new Error('healthz not ok');
33
+
34
+ const init = await fetch(`http://127.0.0.1:${port}/mcp`, {
35
+ method: 'POST',
36
+ headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' },
37
+ body: JSON.stringify({
38
+ jsonrpc: '2.0',
39
+ id: 1,
40
+ method: 'initialize',
41
+ params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'smoke', version: '0' } },
42
+ }),
43
+ });
44
+ if (!init.ok) throw new Error(`initialize -> ${init.status}: ${await init.text()}`);
45
+
46
+ const res = await fetch(`http://127.0.0.1:${port}/mcp`, {
47
+ method: 'POST',
48
+ headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' },
49
+ body: JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }),
50
+ });
51
+ const text = await res.text();
52
+ // Stateless mode may answer as JSON or as a single SSE event; handle both.
53
+ const jsonLine = text.startsWith('event:') || text.startsWith('data:')
54
+ ? text.split('\n').find((l) => l.startsWith('data:')).slice(5).trim()
55
+ : text;
56
+ const body = JSON.parse(jsonLine);
57
+ const names = body.result.tools.map((t) => t.name);
58
+ const expected = [
59
+ 'studio_list_projects', 'studio_generate_image', 'studio_get_job', 'studio_list_characters',
60
+ 'studio_get_character', 'studio_create_character', 'studio_mint_character', 'studio_create_brand',
61
+ 'studio_upload_brand_reference', 'studio_run_workflow', 'studio_get_run', 'studio_get_run_output',
62
+ 'studio_replace_character_in_video', 'studio_get_spend',
63
+ 'studio_workflow_describe', 'studio_workflow_get', 'studio_workflow_create', 'studio_workflow_apply',
64
+ 'studio_workflow_validate',
65
+ ];
66
+ if (JSON.stringify(names) !== JSON.stringify(expected)) {
67
+ throw new Error(`tool list mismatch: ${names.join(', ')}`);
68
+ }
69
+ console.log(`ok: ${names.length} tools over Streamable HTTP on port ${port}`);
70
+ } finally {
71
+ child.kill('SIGTERM');
72
+ }
@@ -0,0 +1,205 @@
1
+ {
2
+ "definition": {
3
+ "version": {
4
+ "schema": 1
5
+ },
6
+ "id": "character-sheet",
7
+ "label": "Character Sheet (seed face -> turnaround)",
8
+ "params": [
9
+ {
10
+ "name": "seedPrompt",
11
+ "type": "string",
12
+ "required": true,
13
+ "label": "Seed face prompt"
14
+ },
15
+ {
16
+ "name": "threeQuarterPrompt",
17
+ "type": "string",
18
+ "required": true,
19
+ "label": "Three-quarter prompt"
20
+ },
21
+ {
22
+ "name": "profilePrompt",
23
+ "type": "string",
24
+ "required": true,
25
+ "label": "Profile prompt"
26
+ },
27
+ {
28
+ "name": "fullBodyPrompt",
29
+ "type": "string",
30
+ "required": true,
31
+ "label": "Full body prompt"
32
+ },
33
+ {
34
+ "name": "closeUpPrompt",
35
+ "type": "string",
36
+ "required": true,
37
+ "label": "Close-up prompt"
38
+ },
39
+ {
40
+ "name": "lightingWarmPrompt",
41
+ "type": "string",
42
+ "required": true,
43
+ "label": "Warm lighting prompt"
44
+ },
45
+ {
46
+ "name": "lightingCoolPrompt",
47
+ "type": "string",
48
+ "required": true,
49
+ "label": "Cool lighting prompt"
50
+ }
51
+ ],
52
+ "nodes": [
53
+ {
54
+ "id": "seed",
55
+ "task": "text-to-image",
56
+ "model": "fal/nano-banana-2",
57
+ "inputs": {
58
+ "prompt": {
59
+ "kind": "param",
60
+ "name": "seedPrompt"
61
+ },
62
+ "aspectRatio": {
63
+ "kind": "literal",
64
+ "value": "1:1"
65
+ }
66
+ }
67
+ },
68
+ {
69
+ "id": "view-three-quarter",
70
+ "task": "image-edit",
71
+ "model": "fal/nano-banana-2-edit",
72
+ "inputs": {
73
+ "prompt": {
74
+ "kind": "param",
75
+ "name": "threeQuarterPrompt"
76
+ },
77
+ "inputImages": {
78
+ "kind": "ref",
79
+ "node": "seed",
80
+ "path": "url",
81
+ "wrap": "array"
82
+ },
83
+ "aspectRatio": {
84
+ "kind": "literal",
85
+ "value": "1:1"
86
+ }
87
+ }
88
+ },
89
+ {
90
+ "id": "view-profile",
91
+ "task": "image-edit",
92
+ "model": "fal/nano-banana-2-edit",
93
+ "inputs": {
94
+ "prompt": {
95
+ "kind": "param",
96
+ "name": "profilePrompt"
97
+ },
98
+ "inputImages": {
99
+ "kind": "ref",
100
+ "node": "seed",
101
+ "path": "url",
102
+ "wrap": "array"
103
+ },
104
+ "aspectRatio": {
105
+ "kind": "literal",
106
+ "value": "1:1"
107
+ }
108
+ }
109
+ },
110
+ {
111
+ "id": "view-full-body",
112
+ "task": "image-edit",
113
+ "model": "fal/nano-banana-2-edit",
114
+ "inputs": {
115
+ "prompt": {
116
+ "kind": "param",
117
+ "name": "fullBodyPrompt"
118
+ },
119
+ "inputImages": {
120
+ "kind": "ref",
121
+ "node": "seed",
122
+ "path": "url",
123
+ "wrap": "array"
124
+ },
125
+ "aspectRatio": {
126
+ "kind": "literal",
127
+ "value": "3:4"
128
+ }
129
+ }
130
+ },
131
+ {
132
+ "id": "view-close-up",
133
+ "task": "image-edit",
134
+ "model": "fal/nano-banana-2-edit",
135
+ "inputs": {
136
+ "prompt": {
137
+ "kind": "param",
138
+ "name": "closeUpPrompt"
139
+ },
140
+ "inputImages": {
141
+ "kind": "ref",
142
+ "node": "seed",
143
+ "path": "url",
144
+ "wrap": "array"
145
+ },
146
+ "aspectRatio": {
147
+ "kind": "literal",
148
+ "value": "1:1"
149
+ }
150
+ }
151
+ },
152
+ {
153
+ "id": "view-lighting-warm",
154
+ "task": "image-edit",
155
+ "model": "fal/nano-banana-2-edit",
156
+ "inputs": {
157
+ "prompt": {
158
+ "kind": "param",
159
+ "name": "lightingWarmPrompt"
160
+ },
161
+ "inputImages": {
162
+ "kind": "ref",
163
+ "node": "seed",
164
+ "path": "url",
165
+ "wrap": "array"
166
+ },
167
+ "aspectRatio": {
168
+ "kind": "literal",
169
+ "value": "1:1"
170
+ }
171
+ }
172
+ },
173
+ {
174
+ "id": "view-lighting-cool",
175
+ "task": "image-edit",
176
+ "model": "fal/nano-banana-2-edit",
177
+ "inputs": {
178
+ "prompt": {
179
+ "kind": "param",
180
+ "name": "lightingCoolPrompt"
181
+ },
182
+ "inputImages": {
183
+ "kind": "ref",
184
+ "node": "seed",
185
+ "path": "url",
186
+ "wrap": "array"
187
+ },
188
+ "aspectRatio": {
189
+ "kind": "literal",
190
+ "value": "1:1"
191
+ }
192
+ }
193
+ }
194
+ ]
195
+ },
196
+ "exampleParams": {
197
+ "seedPrompt": "Character identity (keep identical in every view): A woman in her late 30s, short curly auburn hair, freckles, round tortoiseshell glasses, athletic build.\n\nGenerate a clean front-facing reference portrait of this character: head-and-shoulders framing, neutral expression, looking straight at the camera. Square 1:1 crop.\n\nEven, soft, neutral studio lighting.\n\nWardrobe: the character is wearing a plain heather-gray crew-neck t-shirt, unbranded, with no logos, graphics, prints, or patterns.\n\nPlain light-gray seamless studio background, no props, no text, no watermark, photorealistic, sharp focus.\n\nThis is the same person, same face, same identity as the reference image: keep the facial structure, features, hair, and proportions identical. Do not invent a new face.",
198
+ "threeQuarterPrompt": "Character identity (keep identical in every view): A woman in her late 30s, short curly auburn hair, freckles, round tortoiseshell glasses, athletic build.\n\nThree-quarter (45 degree) view: turn the head and upper body slightly to one side. Chest-up framing, square 1:1 crop. Even neutral studio lighting.\n\nWardrobe: the character is wearing a plain heather-gray crew-neck t-shirt, unbranded, with no logos, graphics, prints, or patterns.\n\nPlain light-gray seamless studio background, no props, no text, no watermark, photorealistic, sharp focus.\n\nThis is the same person, same face, same identity as the reference image: keep the facial structure, features, hair, and proportions identical. Do not invent a new face.",
199
+ "profilePrompt": "Character identity (keep identical in every view): A woman in her late 30s, short curly auburn hair, freckles, round tortoiseshell glasses, athletic build.\n\nFull side profile (90 degree) view. Chest-up framing, square 1:1 crop. Even neutral studio lighting.\n\nWardrobe: the character is wearing a plain heather-gray crew-neck t-shirt, unbranded, with no logos, graphics, prints, or patterns.\n\nPlain light-gray seamless studio background, no props, no text, no watermark, photorealistic, sharp focus.\n\nThis is the same person, same face, same identity as the reference image: keep the facial structure, features, hair, and proportions identical. Do not invent a new face.",
200
+ "fullBodyPrompt": "Character identity (keep identical in every view): A woman in her late 30s, short curly auburn hair, freckles, round tortoiseshell glasses, athletic build.\n\nFull-body head-to-toe view: the character standing in a relaxed neutral pose, facing forward. 3:4 portrait crop with the whole figure in frame. Even neutral studio lighting.\n\nWardrobe: the character is wearing a plain heather-gray crew-neck t-shirt and plain dark straight-leg trousers, unbranded, with no logos, graphics, prints, or patterns.\n\nPlain light-gray seamless studio background, no props, no text, no watermark, photorealistic, sharp focus.\n\nThis is the same person, same face, same identity as the reference image: keep the facial structure, features, hair, and proportions identical. Do not invent a new face.",
201
+ "closeUpPrompt": "Character identity (keep identical in every view): A woman in her late 30s, short curly auburn hair, freckles, round tortoiseshell glasses, athletic build.\n\nTight front-facing close-up of the face, capturing fine facial detail. Square 1:1 crop. Even neutral studio lighting.\n\nWardrobe: the character is wearing a plain heather-gray crew-neck t-shirt, unbranded, with no logos, graphics, prints, or patterns.\n\nPlain light-gray seamless studio background, no props, no text, no watermark, photorealistic, sharp focus.\n\nThis is the same person, same face, same identity as the reference image: keep the facial structure, features, hair, and proportions identical. Do not invent a new face.",
202
+ "lightingWarmPrompt": "Character identity (keep identical in every view): A woman in her late 30s, short curly auburn hair, freckles, round tortoiseshell glasses, athletic build.\n\nFront-facing head-and-shoulders portrait, square 1:1 crop, under warm golden-hour key lighting.\n\nWardrobe: the character is wearing a plain heather-gray crew-neck t-shirt, unbranded, with no logos, graphics, prints, or patterns.\n\nPlain light-gray seamless studio background, no props, no text, no watermark, photorealistic, sharp focus.\n\nThis is the same person, same face, same identity as the reference image: keep the facial structure, features, hair, and proportions identical. Do not invent a new face.",
203
+ "lightingCoolPrompt": "Character identity (keep identical in every view): A woman in her late 30s, short curly auburn hair, freckles, round tortoiseshell glasses, athletic build.\n\nFront-facing head-and-shoulders portrait, square 1:1 crop, under cool blue-toned key lighting.\n\nWardrobe: the character is wearing a plain heather-gray crew-neck t-shirt, unbranded, with no logos, graphics, prints, or patterns.\n\nPlain light-gray seamless studio background, no props, no text, no watermark, photorealistic, sharp focus.\n\nThis is the same person, same face, same identity as the reference image: keep the facial structure, features, hair, and proportions identical. Do not invent a new face."
204
+ }
205
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@marlinjai/studio-mcp",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Model Context Protocol (MCP) server over the Lumitra Studio HTTP API: generate images, mint characters, run workflows, swap characters into video.",
6
+ "license": "MIT",
7
+ "author": "Marlin Jai Pohl <marlin@lumitra.co> (https://lumitra.co)",
8
+ "homepage": "https://studio.lumitra.co",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/marlinjai/lumitra-studio-plugin.git",
12
+ "directory": "."
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/marlinjai/lumitra-studio-plugin/issues"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "image-generation",
21
+ "video-generation",
22
+ "character-consistency",
23
+ "brand",
24
+ "lumitra",
25
+ "ai-agents"
26
+ ],
27
+ "type": "module",
28
+ "main": "./dist/index.js",
29
+ "module": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "bin": {
32
+ "studio-mcp": "./dist/cli.js"
33
+ },
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "README.md",
43
+ "LICENSE",
44
+ "examples",
45
+ "!dist/**/*.map"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "engines": {
51
+ "node": ">=20"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "typecheck": "tsc -p tsconfig.typecheck.json",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "start": "node dist/cli.js"
59
+ },
60
+ "dependencies": {
61
+ "@modelcontextprotocol/sdk": "^1.30.0",
62
+ "zod": "^4.4.1"
63
+ },
64
+ "devDependencies": {
65
+ "@marlinjai/studio-core": "workspace:*",
66
+ "@types/node": "^20",
67
+ "tsup": "^8.5.0",
68
+ "typescript": "^5",
69
+ "vitest": "^4.1.5"
70
+ }
71
+ }