#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { RossTalkClient } from './ross-talk.js'; import { RossUltrixState, PanelStatus, InputStatus } from './types.js'; class RossUltrixMCPServer { private server: Server; private rossTalk: RossTalkClient; private state: RossUltrixState = { connected: false, panels: {}, inputs: {}, lastUpdate: new Date() }; constructor() { this.server = new Server( { name: 'ross-ultrix-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.rossTalk = new RossTalkClient({ host: process.env.ROSS_TALK_HOST || '192.168.1.100', port: parseInt(process.env.ROSS_TALK_PORT || '7788'), onStateChange: (state) => this.updateState(state) }); this.setupHandlers(); } private updateState(newState: Partial) { this.state = { ...this.state, ...newState, lastUpdate: new Date() }; } private setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'ross_connect', description: 'Connect to Ross Ultrix system via Ross Talk protocol', }, { name: 'ross_disconnect', description: 'Disconnect from Ross Ultrix system', }, { name: 'ross_get_status', description: 'Get current system status and panel states', }, { name: 'ross_switch_panel', description: 'Switch to a specific panel on the Ultrix dashboard', inputSchema: { type: 'object', properties: { panelId: { type: 'string', description: 'Panel ID to switch to (e.g., "ME1", "AUX1")', }, }, required: ['panelId'], }, }, { name: 'ross_take_transition', description: 'Execute a take/cut transition on the specified mix effect', inputSchema: { type: 'object', properties: { meId: { type: 'string', description: 'Mix Effect ID (e.g., "ME1", "ME2")', default: 'ME1' }, }, }, }, { name: 'ross_auto_transition', description: 'Execute an auto transition with specified duration', inputSchema: { type: 'object', properties: { meId: { type: 'string', description: 'Mix Effect ID', default: 'ME1' }, duration: { type: 'number', description: 'Transition duration in frames', default: 30 }, }, }, }, { name: 'ross_set_preview', description: 'Set preview source on specified mix effect', inputSchema: { type: 'object', properties: { meId: { type: 'string', description: 'Mix Effect ID', default: 'ME1' }, sourceId: { type: 'string', description: 'Source ID to set on preview', }, }, required: ['sourceId'], }, }, { name: 'ross_set_program', description: 'Set program source on specified mix effect', inputSchema: { type: 'object', properties: { meId: { type: 'string', description: 'Mix Effect ID', default: 'ME1' }, sourceId: { type: 'string', description: 'Source ID to set on program', }, }, required: ['sourceId'], }, }, { name: 'ross_get_sources', description: 'Get list of available video sources', }, { name: 'ross_get_panels', description: 'Get list of available control panels', }, { name: 'ross_macro_run', description: 'Execute a macro by name or ID', inputSchema: { type: 'object', properties: { macroId: { type: 'string', description: 'Macro name or ID to execute', }, }, required: ['macroId'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'ross_connect': await this.rossTalk.connect(); return { content: [ { type: 'text', text: `Connected to Ross Ultrix at ${this.rossTalk.getHost()}:${this.rossTalk.getPort()}`, }, ], }; case 'ross_disconnect': await this.rossTalk.disconnect(); return { content: [ { type: 'text', text: 'Disconnected from Ross Ultrix', }, ], }; case 'ross_get_status': const status = await this.rossTalk.getSystemStatus(); return { content: [ { type: 'text', text: JSON.stringify({ connected: this.state.connected, systemStatus: status, lastUpdate: this.state.lastUpdate, panelCount: Object.keys(this.state.panels).length, inputCount: Object.keys(this.state.inputs).length }, null, 2), }, ], }; case 'ross_switch_panel': const { panelId } = args as { panelId: string }; await this.rossTalk.switchPanel(panelId); return { content: [ { type: 'text', text: `Switched to panel: ${panelId}`, }, ], }; case 'ross_take_transition': const { meId: takeMe = 'ME1' } = args as { meId?: string }; await this.rossTalk.takeTransition(takeMe); return { content: [ { type: 'text', text: `Take transition executed on ${takeMe}`, }, ], }; case 'ross_auto_transition': const { meId: autoMe = 'ME1', duration = 30 } = args as { meId?: string; duration?: number }; await this.rossTalk.autoTransition(autoMe, duration); return { content: [ { type: 'text', text: `Auto transition started on ${autoMe} with ${duration} frame duration`, }, ], }; case 'ross_set_preview': const { meId: prevMe = 'ME1', sourceId: prevSource } = args as { meId?: string; sourceId: string }; await this.rossTalk.setPreview(prevMe, prevSource); return { content: [ { type: 'text', text: `Preview set to ${prevSource} on ${prevMe}`, }, ], }; case 'ross_set_program': const { meId: progMe = 'ME1', sourceId: progSource } = args as { meId?: string; sourceId: string }; await this.rossTalk.setProgram(progMe, progSource); return { content: [ { type: 'text', text: `Program set to ${progSource} on ${progMe}`, }, ], }; case 'ross_get_sources': const sources = await this.rossTalk.getSources(); return { content: [ { type: 'text', text: JSON.stringify(sources, null, 2), }, ], }; case 'ross_get_panels': const panels = await this.rossTalk.getPanels(); return { content: [ { type: 'text', text: JSON.stringify(panels, null, 2), }, ], }; case 'ross_macro_run': const { macroId } = args as { macroId: string }; await this.rossTalk.runMacro(macroId); return { content: [ { type: 'text', text: `Macro executed: ${macroId}`, }, ], }; default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}` ); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new McpError( ErrorCode.InternalError, `Ross Ultrix operation failed: ${errorMessage}` ); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Ross Ultrix MCP server running on stdio'); } } const server = new RossUltrixMCPServer(); server.run().catch(console.error);