Skip to content

Commit 6d3ff41

Browse files
ochafikclaude
andcommitted
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>
1 parent 6b40421 commit 6d3ff41

File tree

4 files changed

+119
-230
lines changed

4 files changed

+119
-230
lines changed
Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2-
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
3-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
42
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
5-
import cors from "cors";
6-
import express, { type Request, type Response } from "express";
73
import fs from "node:fs/promises";
84
import path from "node:path";
95
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
6+
import { startServer } from "../shared/server-utils.js";
107

11-
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
128
const DIST_DIR = path.join(import.meta.dirname, "dist");
139

14-
1510
const server = new McpServer({
1611
name: "Basic MCP App Server (React-based)",
1712
version: "1.0.0",
1813
});
1914

20-
2115
// MCP Apps require two-part registration: a tool (what the LLM calls) and a
2216
// resource (the UI it renders). The `_meta` field on the tool links to the
2317
// resource URI, telling hosts which UI to display when the tool executes.
@@ -58,70 +52,4 @@ const server = new McpServer({
5852
);
5953
}
6054

61-
62-
const app = express();
63-
app.use(cors());
64-
app.use(express.json());
65-
66-
// Streamable HTTP transport (current spec) - handles GET, POST, DELETE
67-
app.all("/mcp", async (req: Request, res: Response) => {
68-
try {
69-
const transport = new StreamableHTTPServerTransport({
70-
sessionIdGenerator: undefined,
71-
enableJsonResponse: true,
72-
});
73-
res.on("close", () => { transport.close(); });
74-
75-
await server.connect(transport);
76-
77-
await transport.handleRequest(req, res, req.body);
78-
} catch (error) {
79-
console.error("Error handling MCP request:", error);
80-
if (!res.headersSent) {
81-
res.status(500).json({
82-
jsonrpc: "2.0",
83-
error: { code: -32603, message: "Internal server error" },
84-
id: null,
85-
});
86-
}
87-
}
88-
});
89-
90-
// Legacy SSE transport (deprecated) - for backwards compatibility
91-
const sseTransports = new Map<string, SSEServerTransport>();
92-
93-
app.get("/sse", async (_req: Request, res: Response) => {
94-
const transport = new SSEServerTransport("/messages", res);
95-
sseTransports.set(transport.sessionId, transport);
96-
res.on("close", () => { sseTransports.delete(transport.sessionId); });
97-
await server.connect(transport);
98-
});
99-
100-
app.post("/messages", async (req: Request, res: Response) => {
101-
const sessionId = req.query.sessionId as string;
102-
const transport = sseTransports.get(sessionId);
103-
if (!transport) {
104-
res.status(404).json({ error: "Session not found" });
105-
return;
106-
}
107-
await transport.handlePostMessage(req, res, req.body);
108-
});
109-
110-
const httpServer = app.listen(PORT, (err) => {
111-
if (err) {
112-
console.error("Error starting server:", err);
113-
process.exit(1);
114-
}
115-
console.log(`Server listening on http://localhost:${PORT}/mcp`);
116-
});
117-
118-
function shutdown() {
119-
console.log("\nShutting down...");
120-
httpServer.close(() => {
121-
console.log("Server closed");
122-
process.exit(0);
123-
});
124-
}
125-
126-
process.on("SIGINT", shutdown);
127-
process.on("SIGTERM", shutdown);
55+
startServer(server, { name: "Basic MCP App Server (React-based)" });
Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2-
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
3-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
42
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
5-
import cors from "cors";
6-
import express, { type Request, type Response } from "express";
73
import fs from "node:fs/promises";
84
import path from "node:path";
95
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
6+
import { startServer } from "../shared/server-utils.js";
107

11-
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
128
const DIST_DIR = path.join(import.meta.dirname, "dist");
139

14-
1510
const server = new McpServer({
1611
name: "Basic MCP App Server (Vanilla JS)",
1712
version: "1.0.0",
1813
});
1914

20-
2115
// MCP Apps require two-part registration: a tool (what the LLM calls) and a
2216
// resource (the UI it renders). The `_meta` field on the tool links to the
2317
// resource URI, telling hosts which UI to display when the tool executes.
@@ -58,70 +52,4 @@ const server = new McpServer({
5852
);
5953
}
6054

61-
62-
const app = express();
63-
app.use(cors());
64-
app.use(express.json());
65-
66-
// Streamable HTTP transport (current spec) - handles GET, POST, DELETE
67-
app.all("/mcp", async (req: Request, res: Response) => {
68-
try {
69-
const transport = new StreamableHTTPServerTransport({
70-
sessionIdGenerator: undefined,
71-
enableJsonResponse: true,
72-
});
73-
res.on("close", () => { transport.close(); });
74-
75-
await server.connect(transport);
76-
77-
await transport.handleRequest(req, res, req.body);
78-
} catch (error) {
79-
console.error("Error handling MCP request:", error);
80-
if (!res.headersSent) {
81-
res.status(500).json({
82-
jsonrpc: "2.0",
83-
error: { code: -32603, message: "Internal server error" },
84-
id: null,
85-
});
86-
}
87-
}
88-
});
89-
90-
// Legacy SSE transport (deprecated) - for backwards compatibility
91-
const sseTransports = new Map<string, SSEServerTransport>();
92-
93-
app.get("/sse", async (_req: Request, res: Response) => {
94-
const transport = new SSEServerTransport("/messages", res);
95-
sseTransports.set(transport.sessionId, transport);
96-
res.on("close", () => { sseTransports.delete(transport.sessionId); });
97-
await server.connect(transport);
98-
});
99-
100-
app.post("/messages", async (req: Request, res: Response) => {
101-
const sessionId = req.query.sessionId as string;
102-
const transport = sseTransports.get(sessionId);
103-
if (!transport) {
104-
res.status(404).json({ error: "Session not found" });
105-
return;
106-
}
107-
await transport.handlePostMessage(req, res, req.body);
108-
});
109-
110-
const httpServer = app.listen(PORT, (err) => {
111-
if (err) {
112-
console.error("Error starting server:", err);
113-
process.exit(1);
114-
}
115-
console.log(`Server listening on http://localhost:${PORT}/mcp`);
116-
});
117-
118-
function shutdown() {
119-
console.log("\nShutting down...");
120-
httpServer.close(() => {
121-
console.log("Server closed");
122-
process.exit(0);
123-
});
124-
}
125-
126-
process.on("SIGINT", shutdown);
127-
process.on("SIGTERM", shutdown);
55+
startServer(server, { name: "Basic MCP App Server (Vanilla JS)" });

examples/shared/server-utils.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Shared utilities for running MCP servers with multiple transports.
3+
*
4+
* This module provides a unified way to start MCP servers supporting:
5+
* - stdio transport (for local CLI tools)
6+
* - Streamable HTTP transport (current spec)
7+
* - Legacy SSE transport (deprecated, for backwards compatibility)
8+
*/
9+
10+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
12+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
14+
import cors from "cors";
15+
import express, { type Request, type Response } from "express";
16+
17+
export interface ServerOptions {
18+
/** Port to listen on for HTTP mode. Defaults to 3001 or PORT env variable. */
19+
port?: number;
20+
/** Server name for logging. Defaults to "MCP Server". */
21+
name?: string;
22+
}
23+
24+
/**
25+
* Starts an MCP server with support for stdio and HTTP transports.
26+
*
27+
* Transport is selected based on command line arguments:
28+
* - `--stdio`: Uses stdio transport for local process communication
29+
* - Otherwise: Starts HTTP server with Streamable HTTP and legacy SSE support
30+
*
31+
* @param server - The MCP server instance to start
32+
* @param options - Optional configuration
33+
*/
34+
export async function startServer(
35+
server: McpServer,
36+
options: ServerOptions = {},
37+
): Promise<void> {
38+
const port =
39+
options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : 3001);
40+
const name = options.name ?? "MCP Server";
41+
42+
if (process.argv.includes("--stdio")) {
43+
const transport = new StdioServerTransport();
44+
await server.connect(transport);
45+
console.error(`${name} running in stdio mode`);
46+
} else {
47+
const app = express();
48+
app.use(cors());
49+
app.use(express.json());
50+
51+
// Streamable HTTP transport (current spec) - handles GET, POST, DELETE
52+
app.all("/mcp", async (req: Request, res: Response) => {
53+
try {
54+
const transport = new StreamableHTTPServerTransport({
55+
sessionIdGenerator: undefined,
56+
enableJsonResponse: true,
57+
});
58+
res.on("close", () => {
59+
transport.close();
60+
});
61+
62+
await server.connect(transport);
63+
await transport.handleRequest(req, res, req.body);
64+
} catch (error) {
65+
console.error("Error handling MCP request:", error);
66+
if (!res.headersSent) {
67+
res.status(500).json({
68+
jsonrpc: "2.0",
69+
error: { code: -32603, message: "Internal server error" },
70+
id: null,
71+
});
72+
}
73+
}
74+
});
75+
76+
// Legacy SSE transport (deprecated) - for backwards compatibility
77+
const sseTransports = new Map<string, SSEServerTransport>();
78+
79+
app.get("/sse", async (_req: Request, res: Response) => {
80+
const transport = new SSEServerTransport("/messages", res);
81+
sseTransports.set(transport.sessionId, transport);
82+
res.on("close", () => {
83+
sseTransports.delete(transport.sessionId);
84+
});
85+
await server.connect(transport);
86+
});
87+
88+
app.post("/messages", async (req: Request, res: Response) => {
89+
const sessionId = req.query.sessionId as string;
90+
const transport = sseTransports.get(sessionId);
91+
if (!transport) {
92+
res.status(404).json({ error: "Session not found" });
93+
return;
94+
}
95+
await transport.handlePostMessage(req, res, req.body);
96+
});
97+
98+
const httpServer = app.listen(port, () => {
99+
console.log(`${name} listening on http://localhost:${port}/mcp`);
100+
});
101+
102+
const shutdown = () => {
103+
console.log("\nShutting down...");
104+
httpServer.close(() => {
105+
console.log("Server closed");
106+
process.exit(0);
107+
});
108+
};
109+
110+
process.on("SIGINT", shutdown);
111+
process.on("SIGTERM", shutdown);
112+
}
113+
}

0 commit comments

Comments
 (0)