162 lines
5.4 KiB
TypeScript
162 lines
5.4 KiB
TypeScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
import express, { Request, Response } from "express";
|
|
import { toolList } from './toolList.js';
|
|
import {
|
|
createToolContext,
|
|
loginHandler,
|
|
sendMessageHandler,
|
|
getForumChannelsHandler,
|
|
createForumPostHandler,
|
|
getForumPostHandler,
|
|
replyToForumHandler,
|
|
deleteForumPostHandler,
|
|
createTextChannelHandler,
|
|
deleteChannelHandler,
|
|
readMessagesHandler,
|
|
getServerInfoHandler,
|
|
addReactionHandler,
|
|
addMultipleReactionsHandler,
|
|
removeReactionHandler,
|
|
deleteMessageHandler,
|
|
createWebhookHandler,
|
|
sendWebhookMessageHandler,
|
|
editWebhookHandler,
|
|
deleteWebhookHandler,
|
|
editCategoryHandler,
|
|
createCategoryHandler,
|
|
deleteCategoryHandler,
|
|
listServersHandler,
|
|
searchMessagesHandler
|
|
} from './tools/tools.js';
|
|
import { Client, GatewayIntentBits } from "discord.js";
|
|
import { info, error } from './logger.js';
|
|
|
|
export interface MCPTransport {
|
|
start(server: Server): Promise<void>;
|
|
stop(): Promise<void>;
|
|
}
|
|
|
|
export class StdioTransport implements MCPTransport {
|
|
private transport: StdioServerTransport | null = null;
|
|
|
|
async start(server: Server): Promise<void> {
|
|
this.transport = new StdioServerTransport();
|
|
await server.connect(this.transport);
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
if (this.transport) {
|
|
await this.transport.close();
|
|
this.transport = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class StreamableHttpTransport implements MCPTransport {
|
|
private app: express.Application;
|
|
private server: Server | null = null;
|
|
private httpServer: any = null;
|
|
private transport: StreamableHTTPServerTransport | null = null;
|
|
private toolContext: ReturnType<typeof createToolContext> | null = null;
|
|
private sessionId: string = '';
|
|
|
|
constructor(private port: number = 8080) {
|
|
this.app = express();
|
|
this.app.use(express.json());
|
|
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
info(`Created HTTP transport with session ID: ${this.sessionId}`);
|
|
}
|
|
|
|
async start(server: Server): Promise<void> {
|
|
this.server = server;
|
|
info('Starting HTTP transport with server: ' + String(!!this.server));
|
|
|
|
// Try to get client from the DiscordMCPServer instance
|
|
if (server) {
|
|
const anyServer = server as any;
|
|
let client: Client | undefined;
|
|
|
|
if (anyServer._context?.client) {
|
|
client = anyServer._context.client;
|
|
info('Found client in server._context');
|
|
}
|
|
else if (anyServer.client instanceof Client) {
|
|
client = anyServer.client;
|
|
info('Found client directly on server object');
|
|
}
|
|
else if (anyServer._parent?.client instanceof Client) {
|
|
client = anyServer._parent.client;
|
|
info('Found client in server._parent');
|
|
}
|
|
|
|
if (client) {
|
|
this.toolContext = createToolContext(client);
|
|
info('Tool context initialized with Discord client');
|
|
} else {
|
|
info('Creating new Discord client for transport');
|
|
const newClient = new Client({
|
|
intents: [
|
|
GatewayIntentBits.Guilds,
|
|
GatewayIntentBits.GuildMessages,
|
|
GatewayIntentBits.MessageContent,
|
|
GatewayIntentBits.GuildMessageReactions
|
|
]
|
|
});
|
|
this.toolContext = createToolContext(newClient);
|
|
info('Tool context initialized with new Discord client');
|
|
}
|
|
}
|
|
|
|
// Create transport
|
|
this.transport = new StreamableHTTPServerTransport({
|
|
sessionIdGenerator: undefined // stateless
|
|
});
|
|
|
|
// Connect the transport
|
|
await this.server.connect(this.transport);
|
|
info('Transport connected');
|
|
|
|
// Setup /mcp endpoint to use transport.handleRequest()
|
|
this.app.all('/mcp', async (req: Request, res: Response) => {
|
|
try {
|
|
await this.transport!.handleRequest(req, res, req.body);
|
|
} catch (err) {
|
|
error('Error handling MCP request: ' + String(err));
|
|
if (!res.headersSent) {
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
}
|
|
});
|
|
|
|
return new Promise((resolve) => {
|
|
this.httpServer = this.app.listen(this.port, '0.0.0.0', () => {
|
|
info(`MCP Server listening on 0.0.0.0:${this.port}`);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
if (this.transport) {
|
|
await this.transport.close();
|
|
this.transport = null;
|
|
}
|
|
|
|
if (this.server) {
|
|
await this.server.close();
|
|
this.server = null;
|
|
}
|
|
|
|
if (this.httpServer) {
|
|
return new Promise((resolve) => {
|
|
this.httpServer.close(() => {
|
|
info('HTTP server closed');
|
|
this.httpServer = null;
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|