// src/server/ServerRunner.ts
import express, { Request, Response } from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { AppConfig } from "../config/AppConfig";
import { StoriesService } from "../services/StoriesService";
import { ServerConstants } from "../constants/ServerConstants";
import { Logger } from "../utils/logger";
/**
* Server Runner - manages server startup
*/
export class ServerRunner {
private readonly config: AppConfig;
private readonly storiesService: StoriesService;
private readonly mcpServer: McpServer;
constructor(
config: AppConfig,
storiesService: StoriesService,
mcpServer: McpServer
) {
this.config = config;
this.storiesService = storiesService;
this.mcpServer = mcpServer;
}
public async start(): Promise<void> {
if (this.config.shouldUseHttpTransport()) {
await this.startHttpServer();
} else {
await this.startStdioServer();
}
}
private async startHttpServer(): Promise<void> {
const port = this.config.getServerPort();
Logger.info(`🚀 Starting Jira MCP server on port ${port}...`);
const app = this.createExpressApp();
this.configureCors(app);
this.configureRoutes(app);
this.listen(app, port);
}
private async startStdioServer(): Promise<void> {
const transport = new StdioServerTransport();
Logger.success("Jira MCP server running with stdio transport");
this.logStoriesCount();
await this.mcpServer.connect(transport);
}
private createExpressApp(): express.Application {
const app = express();
app.use(express.json());
return app;
}
private configureCors(app: express.Application): void {
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", ServerConstants.CORS_ORIGIN);
res.header("Access-Control-Allow-Methods", ServerConstants.CORS_METHODS);
res.header("Access-Control-Allow-Headers", ServerConstants.CORS_HEADERS);
if (req.method === "OPTIONS") {
res.sendStatus(200);
} else {
next();
}
});
}
private configureRoutes(app: express.Application): void {
app.post(
ServerConstants.MCP_ENDPOINT,
async (req: Request, res: Response) => {
await this.handleMcpRequest(req, res);
}
);
}
private async handleMcpRequest(req: Request, res: Response): Promise<void> {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
Logger.debug(`MCP Request: ${req.body?.method || "no method"}`);
res.on("close", () => transport.close());
await this.mcpServer.connect(transport);
await transport.handleRequest(req, res, req.body);
}
private listen(app: express.Application, port: number): void {
app.listen(port, () => {
Logger.success(
`Jira MCP server running at http://localhost:${port}${ServerConstants.MCP_ENDPOINT}`
);
this.logStoriesCount();
});
}
private logStoriesCount(): void {
const count = this.storiesService.getStoriesCount();
Logger.info(`📊 Loaded ${count} stories`);
}
}