Building MCP Remote Server - Part 2: /mcp

8 months ago

In Part 1, we deployed the MCP remote server using the old SSE protocol. In this Part 2, we will continue implementing a newer protocol: MCP.

The implementation is very straightforward, similar to SSE, but requires the implementation of three endpoints.

First, declare a variable transports to store the state of incoming connections based on sessionID.

const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};

Implement the POST protocol for /mcp. This is the main endpoint for interaction between the server and the client.

app.post('/mcp', async (req, res) => {
  // Check for existing session ID
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  let transport: StreamableHTTPServerTransport;

  if (sessionId && transports[sessionId]) {
    // Reuse existing transport
    transport = transports[sessionId];
  } else if (!sessionId && isInitializeRequest(req.body)) {
    // New initialization request
    transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: () => randomUUID(),
      onsessioninitialized: (sessionId) => {
        // Store the transport by session ID
        transports[sessionId] = transport;
      },
    });

    // Clean up transport when closed
    transport.onclose = () => {
      if (transport.sessionId) {
        delete transports[transport.sessionId];
      }
    };

    await server.connect(transport);
  } else {
    res.status(400).json({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: 'Bad Request: No valid session ID provided',
      },
      id: null,
    });
    return;
  }

  await transport.handleRequest(req, res, req.body);
});

Implement two additional protocols, GET and DELETE, for /mcp so that the client can receive data from the server or close the session.

const handleSessionRequest = async (req: express.Request, res: express.Response) => {
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  if (!sessionId || !transports[sessionId]) {
    res.status(400).send('Invalid or missing session ID');
    return;
  }
  
  const transport = transports[sessionId];
  await transport.handleRequest(req, res);
};

// Handle GET requests for server-to-client notifications via SSE
app.get('/mcp', handleSessionRequest);

// Handle DELETE requests for session termination
app.delete('/mcp', handleSessionRequest);

Put it all together, edit the mcp.json file in LM Studio, and try issuing a command to it.

Example

Congratulations on completing this! Refer to the source code in the example at Github. In the next article, we will implement authentication for the MCP remote server together!