index.ts•8.66 kB
#!/usr/bin/env node
/**
* Restream MCP Server
* Provides Model Context Protocol tools for interacting with Restream API
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import dotenv from 'dotenv';
import { RestreamClient } from './restream-client.js';
import type { RestreamConfig } from './types.js';
// Load environment variables
dotenv.config();
// Validate required environment variables
const requiredEnvVars = ['RESTREAM_CLIENT_ID', 'RESTREAM_CLIENT_SECRET'];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
console.error(`Error: Missing required environment variable: ${envVar}`);
process.exit(1);
}
}
// Initialize Restream client
const config: RestreamConfig = {
clientId: process.env.RESTREAM_CLIENT_ID!,
clientSecret: process.env.RESTREAM_CLIENT_SECRET!,
baseUrl: process.env.RESTREAM_API_BASE_URL || 'https://api.restream.io/v2',
};
const restreamClient = new RestreamClient(config);
// Define MCP tools
const tools: Tool[] = [
{
name: 'get_user_profile',
description: 'Get the authenticated user profile information including email, display name, and account details',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'list_channels',
description: 'List all connected streaming channels/platforms (YouTube, Twitch, Facebook, etc.) with their connection status',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'get_channel',
description: 'Get detailed information about a specific channel by ID',
inputSchema: {
type: 'object',
properties: {
channelId: {
type: 'string',
description: 'The ID of the channel to retrieve',
},
},
required: ['channelId'],
},
},
{
name: 'update_channel_status',
description: 'Enable or disable a specific streaming channel',
inputSchema: {
type: 'object',
properties: {
channelId: {
type: 'string',
description: 'The ID of the channel to update',
},
enabled: {
type: 'boolean',
description: 'Whether to enable (true) or disable (false) the channel',
},
},
required: ['channelId', 'enabled'],
},
},
{
name: 'get_current_stream',
description: 'Get information about the current/active stream including title, status, RTMP URL, and viewer count',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'update_stream_settings',
description: 'Update settings for the current stream such as title, description, or privacy settings',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'The stream title',
},
description: {
type: 'string',
description: 'The stream description',
},
privacy: {
type: 'string',
enum: ['public', 'private', 'unlisted'],
description: 'The stream privacy setting',
},
},
},
},
{
name: 'get_stream_analytics',
description: 'Get analytics and statistics for streams including viewer counts, engagement metrics, and performance data',
inputSchema: {
type: 'object',
properties: {
streamId: {
type: 'string',
description: 'Optional stream ID to get analytics for a specific stream. If not provided, returns analytics for the current user',
},
},
},
},
{
name: 'start_stream',
description: 'Start a new stream with optional settings',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'The stream title',
},
description: {
type: 'string',
description: 'The stream description',
},
privacy: {
type: 'string',
enum: ['public', 'private', 'unlisted'],
description: 'The stream privacy setting',
},
},
},
},
{
name: 'stop_stream',
description: 'Stop the current active stream',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
];
// Create MCP server
const server = new Server(
{
name: 'restream-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'get_user_profile': {
const profile = await restreamClient.getUserProfile();
return {
content: [
{
type: 'text',
text: JSON.stringify(profile, null, 2),
},
],
};
}
case 'list_channels': {
const channels = await restreamClient.getChannels();
return {
content: [
{
type: 'text',
text: JSON.stringify(channels, null, 2),
},
],
};
}
case 'get_channel': {
if (!args || typeof args.channelId !== 'string') {
throw new Error('channelId is required');
}
const channel = await restreamClient.getChannel(args.channelId);
return {
content: [
{
type: 'text',
text: JSON.stringify(channel, null, 2),
},
],
};
}
case 'update_channel_status': {
if (!args || typeof args.channelId !== 'string' || typeof args.enabled !== 'boolean') {
throw new Error('channelId and enabled are required');
}
const channel = await restreamClient.updateChannelStatus(args.channelId, args.enabled);
return {
content: [
{
type: 'text',
text: JSON.stringify(channel, null, 2),
},
],
};
}
case 'get_current_stream': {
const stream = await restreamClient.getCurrentStream();
return {
content: [
{
type: 'text',
text: stream ? JSON.stringify(stream, null, 2) : 'No active stream',
},
],
};
}
case 'update_stream_settings': {
if (!args) {
throw new Error('At least one setting is required');
}
const stream = await restreamClient.updateStreamSettings(args);
return {
content: [
{
type: 'text',
text: JSON.stringify(stream, null, 2),
},
],
};
}
case 'get_stream_analytics': {
const analytics = await restreamClient.getStreamAnalytics(
args && typeof args.streamId === 'string' ? args.streamId : undefined
);
return {
content: [
{
type: 'text',
text: JSON.stringify(analytics, null, 2),
},
],
};
}
case 'start_stream': {
const stream = await restreamClient.startStream(args || {});
return {
content: [
{
type: 'text',
text: JSON.stringify(stream, null, 2),
},
],
};
}
case 'stop_stream': {
await restreamClient.stopStream();
return {
content: [
{
type: 'text',
text: 'Stream stopped successfully',
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Restream MCP Server running on stdio');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});