ross-ultrix-mcp/src/index.ts

337 lines
9.7 KiB
JavaScript

#!/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<RossUltrixState>) {
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);