@mindstone-engineering/mcp-server-runway 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.
- package/dist/auth.d.ts +21 -0
- package/dist/auth.js +29 -0
- package/dist/bridge.d.ts +16 -0
- package/dist/bridge.js +43 -0
- package/dist/client.d.ts +43 -0
- package/dist/client.js +261 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +30 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +17 -0
- package/dist/tools/account.d.ts +3 -0
- package/dist/tools/account.js +71 -0
- package/dist/tools/audio.d.ts +3 -0
- package/dist/tools/audio.js +138 -0
- package/dist/tools/configure.d.ts +3 -0
- package/dist/tools/configure.js +46 -0
- package/dist/tools/image.d.ts +3 -0
- package/dist/tools/image.js +52 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/tasks.d.ts +3 -0
- package/dist/tools/tasks.js +193 -0
- package/dist/tools/video.d.ts +3 -0
- package/dist/tools/video.js +168 -0
- package/dist/tools/voices.d.ts +3 -0
- package/dist/tools/voices.js +85 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.js +68 -0
- package/dist/utils.d.ts +14 -0
- package/dist/utils.js +42 -0
- package/package.json +48 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runwayFetch, runwayRawFetch } from '../client.js';
|
|
3
|
+
import { RunwayError } from '../types.js';
|
|
4
|
+
import { withErrorHandling } from '../utils.js';
|
|
5
|
+
export function registerVoiceTools(server) {
|
|
6
|
+
// ── List Custom Voices ────────────────────────────────────────────────
|
|
7
|
+
server.registerTool('list_custom_voices', {
|
|
8
|
+
description: 'List all custom voices you\'ve created. Returns voice IDs, names, descriptions, and status. ' +
|
|
9
|
+
'Use the voice ID with generate_speech.',
|
|
10
|
+
inputSchema: z.object({}),
|
|
11
|
+
annotations: { readOnlyHint: true },
|
|
12
|
+
}, withErrorHandling(async () => {
|
|
13
|
+
const result = await runwayFetch('/voices');
|
|
14
|
+
const voices = result.data.map(v => ({
|
|
15
|
+
id: v.id, name: v.name, description: v.description || '', status: v.status, created: v.createdAt,
|
|
16
|
+
}));
|
|
17
|
+
return JSON.stringify({
|
|
18
|
+
ok: true, voices, count: voices.length, has_more: result.hasMore,
|
|
19
|
+
message: voices.length === 0
|
|
20
|
+
? 'No custom voices yet. Create one with create_custom_voice.'
|
|
21
|
+
: `Found ${voices.length} custom voice(s).`,
|
|
22
|
+
});
|
|
23
|
+
}));
|
|
24
|
+
// ── Create Custom Voice ───────────────────────────────────────────────
|
|
25
|
+
server.registerTool('create_custom_voice', {
|
|
26
|
+
description: 'Create a custom voice from a text description of desired voice characteristics. ' +
|
|
27
|
+
'MODELS: eleven_multilingual_ttv_v2 (default), eleven_ttv_v3 (newer). ' +
|
|
28
|
+
'Check status with list_custom_voices. Once READY, use its ID with generate_speech.',
|
|
29
|
+
inputSchema: z.object({
|
|
30
|
+
name: z.string().describe('Name for the voice. Max 100 characters.'),
|
|
31
|
+
prompt: z.string().describe('Text description of desired voice. Min 20, max 1000 characters.'),
|
|
32
|
+
model: z.enum(['eleven_multilingual_ttv_v2', 'eleven_ttv_v3']).optional().describe('Voice design model. Default: eleven_multilingual_ttv_v2.'),
|
|
33
|
+
description: z.string().optional().describe('Optional description for your reference. Max 512 characters.'),
|
|
34
|
+
}),
|
|
35
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
36
|
+
}, withErrorHandling(async (args) => {
|
|
37
|
+
const model = args.model || 'eleven_multilingual_ttv_v2';
|
|
38
|
+
const body = {
|
|
39
|
+
name: args.name,
|
|
40
|
+
from: { type: 'text', prompt: args.prompt, model },
|
|
41
|
+
};
|
|
42
|
+
if (args.description)
|
|
43
|
+
body.description = args.description;
|
|
44
|
+
const result = await runwayFetch('/voices', { method: 'POST', body: JSON.stringify(body) });
|
|
45
|
+
return JSON.stringify({
|
|
46
|
+
ok: true, voice_id: result.id,
|
|
47
|
+
message: `Custom voice "${args.name}" created (ID: ${result.id}). It may take a few seconds to process. Check status with list_custom_voices.`,
|
|
48
|
+
});
|
|
49
|
+
}));
|
|
50
|
+
// ── Preview Voice ─────────────────────────────────────────────────────
|
|
51
|
+
server.registerTool('preview_voice', {
|
|
52
|
+
description: 'Generate a short audio preview of a voice from a text description, without creating it. ' +
|
|
53
|
+
'Use to audition voice characteristics before committing with create_custom_voice.',
|
|
54
|
+
inputSchema: z.object({
|
|
55
|
+
prompt: z.string().describe('Text description of desired voice. Min 20, max 1000 characters.'),
|
|
56
|
+
model: z.enum(['eleven_multilingual_ttv_v2', 'eleven_ttv_v3']).optional().describe('Voice design model. Default: eleven_multilingual_ttv_v2.'),
|
|
57
|
+
}),
|
|
58
|
+
annotations: { readOnlyHint: true },
|
|
59
|
+
}, withErrorHandling(async (args) => {
|
|
60
|
+
const model = args.model || 'eleven_multilingual_ttv_v2';
|
|
61
|
+
const result = await runwayFetch('/voices/preview', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: JSON.stringify({ prompt: args.prompt, model }),
|
|
64
|
+
});
|
|
65
|
+
return JSON.stringify({
|
|
66
|
+
ok: true, preview_url: result.url, duration_seconds: result.durationSecs,
|
|
67
|
+
message: `Voice preview generated (${result.durationSecs}s). Listen at: ${result.url}`,
|
|
68
|
+
});
|
|
69
|
+
}));
|
|
70
|
+
// ── Delete Custom Voice ───────────────────────────────────────────────
|
|
71
|
+
server.registerTool('delete_custom_voice', {
|
|
72
|
+
description: 'Delete a custom voice by ID. This is permanent and cannot be undone.',
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
voice_id: z.string().describe('UUID of the voice to delete (from list_custom_voices).'),
|
|
75
|
+
}),
|
|
76
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
77
|
+
}, withErrorHandling(async (args) => {
|
|
78
|
+
const delRes = await runwayRawFetch(`/voices/${args.voice_id}`, { method: 'DELETE' });
|
|
79
|
+
if (!delRes.ok && delRes.status !== 204) {
|
|
80
|
+
throw new RunwayError(`Failed to delete voice (HTTP ${delRes.status})`, `HTTP_${delRes.status}`, 'Check the voice ID and try again.');
|
|
81
|
+
}
|
|
82
|
+
return JSON.stringify({ ok: true, message: `Voice ${args.voice_id} deleted.` });
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=voices.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export declare const REQUEST_TIMEOUT_MS = 30000;
|
|
2
|
+
export declare const RUNWAY_API_BASE = "https://api.dev.runwayml.com/v1";
|
|
3
|
+
export declare const RUNWAY_API_VERSION = "2024-11-06";
|
|
4
|
+
export interface BridgeState {
|
|
5
|
+
port: number;
|
|
6
|
+
token: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class RunwayError extends Error {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
readonly resolution: string;
|
|
11
|
+
constructor(message: string, code: string, resolution: string);
|
|
12
|
+
}
|
|
13
|
+
export interface TaskResponse {
|
|
14
|
+
id: string;
|
|
15
|
+
}
|
|
16
|
+
export interface TaskDetail {
|
|
17
|
+
id: string;
|
|
18
|
+
status: 'PENDING' | 'THROTTLED' | 'RUNNING' | 'SUCCEEDED' | 'FAILED';
|
|
19
|
+
createdAt: string;
|
|
20
|
+
output?: string[];
|
|
21
|
+
failure?: string;
|
|
22
|
+
failureCode?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface OrgResponse {
|
|
25
|
+
tier: {
|
|
26
|
+
maxMonthlyCreditSpend: number;
|
|
27
|
+
models: Record<string, {
|
|
28
|
+
maxConcurrentGenerations: number;
|
|
29
|
+
maxDailyGenerations: number;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
creditBalance: number;
|
|
33
|
+
usage: {
|
|
34
|
+
models: Record<string, {
|
|
35
|
+
dailyGenerations: number;
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface UsageResponse {
|
|
40
|
+
results: Array<{
|
|
41
|
+
date: string;
|
|
42
|
+
usedCredits: Array<{
|
|
43
|
+
model: string;
|
|
44
|
+
amount: number;
|
|
45
|
+
}>;
|
|
46
|
+
}>;
|
|
47
|
+
models: string[];
|
|
48
|
+
}
|
|
49
|
+
export interface VoiceItem {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
description?: string;
|
|
53
|
+
createdAt: string;
|
|
54
|
+
status: string;
|
|
55
|
+
}
|
|
56
|
+
export interface VoiceListResponse {
|
|
57
|
+
data: VoiceItem[];
|
|
58
|
+
hasMore: boolean;
|
|
59
|
+
nextCursor?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface VoicePreviewResponse {
|
|
62
|
+
url: string;
|
|
63
|
+
durationSecs: number;
|
|
64
|
+
}
|
|
65
|
+
export interface UploadResponse {
|
|
66
|
+
uploadUrl: string;
|
|
67
|
+
fields: Record<string, string>;
|
|
68
|
+
runwayUri: string;
|
|
69
|
+
}
|
|
70
|
+
export declare const MIME_MAP: Record<string, string>;
|
|
71
|
+
/** Data URI size limits (binary, before base64 expansion) */
|
|
72
|
+
export declare const DATA_URI_BINARY_LIMITS: Record<string, number>;
|
|
73
|
+
export declare const MAX_UPLOAD_BYTES: number;
|
|
74
|
+
export declare const MIN_UPLOAD_BYTES = 512;
|
|
75
|
+
export declare const VOICE_PRESETS: readonly ["Maya", "Arjun", "Serene", "Bernard", "Billy", "Mark", "Clint", "Mabel", "Chad", "Leslie", "Eleanor", "Elias", "Elliot", "Grungle", "Brodie", "Sandra", "Kirk", "Kylie", "Lara", "Lisa", "Malachi", "Marlene", "Martin", "Miriam", "Monster", "Paula", "Pip", "Rusty", "Ragnar", "Xylar", "Maggie", "Jack", "Katie", "Noah", "James", "Rina", "Ella", "Mariah", "Frank", "Claudia", "Niki", "Vincent", "Kendrick", "Myrna", "Tom", "Wanda", "Benjamin", "Kiana", "Rachel"];
|
|
76
|
+
export declare const DUBBING_LANGUAGES: readonly ["en", "hi", "pt", "zh", "es", "fr", "de", "ja", "ar", "ru", "ko", "id", "it", "nl", "tr", "pl", "sv", "fil", "ms", "ro", "uk", "el", "cs", "da", "fi", "bg", "hr", "sk", "ta"];
|
|
77
|
+
/**
|
|
78
|
+
* Resolve an error status code to an actionable resolution string.
|
|
79
|
+
*/
|
|
80
|
+
export declare function getErrorResolution(status: number): string;
|
|
81
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const REQUEST_TIMEOUT_MS = 30_000;
|
|
2
|
+
export const RUNWAY_API_BASE = 'https://api.dev.runwayml.com/v1';
|
|
3
|
+
export const RUNWAY_API_VERSION = '2024-11-06';
|
|
4
|
+
export class RunwayError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
resolution;
|
|
7
|
+
constructor(message, code, resolution) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.resolution = resolution;
|
|
11
|
+
this.name = 'RunwayError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// MIME & size limits
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export const MIME_MAP = {
|
|
18
|
+
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',
|
|
19
|
+
webp: 'image/webp', bmp: 'image/bmp',
|
|
20
|
+
mp4: 'video/mp4', mov: 'video/quicktime', webm: 'video/webm', avi: 'video/x-msvideo',
|
|
21
|
+
mkv: 'video/x-matroska', '3gp': 'video/3gpp',
|
|
22
|
+
mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg', m4a: 'audio/mp4',
|
|
23
|
+
aac: 'audio/aac', flac: 'audio/flac',
|
|
24
|
+
};
|
|
25
|
+
/** Data URI size limits (binary, before base64 expansion) */
|
|
26
|
+
export const DATA_URI_BINARY_LIMITS = {
|
|
27
|
+
image: 3_300_000,
|
|
28
|
+
video: 12_000_000,
|
|
29
|
+
audio: 12_000_000,
|
|
30
|
+
};
|
|
31
|
+
export const MAX_UPLOAD_BYTES = 200 * 1_048_576;
|
|
32
|
+
export const MIN_UPLOAD_BYTES = 512;
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Voice presets and language constants
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
export const VOICE_PRESETS = [
|
|
37
|
+
'Maya', 'Arjun', 'Serene', 'Bernard', 'Billy', 'Mark', 'Clint', 'Mabel',
|
|
38
|
+
'Chad', 'Leslie', 'Eleanor', 'Elias', 'Elliot', 'Grungle', 'Brodie',
|
|
39
|
+
'Sandra', 'Kirk', 'Kylie', 'Lara', 'Lisa', 'Malachi', 'Marlene', 'Martin',
|
|
40
|
+
'Miriam', 'Monster', 'Paula', 'Pip', 'Rusty', 'Ragnar', 'Xylar', 'Maggie',
|
|
41
|
+
'Jack', 'Katie', 'Noah', 'James', 'Rina', 'Ella', 'Mariah', 'Frank',
|
|
42
|
+
'Claudia', 'Niki', 'Vincent', 'Kendrick', 'Myrna', 'Tom', 'Wanda',
|
|
43
|
+
'Benjamin', 'Kiana', 'Rachel',
|
|
44
|
+
];
|
|
45
|
+
export const DUBBING_LANGUAGES = [
|
|
46
|
+
'en', 'hi', 'pt', 'zh', 'es', 'fr', 'de', 'ja', 'ar', 'ru', 'ko', 'id',
|
|
47
|
+
'it', 'nl', 'tr', 'pl', 'sv', 'fil', 'ms', 'ro', 'uk', 'el', 'cs', 'da',
|
|
48
|
+
'fi', 'bg', 'hr', 'sk', 'ta',
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Resolve an error status code to an actionable resolution string.
|
|
52
|
+
*/
|
|
53
|
+
export function getErrorResolution(status) {
|
|
54
|
+
if (status === 401) {
|
|
55
|
+
return 'Authentication failed. Check your Runway API key. Get one at https://dev.runwayml.com/';
|
|
56
|
+
}
|
|
57
|
+
if (status === 403) {
|
|
58
|
+
return 'Access forbidden. Check your Runway API key and account permissions.';
|
|
59
|
+
}
|
|
60
|
+
if (status === 404) {
|
|
61
|
+
return 'The resource does not exist or was deleted.';
|
|
62
|
+
}
|
|
63
|
+
if (status === 429) {
|
|
64
|
+
return 'Rate limited. Wait a moment and try again.';
|
|
65
|
+
}
|
|
66
|
+
return 'Try again or check https://dev.runwayml.com/';
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=types.js.map
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
type ToolHandler<T> = (args: T, extra: unknown) => Promise<CallToolResult>;
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a tool handler with standard error handling.
|
|
5
|
+
*
|
|
6
|
+
* - On success: returns the string result as a text content block.
|
|
7
|
+
* - On RunwayError: returns a structured JSON error with code and resolution.
|
|
8
|
+
* - On unknown error: returns a generic error message.
|
|
9
|
+
*
|
|
10
|
+
* Secrets are never exposed in error messages.
|
|
11
|
+
*/
|
|
12
|
+
export declare function withErrorHandling<T>(fn: (args: T, extra: unknown) => Promise<string>): ToolHandler<T>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RunwayError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a tool handler with standard error handling.
|
|
4
|
+
*
|
|
5
|
+
* - On success: returns the string result as a text content block.
|
|
6
|
+
* - On RunwayError: returns a structured JSON error with code and resolution.
|
|
7
|
+
* - On unknown error: returns a generic error message.
|
|
8
|
+
*
|
|
9
|
+
* Secrets are never exposed in error messages.
|
|
10
|
+
*/
|
|
11
|
+
export function withErrorHandling(fn) {
|
|
12
|
+
return async (args, extra) => {
|
|
13
|
+
try {
|
|
14
|
+
const result = await fn(args, extra);
|
|
15
|
+
return { content: [{ type: 'text', text: result }] };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error instanceof RunwayError) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: JSON.stringify({
|
|
24
|
+
ok: false,
|
|
25
|
+
error: error.message,
|
|
26
|
+
code: error.code,
|
|
27
|
+
resolution: error.resolution,
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: false, error: errorMessage }) }],
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=utils.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mindstone-engineering/mcp-server-runway",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Runway ML MCP server for Model Context Protocol hosts — AI video, image, audio generation, custom voices, task management",
|
|
5
|
+
"license": "FSL-1.1-MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-server-runway": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/**/*.map"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/nspr-io/mcp-servers.git",
|
|
17
|
+
"directory": "connectors/runway"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/nspr-io/mcp-servers/tree/main/connectors/runway",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc && shx chmod +x dist/index.js",
|
|
25
|
+
"prepare": "npm run build",
|
|
26
|
+
"watch": "tsc --watch",
|
|
27
|
+
"start": "node dist/index.js",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"test:coverage": "vitest run --coverage"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
34
|
+
"zod": "^3.23.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@mindstone-engineering/mcp-test-harness": "file:../../test-harness",
|
|
38
|
+
"@types/node": "^22",
|
|
39
|
+
"@vitest/coverage-v8": "^4.1.3",
|
|
40
|
+
"msw": "^2.13.2",
|
|
41
|
+
"shx": "^0.3.4",
|
|
42
|
+
"typescript": "^5.8.2",
|
|
43
|
+
"vitest": "^4.1.3"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
}
|
|
48
|
+
}
|