@iflow-mcp/chess-mcp
Version:
A Model Context Protocol (MCP) server that provides an interactive chess game experience using MCP-UI. This server exposes chess game functionality through MCP tools and provides a web-based chessboard interface for playing games.
135 lines (120 loc) • 3.42 kB
text/typescript
import express, {
type Request,
type RequestHandler,
type Response,
} from "express";
import { randomUUID } from "node:crypto";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import header from "./header";
import { validUUID } from "./uuid";
export function Middleware(
newServer: (
req: Request,
res: Response,
transport: StreamableHTTPServerTransport,
) => Promise<McpServer> | McpServer,
): RequestHandler {
const middleware = new McpServerMiddleware(newServer);
return middleware.middleware;
}
class McpServerMiddleware {
private readonly transports: Record<string, StreamableHTTPServerTransport> =
{};
private readonly app = express();
private readonly newServer: (
req: Request,
res: Response,
transport: StreamableHTTPServerTransport,
) => Promise<McpServer> | McpServer;
get middleware(): RequestHandler {
return this.app;
}
constructor(
newServer: (
req: Request,
res: Response,
transport: StreamableHTTPServerTransport,
) => Promise<McpServer> | McpServer,
) {
this.newServer = newServer;
this.setup();
}
private setup = () => {
this.app.use(express.json());
this.app.post("/", this.post);
this.app.get("/", this.post);
this.app.delete("/", this.getDelete);
};
private newTransport = async (
sessionId: string | undefined,
req: Request,
res: Response,
) => {
let transport: StreamableHTTPServerTransport;
if (sessionId) {
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
transport.sessionId = sessionId;
this.transports[sessionId] = transport;
transport.onclose = () => {
if (transport.sessionId) {
delete this.transports[transport.sessionId];
}
};
} else {
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
this.transports[sessionId] = transport;
},
});
}
transport.onclose = () => {
if (transport.sessionId) {
delete this.transports[transport.sessionId];
}
};
const server = await this.newServer(req, res, transport);
await server.connect(transport);
return transport;
};
private post = async (req: Request, res: Response) => {
const sessionId = header(req.headers, "mcp-session-id");
let transport: StreamableHTTPServerTransport;
if (sessionId && this.transports[sessionId]) {
transport = this.transports[sessionId];
} else if (
(!sessionId && isInitializeRequest(req.body)) ||
validUUID(sessionId)
) {
transport = await this.newTransport(sessionId, req, res);
} else {
res.status(404).json({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Bad Request: No valid session ID provided",
},
id: null,
});
return;
}
await transport.handleRequest(req, res, req.body);
};
private getDelete = async (req: Request, res: Response) => {
const sessionId = header(req.headers, "mcp-session-id");
const transport = sessionId && this.transports[sessionId];
if (!transport) {
res
.status(400)
.send(
`Invalid or missing session ID: ${sessionId ? sessionId.slice(0, 8) : "none"}`,
);
return;
}
await transport.handleRequest(req, res);
};
}