DEV Community

Dat Tran
Dat Tran

Posted on

Secure MCP Server with NGINX + Supergateway + Render

Joint article by Dat Tran (Partner & CTO at DATANOMIQ) and Dr. Alexander Lammers (Chief Data Scientist at DATANOMIQ)

Security Image
Photo by tungnguyen0905 on Pixabay

Model Context Protocol (MCP) is the new open standard for connecting AI assistants. Typically you run it as:

  • stdio (Standard Input/Output): Used when Client and Server run on the same machines. This is simple and effective for local integrations (e.g., accessing local files or running a local script).
  • HTTP via SSE (Server-Sent Events): The Client connects to the Server via HTTP. After an initial setup, the Server can push messages (events) to the Client over a persistent connection using the SSE standard.

Most MCPs at the moment run as stdio locally but this is not ideal if you have a shared service that you want to deploy on a server. In this article, we will explain how you could do it and also secure an MCP Server with NGINX, Supergateway and deploying it on Render. Securing the endpoint is critical as the MCP server might access sensitive data. In our example, we just show a basic auth example but you can easily use OAuth as well.

Getting Started

For this tutorial we use the Airbnb MCP Server example. Normally, you can use it with Claude like this:

{
  "mcpServers": {
    "airbnb": {
      "command": "npx",
      "args": [
        "-y",
        "@openbnb/mcp-server-airbnb"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This assumes that you have node installed and have it locally running. Let's say you rather want to have it running remotely, then you can use Supergateway
which can run MCP stdio-based servers over SSE (Server-Sent Events) or WebSockets (WS) with one command.

For example, you could easily expose our MCP service over SSE:

npx -y supergateway \
    --stdio "npx -y @openbnb/mcp-server-airbnb" \
    --port 8000 --baseUrl http://localhost:8000
Enter fullscreen mode Exit fullscreen mode

Then in Claude, you can consume it like this:

npx -y supergateway --sse "http://localhost:8000/sse"
Enter fullscreen mode Exit fullscreen mode

I hope you get the point till now. Since we can easily convert stdio to SSE and vice versa, we can actually deploy the service to any cloud provider and then use it in Claude easily. Obviously for this service, deploying it with authentication is fine as it only calls the AirBnB api. However, in many cases you have an API key or access more sensitive systems where you want the service to be secured, for example, via OAuth. MCP itself provides a specification for OAuth 2.1, however, this is still a draft and there a flaws with the implementation.

Another method is to leverage NGINX for this. In this blog post we use NGINX with simple basic authentication (username and password) but you can easily use it with OAuth:

events {
    worker_connections 1024;
}

http {
    upstream airbnb_server {
        server 127.0.0.1:5000;
    }

    # Enable error logging
    error_log /var/log/nginx/error.log debug;
    access_log /var/log/nginx/access.log;

    server {
        listen 8000;
        server_name localhost;

        # Basic Authentication
        auth_basic "Restricted Access";
        auth_basic_user_file /etc/nginx/.htpasswd;

        # Add a location for regular HTTP requests
        location / {
            proxy_pass http://airbnb_server;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
            proxy_set_header X-NginX-Proxy true;
        }

        location /sse {
            proxy_pass http://airbnb_server;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
            proxy_set_header X-NginX-Proxy true;

            # SSE specific settings
            proxy_buffering off;
            proxy_cache off;
            proxy_read_timeout 86400s;
            proxy_send_timeout 86400s;
            keepalive_timeout 86400s;
            send_timeout 86400s;
        }
    }
} 
Enter fullscreen mode Exit fullscreen mode

Now we could deploy the service to the cloud, in our case, I chose Render as it's easy to do. Here we just need a Dockerfile:

# Use Python base image
FROM python:3.10-slim-bookworm

# Install Node.js & nginx
RUN apt-get update && apt-get install -y \
    curl \
    nginx \
    apache2-utils \
    && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs \
    && rm -rf /var/lib/apt/lists/*


# Copy nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf

# Create password file (replace 'password' with your desired password)
RUN htpasswd -bc /etc/nginx/.htpasswd user testuser1234!

# Create log directories
RUN mkdir -p /var/log/nginx && \
    touch /var/log/nginx/error.log && \
    touch /var/log/nginx/access.log && \
    chown -R www-data:www-data /var/log/nginx

# Expose ports
EXPOSE 8000 5000

CMD nginx && npx -y supergateway --stdio "npx -y @openbnb/mcp-server-airbnb" --port 5000
Enter fullscreen mode Exit fullscreen mode

Once deployed to Render, you could go to the url then: https://<>.onrender.com/sse. It will pop up with login window where you need to enter your username and password. Alright this is nice but how do we use it in Claude then? Remember we use Supergateway to convert SSE back to stdio. Supergateway also offers the possibility to pass a --header. This is useful for as we can pass an Authorization header. But what do we need to send exactly? We can't simply send the username and password but both are encoded as base64. So what you can do is this here:

curl -v -u user:testuser1234! https://<<your-project>>.onrender.com/sse
Enter fullscreen mode Exit fullscreen mode

This will give us something like this: Authorization: Basic <<your-base64-key>>.

Now we can finally use it Claude:

{
  "mcpServers": {
    "airbnb-server": {
      "command": "npx",
      "args": [
        "-y",
        "supergateway",
        "--sse",
        "https://<<your-project>>.onrender.com/sse",
        "--header",
        "Authorization: Basic <<your-base64-key>>"
      ]
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Voila! We now have a secured MCP server with basic authentication. If you want to use OAuth, you simply need a provider like auth0, Clerk etc and then use the --oauth2Bearer flag.

Summary

Hope you enjoyed this short article. MCP is super new and a lot of things can change radically. It's far from production ready but hopefully you can use this for your next project. The full code is also on Github which you can use directly to deploy on Render.

Top comments (1)

Collapse
 
michael_liang_0208 profile image
Michael Liang

awesome!