Building and Deploying a Stateless Remote Model Context Protocol (MCP) Server with TypeScript and Streamable HTTP
Tom Dohnal
Summary:
This video demonstrates how to build and deploy a remote Model Context Protocol (MCP) server using TypeScript and Streamable HTTP.
- The process starts by defining core MCP tools (
getModelNames and getReviews) for interacting with an AI model review database.
- The video illustrates the difference between local MCP servers using standard I/O transport and remote MCP servers using streamable HTTP.
- It then focuses on implementing a stateless remote MCP server, which creates a new server instance for each request to avoid session management complexities.
- The implementation involves wrapping the MCP server creation in a function, setting up an Express HTTP server, and using the
StreamableHttpServerTransport.
- Finally, the remote server is tested locally with an MCP inspector and then deployed to a cloud platform (Render) and verified within Cursor AI.
Introduction to Model Context Protocol (MCP) [00:00:00]
The video begins by introducing a scenario where a database for reviewing AI models needs to be integrated with AI applications like Cursor or VS Code desktop.
- The goal is to create a Model Context Protocol (MCP) server to facilitate this integration.
- The approach involves first building a local MCP and then extending the same core logic to a remote MCP.
- The MCP "core" (tool definitions) remains consistent between local and remote implementations.
- Only the "transport layer" changes: local uses standard I/O, remote uses streamable HTTP.
Implementing a Local MCP Server [00:00:24]
The initial step is to define the functionalities (tools) of the MCP server.
getModelNames Tool [00:00:27]
- Purpose: Retrieves all model names from the
ai_models table.
- Implementation: A simple SQL query
SELECT name FROM ai_models;
- Output: Returns model names as JSON.
getReviews Tool [00:00:44]
- Purpose: Fetches reviews for a given model name.
- Arguments: Accepts
modelName as an input.
- Implementation: Constructs a SQL query to join
model_reviews and ai_models tables, filtering by ai_model.name.
- Output: Returns reviews as JSON for the specified model.
- Connecting the Local MCP Server [00:03:07]
- The local MCP server uses a standard I/O transport layer.
- Code involves instantiating
StdioServerTransport and connecting it to the MCPServer instance.
Understanding MCP Transport Layers (Standard IO vs. Streamable HTTP) [00:01:14]
The video elaborates on how different transport layers handle communication between MCP clients and servers.
- Standard I/O (stdio) Transport [00:01:18]
- Mechanism:
- An MCP client (e.g., Cursor) launches a local MCP server.
- The client writes requests to the server's standard input (stdin).
- The server responds by writing results to its standard output (stdout), which the client reads.
- Protocol: Uses JSON RPC (Remote Procedure Call) for requests and responses, which are structured JSON objects.
- Applicability: Ideal for local machine environments where client and server run on the same system.
- Streamable HTTP Transport [00:02:04]
- Mechanism:
- Instead of stdin/stdout, communication occurs via HTTP requests and responses over the internet.
- MCP client sends HTTP requests, and the remote MCP server sends HTTP responses.
- Protocol: Still uses JSON RPC for the content of requests and responses.
- Streaming: Optionally supports streaming HTTP responses, which gives it the "streamable" designation.
- Replaces Deprecated Technology: Streamable HTTP replaces Server-Sent Events (SSE) (aka SSC) which is deprecated.
Local vs. Remote MCP Server State Management [00:04:55]
The video discusses how state is managed in local versus remote MCP servers.
- Local MCP Servers (stdio) [00:05:10]
- Relationship: Always a one-to-one relationship between an MCP client and an MCP server.
- Statefulness: Each MCP server instance maintains its own state (e.g., message history, request count) without needing explicit session management.
- Isolation: Each client launches its own server, ensuring complete isolation of state.
- Remote MCP Servers (Streamable HTTP) [00:05:51]
- Challenge: Multiple clients can connect to a single remote HTTP server, requiring a way to manage state for each client.
- Stateful (with session management) [00:06:00]
- When a client first connects, the HTTP server creates a new MCP server instance and assigns a
session-id.
- Subsequent requests from that client include the
session-id, allowing the HTTP server to route requests to the correct, stateful MCP server instance.
- This approach is more complex, potentially requiring authentication.
- Stateless (without session management) [00:07:08]
- Simplicity: A new MCP server instance is created for every incoming request.
- Handling: The request is processed, a response is sent, and the server instance is then discarded.
- Benefit: Avoids the complexities of session management and state tracking on the server side.
Building a Stateless Remote MCP Server with Streamable HTTP [00:07:44]
The implementation shifts from local stdio to remote stateless streamable HTTP.
- Refactoring Server Creation [00:07:52]
- The MCP server creation logic (defining tools) is encapsulated in a
getServer function.
- This allows a fresh server instance to be generated for each incoming HTTP request.
- Setting Up the HTTP Server [00:08:03]
- An Express application (
app) is used as the HTTP server.
- The MCP SDK requires Express request objects, so native Bun server cannot be used directly.
- Defining the MCP Endpoint [00:08:21]
- A POST endpoint (
/mcp) is registered to handle incoming MCP requests as per the specification.
- Request Handling Logic:
- Call
getServer() to create a new, fresh MCP server instance.
- Instantiate
StreamableHttpServerTransport, passing undefined for sessionIDGenerator to ensure stateless operation.
- Register a
res.on('close') event listener to close the transport and server when the HTTP response stream ends, ensuring resource cleanup.
- Connect the newly created server to the transport (
server.connect(transport)).
- Handle the incoming HTTP request using
transport.handleRequest(req, res, req.body).
- Implement a
try-catch block for robust error handling, returning JSON RPC compliant error codes in case of failures.
- Starting the HTTP Server [00:10:14]
- The Express app is configured to listen on a specified port (e.g., 3000).
Testing the Remote MCP Server [00:10:31]
The remote server's functionality is verified in multiple environments.
- Local Testing with MCP Inspector [00:10:31]
- The MCP inspector is configured to use the
Streamable HTTP transport type.
- The URL is set to
http://localhost:3000/mcp.
- The local HTTP server is launched manually.
- Both
getModelNames and getReviews tools are successfully executed via the inspector.
- Deployed Testing [00:11:34]
- The server code is deployed to a cloud platform (e.g., Render, which supports Bun).
- The MCP inspector's URL is updated to point to the deployed instance's MCP endpoint.
- The tools are tested again, confirming functionality in a deployed environment.
- Integration with Cursor AI [00:12:08]
- The remote MCP server's URL is added to Cursor's MCP tools settings.
- An AI assistant in Cursor is then used to query the deployed MCP server:
- Asking "what models are in tostardos" successfully invokes the
getModelNames tool.
- Asking "what are the pros and cons of the Kronos model" successfully invokes the
getReviews tool, demonstrating the AI's ability to infer parameters and present results in a human-readable format.
Conclusion and Recap [00:13:34]
The video concludes by reiterating the key takeaways:
- The core MCP logic (tool definition) is reusable for both local and remote servers.
- Transport layers are interchangeable (stdio for local, streamable HTTP for remote).
- Streamable HTTP remote servers can be either stateful (with session management) or stateless (new server per request). The video demonstrated the stateless approach for simplicity.