Add core MCP server implementation and Ross Talk protocol handler: index.ts
This commit is contained in:
parent
536aedf750
commit
b9f387b53f
1 changed files with 337 additions and 0 deletions
337
src/index.ts
Normal file
337
src/index.ts
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
#!/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);
|
||||
Loading…
Reference in a new issue