sia-vision-mcp-server
Version:
Enhanced v2 MCP server with improved error handling, validation, and comprehensive tool schemas for SIA.Vision storytelling platform
589 lines • 27.2 kB
JavaScript
// Shared enums/constants to keep schemas in sync and avoid drift
const CORE_NODE_TYPES = [
'STORYWORLD',
'SCENE',
'NOTE',
'BOARD',
'VISUAL',
'ASSET',
'CHARACTER',
'LOCATION',
'PROMPT',
];
const SEARCHABLE_NODE_TYPES = [
'SCENE',
'CHARACTER',
'LOCATION',
'NOTE',
'VISUAL',
'ASSET',
'PROMPT',
'BOARD',
];
const REL_TYPES = [
'related_to',
'contains',
'belongs_to',
'next_in_sequence',
'references',
'referenced_by',
'appears_in',
'character_appears_in',
'located_in',
'uses_asset',
'derived_from',
'generates',
'parent_child',
];
// Optional mapping helpers for UI terms → API schema
export const UI_TO_API_TYPE = {
scene: { nodeType: 'SCENE' },
character: { nodeType: 'CHARACTER' },
location: { nodeType: 'LOCATION' },
visual: { nodeType: 'VISUAL' },
prompt: { nodeType: 'PROMPT' },
notes: { nodeType: 'NOTE' },
song: { nodeType: 'ASSET', metadata: { assetSubtype: 'song' } },
section: { nodeType: 'BOARD', metadata: { boardKind: 'section' } },
collection: { nodeType: 'BOARD', metadata: { boardKind: 'collection' } },
};
// Enhanced v2 core tools for SIA Vision storytelling platform
// All tools proxy execution to Firebase Functions with comprehensive validation
export const v2CoreTools = [
{
name: 'node_create',
description: 'Create a new content node in the hierarchical story structure. UI mapping: scene→SCENE, character→CHARACTER, location→LOCATION, visual→VISUAL, prompt→PROMPT, notes→NOTE, song→ASSET{assetSubtype:"song"}, section→BOARD{boardKind:"section"}, collection→BOARD{boardKind:"collection"}. For SCENE you may include metadata.slugline { intExt, location, timeOfDay }. Returns nodeId, storyworldId, and hierarchyPath.',
inputSchema: {
type: 'object',
properties: {
storyworldId: {
type: 'string',
description: 'Required for all nodes except STORYWORLD. Get this from get_content_hierarchy or a previously created STORYWORLD node. Example: "507f1f77bcf86cd799439011"'
},
nodeType: {
type: 'string',
enum: [...CORE_NODE_TYPES],
description: 'Choose the content type: STORYWORLD (top-level project), SCENE (individual scene), NOTE (text note), BOARD (mood board/visual board), VISUAL (image/visual asset), ASSET (media file), CHARACTER (cast member), LOCATION (setting), PROMPT (AI prompt/script)'
},
title: {
type: 'string',
minLength: 1,
maxLength: 200,
description: 'Descriptive title that will be visible in the UI. For scenes, this becomes the scene heading. Examples: "The Mysterious Stranger", "INT. COFFEE SHOP - DAY", "Sarah Chen"'
},
parentId: {
type: 'string',
description: 'Parent node ID for hierarchy. SCENE nodes should be children of STORYWORLD. CHARACTER/LOCATION nodes can be at storyworld level. Get parent IDs from get_content_hierarchy.'
},
content: {
type: 'string',
description: 'Initial description or content. For scenes, this can be the initial script content. Supports basic formatting and will be editable in the scene editor.'
},
metadata: {
type: 'object',
description: 'Structured data by nodeType. SCENE: { slugline: { intExt: "INT."|"EXT."|"INT/EXT.", location: string, timeOfDay: "DAY"|"NIGHT"|... }, genre?, tone?, duration? }. BOARD: { boardKind: "section"|"collection" }. ASSET: { assetSubtype?: "song"|string, fileType?, dimensions? }. CHARACTER/LOCATION: { traits?, age?, role? }.',
properties: {
slugline: {
type: 'object',
properties: {
intExt: { type: 'string', enum: ['INT.', 'EXT.', 'INT/EXT.'] },
location: { type: 'string' },
timeOfDay: { type: 'string', enum: ['DAY', 'NIGHT', 'DAWN', 'DUSK', 'MORNING', 'EVENING'] }
}
},
boardKind: { type: 'string', enum: ['section', 'collection'] },
assetSubtype: { type: 'string', description: 'For ASSET, set to "song" to align with UI song cards.' }
}
},
idempotencyKey: {
type: 'string',
description: 'Prevents duplicate creation. Use consistent keys like "storyworld-main" or "character-john-doe" to avoid accidental duplicates during regeneration.'
}
},
required: ['nodeType', 'title']
}
},
{
name: 'node_update',
description: 'Update mutable fields on an existing node including title and metadata with optimistic versioning. For SCENE slugline updates, set metadata.slugline = { intExt, location, timeOfDay } to keep UI in sync.',
inputSchema: {
type: 'object',
properties: {
nodeId: {
type: 'string',
description: 'ObjectId of the node to update. Must be accessible by the current user.'
},
title: {
type: 'string',
minLength: 1,
maxLength: 200,
description: 'New title for the node. Will update display name across all references.'
},
content: {
type: 'string',
description: 'Updated content/description. Supports markdown and rich text formatting.'
},
metadata: {
type: 'object',
description: 'Updated metadata object. Will merge with existing metadata, replacing matching keys.'
}
},
required: ['nodeId']
}
},
{
name: 'node_delete',
description: 'Delete a node and its descendants; cleans up associated documents',
inputSchema: {
type: 'object',
properties: {
nodeId: { type: 'string' }
},
required: ['nodeId']
}
},
{
name: 'get_content_hierarchy',
description: 'Retrieve the complete hierarchical structure of a storyworld including series, episodes, scenes, and content statistics. Essential for understanding story organization before making changes.',
inputSchema: {
type: 'object',
properties: {
storyworld_id: {
type: 'string',
description: 'ObjectId of the storyworld to retrieve. Must be accessible by the current user.'
},
include_counts: {
type: 'boolean',
default: true,
description: 'Whether to include statistics (episode counts, scene counts) at each hierarchy level.'
},
max_depth: {
type: 'number',
default: 4,
enum: [1, 2, 3, 4],
description: 'Maximum depth to traverse: 1=storyworld only, 2=+series, 3=+episodes, 4=+scenes (recommended).'
}
},
required: ['storyworld_id']
}
},
{
name: 'reorder_nodes',
description: 'Reorder sibling nodes by assigning new order keys',
inputSchema: {
type: 'object',
properties: {
nodeIds: { type: 'array', items: { type: 'string' } },
orderKeys: { type: 'array', items: { type: 'string' } },
},
required: ['nodeIds', 'orderKeys']
}
},
{
name: 'move_node',
description: 'Move a node to a new parent and/or position',
inputSchema: {
type: 'object',
properties: {
nodeId: { type: 'string' },
newParentId: { type: 'string' },
newOrderKey: { type: 'string' },
},
required: ['nodeId']
}
},
{
name: 'nodes_get',
description: 'Query nodes by storyworld, parent, and type with ordering and field selection',
inputSchema: {
type: 'object',
properties: {
storyworldId: { type: 'string' },
parentId: { type: 'string' },
nodeTypes: {
type: 'array',
items: {
type: 'string',
enum: [...CORE_NODE_TYPES],
},
description: 'Filter by specific node types. Leave empty to get all types.'
},
fields: { type: 'array', items: { type: 'string' } },
limit: { type: 'number', default: 100 },
orderBy: { type: 'string', enum: ['orderKey', 'createdAt', 'updatedAt'], default: 'orderKey' },
orderDirection: { type: 'string', enum: ['asc', 'desc'], default: 'asc' }
},
required: ['storyworldId']
}
},
{
name: 'document_ensure',
description: 'Ensure a TipTap document exists for a node. Creates document storage for content-editable node types: SCENE, NOTE, CHARACTER, LOCATION. Excludes STORYWORLD, BOARD, VISUAL, ASSET, PROMPT which use other storage mechanisms.',
inputSchema: {
type: 'object',
properties: {
nodeId: {
type: 'string',
description: 'ObjectId of the node that needs document storage. Must be one of: SCENE, NOTE, CHARACTER, LOCATION.'
}
},
required: ['nodeId']
}
},
{
name: 'get_document_view',
description: 'Retrieve the current view of a script document with addressable blocks',
inputSchema: {
type: 'object',
properties: { document_id: { type: 'string' } },
required: ['document_id']
}
},
{
name: 'apply_document_patch',
description: 'Apply RFC-6902 JSON Patch operations to modify scene documents with version control. This is the primary way to edit scene content. Supports adding text blocks, dialogue, action lines, character directions, and transitions. Always include a version test operation to prevent conflicts during collaborative editing.',
inputSchema: {
type: 'object',
properties: {
document_id: {
type: 'string',
description: 'ObjectId of the document to modify. Get this from node creation response or get_document_view. Example: "507f1f77bcf86cd799439011"'
},
version: {
type: 'number',
minimum: 1,
description: 'Current version number from get_document_view. If version mismatch occurs, refetch document and retry with correct version.'
},
ops: {
type: 'array',
minItems: 1,
items: {
type: 'object',
properties: {
op: {
type: 'string',
enum: ['test', 'add', 'remove', 'replace', 'move', 'copy'],
description: 'Operation type: "test" (validate version), "add" (insert content), "replace" (modify content), "remove" (delete content)'
},
path: {
type: 'string',
description: 'JSON Pointer path: "/content/0" (first block), "/content/-" (append), "/content/2/content" (specific block content)'
},
value: {
description: 'Content to add/replace. Use block objects like: {"id": "unique-id", "type": "paragraph", "content": "dialogue text"} or {"id": "unique-id", "type": "heading2", "content": "INT. LOCATION - DAY"}'
},
from: {
type: 'string',
description: 'Source path for move/copy operations. Rarely used for scene editing.'
}
},
required: ['op', 'path']
},
description: 'Operations array. Always start with {"op": "test", "path": "/version", "value": current_version} to prevent conflicts.'
}
},
required: ['document_id', 'version', 'ops']
}
},
{
name: 'create_scene_with_slugline',
description: 'Create a SCENE with proper slugline (INT./EXT., LOCATION, TIME OF DAY) and optional initial content blocks. Also writes the heading block in the document so the UI shows the slugline.',
inputSchema: {
type: 'object',
properties: {
storyworldId: {
type: 'string',
description: 'Storyworld ObjectId where scene will be created. Get from get_content_hierarchy.'
},
parentId: {
type: 'string',
description: 'Episode ObjectId to contain this scene. Required for proper hierarchy.'
},
title: {
type: 'string',
description: 'Scene title that will appear in the outline. Keep it descriptive but concise.'
},
slugline: {
type: 'object',
properties: {
intExt: { type: 'string', enum: ['INT.', 'EXT.', 'INT/EXT.'], description: 'Interior/Exterior indicator' },
location: { type: 'string', description: 'Location name in ALL CAPS (e.g., "COFFEE SHOP", "SARAH\'S APARTMENT")' },
timeOfDay: { type: 'string', enum: ['DAY', 'NIGHT', 'DAWN', 'DUSK', 'MORNING', 'EVENING'], description: 'Time of day' }
},
required: ['intExt', 'location', 'timeOfDay'],
description: 'Screenplay slugline structure. Will be formatted as "INT. LOCATION - TIME OF DAY"'
},
initialContent: {
type: 'array',
description: 'Initial scene content as array of block objects. Include action, dialogue, character names, etc.',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique block ID (use random strings)' },
type: { type: 'string', enum: ['heading2', 'paragraph', 'character', 'dialogue', 'transition'], description: 'Block type' },
content: { type: 'string', description: 'Block text content' }
},
required: ['id', 'type', 'content']
}
},
metadata: {
type: 'object',
description: 'Scene metadata including genre, tone, estimated duration, etc.',
properties: {
genre: { type: 'string', description: 'Scene genre/tone (e.g., "tense", "romantic", "action")' },
duration: { type: 'string', description: 'Estimated duration (e.g., "2:30", "5 minutes")' },
characters: { type: 'array', items: { type: 'string' }, description: 'Character names appearing in scene' },
locations: { type: 'array', items: { type: 'string' }, description: 'Locations referenced in scene' }
}
}
},
required: ['storyworldId', 'parentId', 'title', 'slugline']
}
},
{
name: 'update_document',
description: 'Update a document content with version control (RFC-6902 compatible). Content is a JSON string (block array) matching the editor format.',
inputSchema: {
type: 'object',
properties: {
documentId: { type: 'string' },
content: { type: 'string' },
version: { type: 'number' }
},
required: ['documentId', 'content', 'version']
}
},
{
name: 'create_relationship',
description: 'Create a relationship between two nodes within a storyworld',
inputSchema: {
type: 'object',
properties: {
storyworldId: { type: 'string' },
sourceId: { type: 'string' },
targetId: { type: 'string' },
relType: {
type: 'string',
enum: [...REL_TYPES],
},
metadata: { type: 'object' }
},
required: ['storyworldId', 'sourceId', 'targetId', 'relType']
}
},
{
name: 'get_relationships',
description: 'Get relationships for a node or storyworld',
inputSchema: {
type: 'object',
properties: {
storyworldId: { type: 'string' },
nodeId: { type: 'string' },
relType: { type: 'string' },
direction: { type: 'string', enum: ['source', 'target', 'both'], default: 'both' },
includeNodes: { type: 'boolean', default: false },
limit: { type: 'number', default: 100 }
},
required: ['storyworldId']
}
},
{
name: 'get_related_nodes',
description: 'Get nodes related to a specific node via relationships',
inputSchema: {
type: 'object',
properties: {
nodeId: { type: 'string' },
relType: { type: 'string' },
direction: { type: 'string', enum: ['source', 'target', 'both'], default: 'both' },
depth: { type: 'number', default: 1, maximum: 3 },
includeContent: { type: 'boolean', default: false }
},
required: ['nodeId']
}
},
{
name: 'search_content',
description: 'Search through existing story content to find relevant scenes, characters, locations, or themes. Use this before creating new content to maintain consistency and avoid duplication. Returns matching nodes with excerpts and relevance scores.',
inputSchema: {
type: 'object',
properties: {
storyworldId: {
type: 'string',
description: 'Storyworld ObjectId to search within. Get from get_content_hierarchy.'
},
query: {
type: 'string',
description: 'Search query. Can be character names, locations, themes, dialogue snippets, or descriptive terms. Example: "coffee shop", "detective", "romantic tension"'
},
nodeTypes: {
type: 'array',
items: { type: 'string', enum: [...SEARCHABLE_NODE_TYPES] },
description: 'Filter by content types. Default searches all content types.'
},
limit: {
type: 'number',
default: 10,
maximum: 50,
description: 'Maximum results to return. Use higher numbers for broader searches.'
},
includeContent: {
type: 'boolean',
default: true,
description: 'Include content excerpts in results for context.'
}
},
required: ['storyworldId', 'query']
}
},
{
name: 'get_scene_context',
description: 'Get comprehensive context for a specific scene including previous/next scenes, related characters, locations, and themes. Essential for maintaining continuity when writing or editing scenes.',
inputSchema: {
type: 'object',
properties: {
sceneId: {
type: 'string',
description: 'Scene ObjectId to get context for. Get from get_content_hierarchy or node creation.'
},
includeAdjacentScenes: {
type: 'boolean',
default: true,
description: 'Include brief summaries of previous and next scenes in the episode.'
},
includeCharacters: {
type: 'boolean',
default: true,
description: 'Include character descriptions and relationships relevant to this scene.'
},
includeLocations: {
type: 'boolean',
default: true,
description: 'Include location descriptions referenced in this scene.'
},
includeThemes: {
type: 'boolean',
default: false,
description: 'Include thematic elements and motifs from the storyworld.'
}
},
required: ['sceneId']
}
},
{
name: 'validate_scene_content',
description: 'Validate scene content for screenplay formatting, character consistency, and story continuity. Checks for proper sluglines, dialogue formatting, character introductions, and plot consistency.',
inputSchema: {
type: 'object',
properties: {
sceneId: {
type: 'string',
description: 'Scene ObjectId to validate. Get from get_content_hierarchy.'
},
content: {
type: 'string',
description: 'Scene content as JSON string array. Get from get_document_view.'
},
checkFormatting: {
type: 'boolean',
default: true,
description: 'Validate screenplay formatting (sluglines, dialogue, transitions).'
},
checkContinuity: {
type: 'boolean',
default: true,
description: 'Check for character/location consistency with storyworld.'
},
checkPacing: {
type: 'boolean',
default: false,
description: 'Analyze scene pacing and suggest improvements.'
}
},
required: ['sceneId']
}
},
{
name: 'asset_upload_and_create',
description: 'Upload a base64-encoded file to Firebase Storage and create an ASSET node with metadata. Supports images, documents, and media files. Returns assetId, storagePath, and download URL for integration into documents.',
inputSchema: {
type: 'object',
properties: {
storyworldId: {
type: 'string',
description: 'ObjectId of the storyworld that will contain this asset.'
},
title: {
type: 'string',
minLength: 1,
maxLength: 200,
description: 'Descriptive title for the asset (e.g., "Character Portrait - Sarah Chen").'
},
fileBase64: {
type: 'string',
description: 'Base64-encoded file data with proper MIME type prefix (e.g., "data:image/png;base64,iVBORw0...").'
},
mimeType: {
type: 'string',
enum: ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'application/pdf', 'text/plain'],
description: 'MIME type of the uploaded file. Must match the actual file format.'
},
parentId: {
type: 'string',
description: 'Optional parent node ID (typically a scene, character, or location) for hierarchical organization.'
},
metadata: {
type: 'object',
description: 'Additional metadata such as AI generation details, usage context, or custom properties.'
}
},
required: ['storyworldId', 'title', 'fileBase64', 'mimeType']
}
},
{
name: 'asset_create',
description: 'Create an ASSET node for an existing file in Firebase Storage. Use when the file is already uploaded and you need to create the corresponding node with metadata.',
inputSchema: {
type: 'object',
properties: {
storyworldId: {
type: 'string',
description: 'ObjectId of the storyworld that will contain this asset.'
},
title: {
type: 'string',
minLength: 1,
maxLength: 200,
description: 'Descriptive title for the asset.'
},
mimeType: {
type: 'string',
description: 'MIME type of the file (e.g., "image/png", "application/pdf").'
},
fileSize: {
type: 'number',
minimum: 1,
description: 'Size of the file in bytes.'
},
storagePath: {
type: 'string',
description: 'Firebase Storage path where the file is located (e.g., "gs://bucket/path/file.png").'
},
parentId: {
type: 'string',
description: 'Optional parent node ID for hierarchical organization.'
},
metadata: {
type: 'object',
description: 'Additional metadata including SHA256 hash, creation details, or custom properties.'
},
sha256: {
type: 'string',
description: 'Optional SHA256 hash of the file for integrity verification.'
}
},
required: ['storyworldId', 'title', 'mimeType', 'fileSize', 'storagePath']
}
}
];
//# sourceMappingURL=v2-core-tools.js.map