Skip to content

Conversation

@ochafik
Copy link
Collaborator

@ochafik ochafik commented Dec 11, 2025

Summary

  • Add SSE transport backwards compatibility to all example servers (needed by kotlin example host as Kotlin mcp sdk only supports sse)
  • Create shared server-utils.ts module for HTTP transport setup
  • Each example is still mostly self-contained (for stdio mode), and defers to single file helper for HTTP transports.

Changes

New: examples/shared/server-utils.ts

A shared utility for HTTP transports:

  • /mcp (GET/POST/DELETE): Streamable HTTP with stateful sessions
  • /sse + /messages: Legacy SSE for older clients

Features

  • Stateful sessions: Proper session management with sessionIdGenerator
  • Unified session store: Single Map for both transport types
  • Error handling: Try/catch on all endpoints with JSON-RPC errors
  • Graceful shutdown: Closes all sessions on SIGINT/SIGTERM
  • Promise-based: startServer() returns Promise that resolves on listen, rejects on error
  • Accessible: Binds to 0.0.0.0 for Android/device access

Server Pattern

Each server now uses explicit transport selection:

async function main() {
  if (process.argv.includes("--stdio")) {
    await server.connect(new StdioServerTransport());
  } else {
    const port = parseInt(process.env.PORT ?? "3001", 10);
    await startServer(server, { port, name: "Server Name" });
  }
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});

Servers Updated

All 9 TypeScript example servers:

  • basic-server-react, basic-server-vanillajs
  • budget-allocator-server, cohort-heatmap-server
  • customer-segmentation-server, scenario-modeler-server
  • system-monitor-server, threejs-server, wiki-explorer-server

Test plan

  • npm run build passes
  • npm test passes
  • Manual verification of endpoints:
    • POST /mcp returns valid SSE initialize response
    • GET /sse returns SSE stream with endpoint event
    • POST /messages without session returns 404

🤖 Generated with Claude Code

ochafik and others added 5 commits December 11, 2025 18:24
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 11, 2025

Open in StackBlitz

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/ext-apps@136

commit: 503d1c2

- 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 <[email protected]>
@ochafik ochafik force-pushed the ochafik/refactor-servers-use-shared-utility branch from 6a62304 to 46a1596 Compare December 11, 2025 18:24
ochafik and others added 3 commits December 11, 2025 19:26
…isten

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 <[email protected]>
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 <[email protected]>
…dio 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 <[email protected]>
@ochafik ochafik force-pushed the ochafik/refactor-servers-use-shared-utility branch from 690b5dc to 55d652b Compare December 11, 2025 18:37
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 <[email protected]>
ochafik and others added 2 commits December 11, 2025 19:55
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 <[email protected]>
Copy link
Member

@jonathanhefner jonathanhefner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need for the Kotlin SDK to connect to the basic examples? Or are the marketing examples sufficient?

Generally, I think we should keep basic-server-vanillajs and basic-server-react dead simple (and self-contained) as reference examples, so I would prefer that they only support a single transport.

@ochafik
Copy link
Collaborator Author

ochafik commented Dec 11, 2025

Do we need for the Kotlin SDK to connect to the basic examples? Or are the marketing examples sufficient?

@jonathanhefner I found bugs thanks to some of the examples already. If we adopt / keep the Kotlin & Swift examples it's good to be able to test them against something (native e2e tests will come in a while too), esp. since some users will only care about mobile apps hosts.

Generally, I think we should keep basic-server-vanillajs and basic-server-react dead simple (and self-contained) as reference examples, so I would prefer that they only support a single transport.

I kept the stdio part and their import inlined in each file, so users can trivially axe the http transports by removing two lines (or adopt it by bringing in that file, which imho should be in the TS MCP SDK in some form... but I digress).

We do need some kind of remote for our own testing though, and I didn't want this to cause too much duplication.

Copy link
Member

@jonathanhefner jonathanhefner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I don't want to block anything. I will take a look into alternatives at a later date. 👍

@ochafik ochafik merged commit 3a30e94 into main Dec 12, 2025
8 checks passed
@ochafik ochafik deleted the ochafik/refactor-servers-use-shared-utility branch December 13, 2025 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants