feat(examples): add SSE transport support and shared server utility (#136)

* feat(examples): add SSE transport support to all example servers

Add backwards-compatible SSE transport support alongside the current
Streamable HTTP transport. Each server now exposes:

- /mcp (GET, POST, DELETE) - Streamable HTTP transport (current spec)
- /sse (GET) - Legacy SSE transport stream endpoint
- /messages (POST) - Legacy SSE transport message endpoint

This enables older clients using the deprecated HTTP+SSE protocol
(version 2024-11-05) to connect to the example servers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(examples): migrate 6 servers to use shared server utility

Refactored the following example servers to use the shared startServer utility from examples/shared/server-utils.ts:
- threejs-server
- system-monitor-server
- cohort-heatmap-server
- budget-allocator-server
- customer-segmentation-server
- scenario-modeler-server

Changes for each server:
- Removed direct imports: SSEServerTransport, StdioServerTransport, StreamableHTTPServerTransport, cors, express
- Removed PORT constant (now handled by shared utility)
- Added import for startServer from ../shared/server-utils.js
- Replaced entire async main() function with single startServer() call
- Preserved all business logic (tool registration, resource registration, helper functions)

This reduces code duplication by ~700 lines and ensures consistent transport handling across all example servers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(examples): migrate remaining servers to shared utility

Complete the migration of example servers to use the shared
server-utils.ts module for transport setup:

- basic-server-react
- basic-server-vanillajs
- wiki-explorer-server
- Add shared/server-utils.ts

This centralizes all transport handling (stdio, Streamable HTTP, SSE)
in one place, reducing code duplication across ~500 lines.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(examples): improve server-utils with stateful sessions and security

Address critical issues in the shared server utility:

1. Stateful sessions: Use sessionIdGenerator + onsessioninitialized
   to persist StreamableHTTPServerTransport across requests
2. Unified session store: Single Map for both transport types with
   proper type discrimination
3. Error handling: Try/catch on all endpoints with JSON-RPC errors
4. DNS rebinding protection: Use SDK's createMcpExpressApp helper

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(examples): allow connections from any host for development

Use host: "0.0.0.0" in createMcpExpressApp to disable DNS rebinding
protection, allowing connections from Android emulators and other
devices on the network. Also re-add CORS middleware.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(examples): use getPort() helper for transport selection

- Add getPort() helper that returns undefined for --stdio, else port
- Caller now explicitly passes port to startServer()
- port: undefined → stdio mode, port: number → HTTP mode

This makes the transport selection explicit at the call site.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(examples): startServer returns promise that resolves/rejects on listen

Use httpServer.on('listening') and httpServer.on('error') events
to properly resolve or reject the returned promise.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(examples): add error handling to startServer calls

Exit with code 1 if server fails to start (e.g., port in use).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(examples): update server files to use main() pattern with stdio support

Update all example server files to use the new main() pattern that supports both HTTP and stdio transports:
- Add StdioServerTransport import
- Remove getPort import (replaced with direct env var parsing)
- Replace direct startServer call with async main() function
- Add --stdio flag detection to choose between transports
- Use PORT env var with default 3001 for HTTP mode

Updated servers:
- basic-server-vanillajs
- wiki-explorer-server
- threejs-server
- system-monitor-server
- cohort-heatmap-server
- budget-allocator-server
- customer-segmentation-server
- scenario-modeler-server

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(examples): use correct default ports matching run-all.ts

Each server now defaults to its assigned port from run-all.ts:
- basic-server-react: 3101
- basic-server-vanillajs: 3102
- budget-allocator-server: 3103
- cohort-heatmap-server: 3104
- customer-segmentation-server: 3105
- scenario-modeler-server: 3106
- system-monitor-server: 3107
- threejs-server: 3108
- wiki-explorer-server: 3109

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(examples): expose mcp-session-id header for CORS requests

Without exposedHeaders in the CORS config, browsers block JavaScript
from reading the mcp-session-id response header. This caused the SDK's
StreamableHTTPClientTransport to never capture the session ID, breaking
all subsequent requests with "Bad request: not initialized" errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* pretty:fix

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Olivier Chafik 2025-12-12 14:10:10 +01:00 • committed by GitHub
commit 3a30e94501
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 241 additions and 486 deletions

View file

@ -1,22 +1,18 @@
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 type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/server-utils.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const DIST_DIR = path.join(import.meta.dirname, "dist");
const server = new McpServer({
name: "Basic MCP App Server (React-based)",
version: "1.0.0",
});
// MCP Apps require two-part registration: a tool (what the LLM calls) and a
// resource (the UI it renders). The `_meta` field on the tool links to the
// resource URI, telling hosts which UI to display when the tool executes.
@ -57,49 +53,16 @@ const server = new McpServer({
);
}
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => { transport.close(); });
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
async function main() {
if (process.argv.includes("--stdio")) {
await server.connect(new StdioServerTransport());
} else {
const port = parseInt(process.env.PORT ?? "3101", 10);
await startServer(server, { port, name: "Basic MCP App Server (React-based)" });
}
});
const httpServer = app.listen(PORT, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log(`Server listening on http://localhost:${PORT}/mcp`);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
main().catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -1,22 +1,18 @@
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 type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/server-utils.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const DIST_DIR = path.join(import.meta.dirname, "dist");
const server = new McpServer({
name: "Basic MCP App Server (Vanilla JS)",
version: "1.0.0",
});
// MCP Apps require two-part registration: a tool (what the LLM calls) and a
// resource (the UI it renders). The `_meta` field on the tool links to the
// resource URI, telling hosts which UI to display when the tool executes.
@ -57,49 +53,16 @@ const server = new McpServer({
);
}
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => { transport.close(); });
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
async function main() {
if (process.argv.includes("--stdio")) {
await server.connect(new StdioServerTransport());
} else {
const port = parseInt(process.env.PORT ?? "3102", 10);
await startServer(server, { port, name: "Basic MCP App Server (Vanilla JS)" });
}
});
const httpServer = app.listen(PORT, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log(`Server listening on http://localhost:${PORT}/mcp`);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
main().catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -6,19 +6,16 @@
*/
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 type {
CallToolResult,
ReadResourceResult,
} from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/server-utils.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const DIST_DIR = path.join(import.meta.dirname, "dist");
// ---------------------------------------------------------------------------
@ -298,59 +295,14 @@ server.registerResource(
async function main() {
if (process.argv.includes("--stdio")) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Budget Allocator Server running in stdio mode");
await server.connect(new StdioServerTransport());
} else {
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(PORT, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log(
`Budget Allocator Server listening on http://localhost:${PORT}/mcp`,
);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const port = parseInt(process.env.PORT ?? "3103", 10);
await startServer(server, { port, name: "Budget Allocator Server" });
}
}
main().catch(console.error);
main().catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -1,15 +1,12 @@
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 type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/server-utils.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const DIST_DIR = path.join(import.meta.dirname, "dist");
// Schemas - types are derived from these using z.infer
@ -207,60 +204,14 @@ const server = new McpServer({
async function main() {
if (process.argv.includes("--stdio")) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Cohort Heatmap Server running in stdio mode");
await server.connect(new StdioServerTransport());
} else {
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(PORT, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log(
`Cohort Heatmap Server listening on http://localhost:${PORT}/mcp`,
);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const port = parseInt(process.env.PORT ?? "3104", 10);
await startServer(server, { port, name: "Cohort Heatmap Server" });
}
}
main().catch(console.error);
main().catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -1,23 +1,20 @@
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 type {
CallToolResult,
ReadResourceResult,
} from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/server-utils.js";
import {
generateCustomers,
generateSegmentSummaries,
} from "./src/data-generator.ts";
import { SEGMENTS, type Customer, type SegmentSummary } from "./src/types.ts";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const DIST_DIR = path.join(import.meta.dirname, "dist");
// Schemas - types are derived from these using z.infer
@ -106,60 +103,14 @@ const server = new McpServer({
async function main() {
if (process.argv.includes("--stdio")) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Customer Segmentation Server running in stdio mode");
await server.connect(new StdioServerTransport());
} else {
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(PORT, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log(
`Customer Segmentation Server listening on http://localhost:${PORT}/mcp`,
);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const port = parseInt(process.env.PORT ?? "3105", 10);
await startServer(server, { port, name: "Customer Segmentation Server" });
}
}
main().catch(console.error);
main().catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -1,18 +1,15 @@
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 type {
CallToolResult,
ReadResourceResult,
} from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/server-utils.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const DIST_DIR = path.join(import.meta.dirname, "dist");
// ============================================================================
@ -314,59 +311,14 @@ const server = new McpServer({
async function main() {
if (process.argv.includes("--stdio")) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("SaaS Scenario Modeler Server running in stdio mode");
await server.connect(new StdioServerTransport());
} else {
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(PORT, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log(
`SaaS Scenario Modeler Server listening on http://localhost:${PORT}/mcp`,
);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const port = parseInt(process.env.PORT ?? "3106", 10);
await startServer(server, { port, name: "SaaS Scenario Modeler Server" });
}
}
main().catch(console.error);
main().catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -0,0 +1,161 @@
/**
* Shared utilities for running MCP servers with HTTP transports.
*
* Supports:
* - Streamable HTTP transport (/mcp) - stateful sessions
* - Legacy SSE transport (/sse, /messages) - backwards compatibility
*/
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import { randomUUID } from "node:crypto";
import type { Request, Response } from "express";
export interface ServerOptions {
/** Port to listen on (required). */
port: number;
/** Server name for logging. */
name?: string;
}
type Transport = StreamableHTTPServerTransport | SSEServerTransport;
/**
* Starts an MCP server with HTTP transports.
*
* Provides:
* - /mcp (GET/POST/DELETE): Streamable HTTP with stateful sessions
* - /sse (GET) + /messages (POST): Legacy SSE for older clients
*/
export async function startServer(
server: McpServer,
options: ServerOptions,
): Promise<void> {
const { port, name = "MCP Server" } = options;
// Unified session store for both transport types
const sessions = new Map<string, Transport>();
// Express app - bind to all interfaces for development/testing
const app = createMcpExpressApp({ host: "0.0.0.0" });
app.use(
cors({
exposedHeaders: ["mcp-session-id"],
}),
);
// Streamable HTTP (stateful)
app.all("/mcp", async (req: Request, res: Response) => {
try {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
let transport = sessionId
? (sessions.get(sessionId) as StreamableHTTPServerTransport | undefined)
: undefined;
// Session exists but wrong transport type
if (sessionId && sessions.has(sessionId) && !transport) {
return res.status(400).json({
jsonrpc: "2.0",
error: { code: -32000, message: "Session uses different transport" },
id: null,
});
}
// New session requires initialize request
if (!transport) {
if (req.method !== "POST" || !isInitializeRequest(req.body)) {
return res.status(400).json({
jsonrpc: "2.0",
error: { code: -32000, message: "Bad request: not initialized" },
id: null,
});
}
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (id) => {
sessions.set(id, transport!);
},
});
const t = transport;
t.onclose = () => {
if (t.sessionId) sessions.delete(t.sessionId);
};
await server.connect(transport);
}
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("MCP error:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
// Legacy SSE
app.get("/sse", async (_req: Request, res: Response) => {
try {
const transport = new SSEServerTransport("/messages", res);
sessions.set(transport.sessionId, transport);
res.on("close", () => sessions.delete(transport.sessionId));
await server.connect(transport);
} catch (error) {
console.error("SSE error:", error);
if (!res.headersSent) res.status(500).end();
}
});
app.post("/messages", async (req: Request, res: Response) => {
try {
const transport = sessions.get(req.query.sessionId as string);
if (!(transport instanceof SSEServerTransport)) {
return res.status(404).json({
jsonrpc: "2.0",
error: { code: -32001, message: "Session not found" },
id: null,
});
}
await transport.handlePostMessage(req, res, req.body);
} catch (error) {
console.error("Message error:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
return new Promise<void>((resolve, reject) => {
const httpServer = app.listen(port);
httpServer.on("listening", () => {
console.log(`${name} listening on http://localhost:${port}/mcp`);
resolve();
});
httpServer.on("error", (err: Error) => {
reject(err);
});
const shutdown = () => {
console.log("\nShutting down...");
sessions.forEach((t) => t.close().catch(() => {}));
httpServer.close(() => process.exit(0));
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
});
}

View file

@ -1,20 +1,16 @@
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 type {
CallToolResult,
ReadResourceResult,
} from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import si from "systeminformation";
import { z } from "zod";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
import { startServer } from "../shared/server-utils.js";
// Schemas - types are derived from these using z.infer
const CpuCoreSchema = z.object({
@ -178,60 +174,14 @@ const server = new McpServer({
async function main() {
if (process.argv.includes("--stdio")) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("System Monitor Server running in stdio mode");
await server.connect(new StdioServerTransport());
} else {
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(PORT, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log(
`System Monitor Server listening on http://localhost:${PORT}/mcp`,
);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const port = parseInt(process.env.PORT ?? "3107", 10);
await startServer(server, { port, name: "System Monitor Server" });
}
}
main().catch(console.error);
main().catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -5,16 +5,13 @@
*/
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 type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/server-utils.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const DIST_DIR = path.join(import.meta.dirname, "dist");
const THREEJS_DOCUMENTATION = `# Three.js Widget Documentation
@ -187,54 +184,14 @@ const server = new McpServer({
async function main() {
if (process.argv.includes("--stdio")) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Three.js Server running in stdio mode");
await server.connect(new StdioServerTransport());
} else {
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(PORT, () => {
console.log(`Three.js Server listening on http://localhost:${PORT}/mcp`);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const port = parseInt(process.env.PORT ?? "3108", 10);
await startServer(server, { port, name: "Three.js Server" });
}
}
main().catch(console.error);
main().catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -1,19 +1,16 @@
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 type {
CallToolResult,
ReadResourceResult,
} from "@modelcontextprotocol/sdk/types.js";
import * as cheerio from "cheerio";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/server-utils.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const DIST_DIR = path.join(import.meta.dirname, "dist");
type PageInfo = { url: string; title: string };
@ -144,56 +141,14 @@ const server = new McpServer({
async function main() {
if (process.argv.includes("--stdio")) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Wiki Explorer server running in stdio mode");
await server.connect(new StdioServerTransport());
} else {
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(PORT, () => {
console.log(
`Wiki Explorer server listening on http://localhost:${PORT}/mcp`,
);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const port = parseInt(process.env.PORT ?? "3109", 10);
await startServer(server, { port, name: "Wiki Explorer" });
}
}
main().catch(console.error);
main().catch((e) => {
console.error(e);
process.exit(1);
});