MCP Relay Server
by emiliobool
- MCP-Relay
- src
import { Client, TextChannel, Message, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction } from 'discord.js';
export interface RelayConfig {
discordToken: string;
channelId: string;
}
export interface RelayMessage {
type: 'prompt' | 'notification';
title: string;
content: string;
actions?: Array<{
label: string;
value: string;
}>;
timeout?: number;
}
export interface RelayResponse {
success: boolean;
error?: string;
response?: string;
}
export class DiscordRelay {
private client: Client;
private config: RelayConfig;
private responseMap: Map<string, {
resolve: (response: RelayResponse) => void;
timer?: NodeJS.Timeout;
}>;
constructor(config: RelayConfig) {
this.config = config;
this.client = new Client({
intents: ['Guilds', 'GuildMessages', 'MessageContent']
});
this.responseMap = new Map();
}
async initialize(): Promise<void> {
try {
await this.client.login(this.config.discordToken);
this.setupResponseHandling();
console.error('Discord relay initialized successfully');
} catch (error) {
throw new Error(`Failed to initialize Discord client: ${error}`);
}
}
async sendMessage(message: RelayMessage): Promise<RelayResponse> {
try {
const channel = await this.client.channels.fetch(this.config.channelId);
if (!channel || !(channel instanceof TextChannel)) {
throw new Error('Invalid channel or channel not found');
}
const embed = new EmbedBuilder()
.setTitle(message.title)
.setDescription(message.content)
.setColor(message.type === 'notification' ? 0x00ff00 : 0x0099ff)
.setTimestamp();
const components = [];
if (message.actions && message.actions.length > 0) {
const row = new ActionRowBuilder<ButtonBuilder>();
message.actions.forEach((action, index) => {
row.addComponents(
new ButtonBuilder()
.setCustomId(`action_${index}`)
.setLabel(action.label)
.setStyle(ButtonStyle.Primary)
);
});
components.push(row);
}
const discordMessage = await channel.send({
embeds: [embed],
components: components
});
if (message.type === 'notification') {
return { success: true };
}
return new Promise((resolve) => {
this.responseMap.set(discordMessage.id, {
resolve,
timer: message.timeout ? setTimeout(() => {
this.clearResponse(discordMessage.id);
resolve({
success: false,
error: 'Response timeout'
});
}, message.timeout) : undefined
});
});
} catch (error) {
return {
success: false,
error: `Failed to send message: ${error}`
};
}
}
private setupResponseHandling() {
this.client.on('messageCreate', (message: Message) => {
if (message.channel.id !== this.config.channelId || message.author.bot) {
return;
}
// Find the most recent message that's waiting for a response
const entries = Array.from(this.responseMap.entries());
const lastEntry = entries[entries.length - 1];
if (lastEntry) {
const [messageId, handler] = lastEntry;
this.clearResponse(messageId);
handler.resolve({
success: true,
response: message.content
});
}
});
this.client.on('interactionCreate', async (interaction) => {
if (!interaction.isButton()) return;
const messageId = interaction.message.id;
const handler = this.responseMap.get(messageId);
if (handler) {
const actionIndex = parseInt(interaction.customId.split('_')[1]);
const message = interaction.message;
const embed = message.embeds[0];
if (embed && message.components) {
const actions = message.components[0].components
.filter(comp => comp.type === 2) // Button type
.map((comp: any) => ({
label: comp.label,
value: comp.customId
}));
if (actions[actionIndex]) {
this.clearResponse(messageId);
handler.resolve({
success: true,
response: actions[actionIndex].label
});
}
}
await interaction.deferUpdate();
}
});
}
private clearResponse(messageId: string) {
const handler = this.responseMap.get(messageId);
if (handler?.timer) {
clearTimeout(handler.timer);
}
this.responseMap.delete(messageId);
}
async destroy() {
this.client.destroy();
}
}