Introduction & Context

Welcome to the final chapter of this MCP journey! In the previous lessons, you've built sophisticated MCP systems that can orchestrate complex workflows across different services. Your shopping assistant can modify lists, place orders, and interact with external systems, but there's one essential piece missing: security. Without proper authentication, anyone who discovers your server endpoint could access these tools, potentially causing unauthorized actions or data corruption.

In this lesson, we'll transform your MCP server from an open endpoint into a properly secured service by implementing API key authentication middleware. You'll learn how to validate incoming requests, reject unauthorized access with proper JSON-RPC error responses, and configure your OpenAI agent to authenticate correctly when connecting to your secured server.

By the end of this lesson, you'll run the same shopping assistant query from previous lessons, but this time through a secured connection that rejects requests without valid API keys while allowing authenticated agents to connect seamlessly.

Simple Threat Model and Approach

For MCP servers, the primary threat is unauthorized access to your tools and data. Someone could discover your server endpoint and use your tools without permission, potentially causing data corruption, unauthorized actions, or resource consumption.

We'll use API key authentication, which provides a good balance between security and simplicity for MCP servers. The authentication flow is straightforward: your client includes an API key in request headers, your server validates this key against known valid keys, and returns JSON-RPC error responses for invalid or missing keys.

We'll store valid API keys in a simple Set for this lesson, using long, random strings like sk-mcp-12345. In production, you should use environment variables or secure key management services, and always use HTTPS to prevent API keys from being intercepted in transit.

Setting Up Valid API Keys

Now that we understand our authentication approach, let's implement it by creating the middleware that will validate API keys. If you're new to middleware, think of it as a function that sits between incoming requests and your main application logic — it can inspect, modify, or reject requests before they reach your core functionality.

Let's create a new file named auth.ts and start by defining the valid API keys that your server will accept:

We're using a Set data structure because it provides O(1) lookup performance for key validation, which is important if you have many valid keys or high request volumes. In production, you should load these keys from environment variables or secure key management services rather than hardcoding them in your source code.

Creating the Authentication Middleware Function

Now we'll create the middleware function that follows Express.js conventions by accepting Request, Response, and NextFunction parameters. The function first extracts the API key from the x-api-key header. Headers in Express are automatically converted to lowercase, so we access them using lowercase names even if the client sends them with different capitalization.

The NextFunction parameter is crucial — calling next() tells Express to continue processing the request, while not calling next() stops the request processing chain:

When authentication fails, we need to return proper JSON-RPC error responses that follow the MCP protocol standards. The error responses use a 401 Unauthorized status code and include structured error information:

  • The jsonrpc: "2.0" field indicates this is a JSON-RPC 2.0 response
Secure the MCP Endpoint

Now we'll integrate your authentication middleware into your MCP server by applying it to the /mcp endpoint. The key change is adding authenticateApiKey as middleware in the route definition:

Express middleware runs in the order we specify, so authenticateApiKey executes before your main route handler. If authentication fails, the middleware sends an error response and doesn't call next(), preventing the MCP logic from running.

The rest of your MCP server logic remains unchanged from previous lessons. You still handle session management, create new transports for initialize requests, and maintain the same error handling patterns. This demonstrates the power of middleware — we can add cross-cutting concerns like authentication without modifying your core business logic.

Configure the Agent to Authenticate

Now that your MCP server requires authentication, you need to configure the client of your OpenAI agent to include valid API keys in its requests. The MCPServerStreamableHttp class provides a requestInit option that allows you to customize the HTTP requests it makes, including adding authentication headers:

The requestInit property accepts the same options as the standard fetch API, allowing you to customize headers, timeout settings, and other request parameters. The x-api-key header will be included in every HTTP request the agent makes to your MCP server, including the initial connection request and all subsequent tool calls.

The authentication is handled transparently by the HTTP transport layer, so your agent logic doesn't need to change. This separation of concerns makes it easy to add or modify authentication without affecting your agent's behavior or tool usage patterns.

Summary & Final Practices

We've successfully secured your MCP server with API key authentication middleware that validates requests and rejects unauthorized access while maintaining all existing functionality. The authentication is handled transparently through the requestInit configuration, demonstrating clean separation of concerns between security and business logic.

The authentication patterns we've covered provide the foundation for building MCP servers that safely expose powerful tools while maintaining proper access control and security monitoring.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal