// server.ts
import express from "express";
import process from "process";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { WebSocketServer } from "ws";
import { initializeConfig, getConfig } from "./src/config/index";
import { registerAllTools } from "./src/tools/index";
// Create MCP server
const server = new McpServer({
name: "chhotu-ai-pc-assistant",
version: "1.0.0",
});
async function start() {
// Initialize configuration and validate license
console.error("π Initializing AI PC Assistant MCP Server...");
const config = await initializeConfig();
if (!config.license.isValid) {
console.error(
"β License validation failed. Please obtain a valid license."
);
console.error(`π License file location: ${process.env.HOME}/.mcp-license`);
process.exit(1);
}
console.error(
`β
License valid for: ${config.license.licensee || "user"}`
);
console.error(
`π
Expires: ${config.license.expiresAt || "No expiration"}`
);
// Register all tools
console.error("π§ Registering tools...");
await registerAllTools(server);
console.error("β
15 tools registered");
// -------------------------------------------
// 1. STDIO Transport (for local CLI / Gemini)
// -------------------------------------------
try {
const stdio = new StdioServerTransport();
await server.connect(stdio);
console.error("STDIO MCP transport ready.");
} catch (err) {
console.warn("STDIO transport failed:", err);
}
// -------------------------------------------
// 2. HTTP Transport (Streamable HTTP MCP)
// -------------------------------------------
const app = express();
app.use(express.json({ limit: "10mb" }));
const httpTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => "default",
enableJsonResponse: true,
});
// Connect MCP server to HTTP transport
await server.connect(httpTransport);
// MCP endpoint
app.all("/mcp", async (req, res) => {
try {
if (!req.headers["mcp-session-id"]) {
req.headers["mcp-session-id"] = "default";
}
// Support discovery via SSE: some clients send a GET with
// `Accept: text/event-stream` to open a server-sent-events stream.
// In that case there will be no JSON body.
const accept = String(req.headers.accept || "");
if (req.method === "GET" && accept.includes("text/event-stream")) {
await httpTransport.handleRequest(req, res, undefined);
return;
}
// For POST/other requests, forward the parsed JSON body (may be undefined)
await httpTransport.handleRequest(req, res, req.body ?? undefined);
} catch (err) {
console.error("HTTP MCP error:", err);
if (!res.headersSent) {
res.status(500).json({ error: "Internal MCP error" });
}
}
});
// Health check
app.get("/health", (_req, res) => res.json({ status: "ok" }));
const port = parseInt(process.env.PORT || "3000", 10);
app.listen(port, () => {
console.error(`HTTP MCP available at http://127.0.0.1:${port}/mcp`);
});
// -------------------------------------------
// 3. WebSocket-to-HTTP MCP Bridge (ChatGPT)
// -------------------------------------------
const wss = new WebSocketServer({ port: 4000 });
wss.on("connection", (ws) => {
console.error("WebSocket MCP client connected");
ws.on("message", async (msg) => {
try {
const parsed = JSON.parse(msg.toString());
// Forward WS β HTTP
const response = await fetch(`http://127.0.0.1:${port}/mcp`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"mcp-session-id": "ws-session",
},
body: JSON.stringify(parsed),
});
const result = await response.json();
// Send back HTTP β WS
ws.send(JSON.stringify(result));
} catch (err) {
console.error("WS wrapper error:", err);
}
});
});
console.error("WebSocket MCP running at ws://127.0.0.1:4000");
}
start().catch((err) => {
console.error("Fatal MCP startup error:", err);
process.exit(1);
});