跳到正文
DEV Community· octave Nkurunziza·· 3 小時前AI 評分60

Hubfly space 教學:如何透過 HTTPS 和存取金鑰部署自己的 MCP 伺服器

Deploy your own MCP server with HTTPS and an access key

AI 導讀

這篇教學示範如何在 Hubfly space 部署透過 HTTPS 提供服務、並在邊緣層以存取金鑰保護的 MCP 伺服器,再連接 Claude Code、Cursor 和 Codex。

正文

Originally published on the Hubfly space blog.

TL;DR

  • What you get: your own MCP server on a public HTTPS address, locked with an access key, and connected to Claude Code, Cursor and Codex.
  • Who it's for: developers who built (or want to build) an MCP server and are tired of it only working on localhost.
  • Time: about 15 minutes. Cost: free on the shared tier (360 compute hours a month, no card); about $2.25 a month always on (dedicated 0.5 vCPU, 512 MB).

Your MCP server works great on your laptop. Then you open Cursor on your other machine, or a teammate asks for it, and it's gone. Local servers die when your laptop sleeps, and you end up copying the same setup to every computer.

In this guide you'll put one MCP server online, behind HTTPS and an access key, and point every AI tool you use at the same address. One server, one place to update it, and nobody else can call your tools.

Try it on Hubfly space → (free tier, no card needed)

What you'll build

Claude Code ─┐
Cursor ──────┼──► https://your-server.eu1.hubfly.app/mcp ──► access key check ──► your MCP server (Python)
Codex ───────┘                                              (at the edge)         (Hubfly space Cell container)

A small Python MCP server with two tools, add_note and list_notes. Swap in your own tools later; the deploy steps stay the same.

Step 1: Write the server

Install the official Python SDK on your machine to try it locally first:

pip install "mcp==2.3.0"

Create server.py:

import os

from mcp.server import MCPServer

mcp = MCPServer("notes")
notes: list[str] = []


@mcp.tool()
def add_note(text: str) -> str:
    """Save a short note."""
    notes.append(text)
    return f"Saved note #{len(notes)}"


@mcp.tool()
def list_notes() -> list[str]:
    """Return every saved note."""
    return notes


if __name__ == "__main__":
    mcp.run(
        transport="streamable-http",
        host="0.0.0.0",
        port=int(os.environ.get("PORT", "8000")),
    )

Two lines matter more than they look:

  • transport="streamable-http" makes the server speak MCP over HTTP instead of stdin/stdout. Remote clients can only reach an HTTP server.
  • host="0.0.0.0" makes it listen on every network interface. The SDK's default is 127.0.0.1, which only accepts connections from inside the container itself, so the platform's proxy can't reach it and you get a 502 error.

The server answers on /mcp, so locally it lives at http://127.0.0.1:8000/mcp.

Note on the SDK version: version 2 of the mcp package renamed FastMCP to MCPServer. Many tutorials still use from mcp.server.fastmcp import FastMCP, which fails on 2.x. Pin the version so a future release doesn't change things under you.

Step 2: Add a Dockerfile

Put this Dockerfile next to server.py:

FROM python:3.12-slim
WORKDIR /app
RUN pip install --no-cache-dir "mcp==2.3.0"
COPY server.py .
EXPOSE 8000
CMD ["python", "server.py"]

Installing the package during the build (not at startup) keeps restarts fast: the container starts the server straight away instead of downloading packages each time.

Push both files to a GitHub repository.

Step 3: Deploy it on Hubfly space

  1. Sign in to the Hubfly space dashboard and click Create Project. Give it a name like notes-mcp, pick a region close to you, and (optional) open Advanced Options to set a monthly budget cap, for example $10.
  2. In the project, click Create → Create Container. Name it mcp-server.
  3. Under Deployment Source, choose Git repository, connect GitHub through the GitHub App, and pick your repository. A Dockerfile in the repository root is used for the build.
  4. On Resources, keep Shared Tier (Free): 0.3 vCPU and 256 MB RAM, enough for a small server.
  5. Click through to Review and press Deploy Container. The container page shows each deploy stage as it runs.

Open the Logs tab. When you see this line, the server is ready:

INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

Step 4: Give it a public HTTPS address

Right now the server only exists on the private network. To reach it from your laptop:

  1. Open the container's Network tab and click Add Domain.
  2. Keep Auto Endpoint. Under Suggested ports, the live scan shows 8000 · Open now. Click it.
  3. Leave TLS / SSL and Force HTTPS on, and click Create Subdomain.

The address is named after your container, for example https://mcp-server.eu1.hubfly.app. Your MCP endpoint is that address plus /mcp.

Check that it answers. This is the MCP "hello" (the initialize call):

curl -i https://<your-address>/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

You get HTTP 200, an mcp-session-id header and a reply that starts like this:

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{...},"protocolVersion":"2025-06-18","serverInfo":{"name":"notes","version":""}}}

The round trip takes well under a second once the server is awake.

Don't stop here. At this point anyone who finds the address can call your tools. Public addresses get visits from scanners within minutes of being created, so lock it before you connect anything.

Step 5: Lock it with an access key

Hubfly space can check an access key at the edge, before the request ever reaches your container. Your Python code doesn't change.

  1. Go back to the project overview and click the Firewall box above your container. This opens Security & Protection Studio.
  2. Check that your domain is selected in the domain picker, then open the Identity Access Control tab.
  3. Under Access Authentication Mode, choose API Key Only, switch Policy Enforcement State on, and click Save Policy Settings. The status changes from Permissive to Enforced.
  4. Open the API Keys tab. Enter a name (for example laptop-clients), leave Allowed Paths empty, optionally set an expiry date, and click Generate Secret Key.
  5. Copy the key (it starts with hfa_live_). It is shown only once, so save it in your password manager right away.

Now run the curl from Step 4 again without a key. You get:

HTTP/1.1 401
{"error":"identity_access_required","request_id":"..."}

A made-up key gets the same 401. These requests are stopped at the edge: they never show up in your container's logs, so a flood of bad requests doesn't cost your server any work.

To call it with the key, add one header:

-H "X-HubFly-Access-Key: $MCP_KEY"

Keep the key in an environment variable or your password manager, never in a Git repository. Issue one key per person or machine, with an expiry date, so you can retire one without breaking the others.

Step 6: Connect Claude Code, Cursor and Codex

First, put the key in your shell so it stays out of config files:

export MCP_KEY="hfa_live_..."   # paste your key

Claude Code (one command):

claude mcp add --transport http notes https://<your-address>/mcp \
  --header "X-HubFly-Access-Key: $MCP_KEY"

Run /mcp inside Claude Code to see the server and its two tools.

Cursor: add this to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):

{
  "mcpServers": {
    "notes": {
      "url": "https://<your-address>/mcp",
      "headers": { "X-HubFly-Access-Key": "${env:MCP_KEY}" }
    }
  }
}

Codex: add this to ~/.codex/config.toml:

[mcp_servers.notes]
url = "https://<your-address>/mcp"
env_http_headers = { "X-HubFly-Access-Key" = "MCP_KEY" }

env_http_headers reads the value from the MCP_KEY environment variable (use http_headers instead for a fixed value). The key name matters: Codex's setting is http_headers/env_http_headers, not the headers you use in Cursor, and a request without the key gets a 401.

Ask your client to list its MCP tools; you should see add_note and list_notes. Then try "add a note: buy oat milk" followed by "list my notes". Every client you connect uses the same server, so they share the same notes. If a client reports 401, the header name or key is wrong.

What about claude.ai and Claude Desktop connectors? Custom connectors there support OAuth or no auth. Fixed headers are a limited beta for some organizations, and a custom header name like X-HubFly-Access-Key needs approval from Anthropic first. For those apps, plan on OAuth.

What can go wrong

1. 421 Misdirected Request with "Invalid Host header". This happens if you run the server with Uvicorn directly (uvicorn.run(mcp.streamable_http_app(), ...)) instead of mcp.run(...). In that mode the SDK turns on DNS-rebinding protection and only accepts Host: 127.0.0.1 or localhost, so every request through your public address is refused. Fix it by allowing your address:

from mcp.server.transport_security import TransportSecuritySettings

app = mcp.streamable_http_app(
    transport_security=TransportSecuritySettings(
        allowed_hosts=["mcp-server.eu1.hubfly.app"]  # your address
    )
)

Note that once you set allowed_hosts, local requests to 127.0.0.1 are refused with the same 421, so add "127.0.0.1:8000" to the list if you also run it locally. The mcp.run(transport="streamable-http", host="0.0.0.0") setup from Step 1 doesn't hit this.

2. 502 Bad Gateway from the address. The server is listening on 127.0.0.1, usually because host="0.0.0.0" is missing. Add it and redeploy. If Suggested ports in the Add Domain dialog shows nothing, the server isn't listening yet: check the Logs tab.

3. The client times out on the first call of the day. Shared-tier containers suspend after 15 minutes of inactivity, and the first call after a pause can be slow. Some clients give up quickly (Codex waits 10 seconds by default; raise it with startup_timeout_sec = 30 in the same TOML block). If you use the server all day, move it to the dedicated tier with Auto Sleep off.

Bonus: notes disappear after a redeploy. This example keeps notes in memory, and everything outside a volume is wiped when the container restarts or redeploys. For real data, use a database (see below). Also keep an eye on memory: this small server already uses most of the shared tier's 256 MB, so heavier tools need a dedicated container.

What it costs

  • Shared tier: free within the 360 compute hours a month included in the free tier. The project's budget panel shows a projected $0.00/month for this setup.
  • Always on (dedicated, 0.5 vCPU, 512 MB RAM, 5 GB disk, Auto Sleep off): about $2.25 a month.
    • CPU: 0.5 × $0.004 × 730 h = $1.46
    • RAM: 0.5 GB × $0.002 × 730 h = $0.73
    • Disk: 5 GB × $0.00001536 × 730 h = $0.06
  • The access key check, the HTTPS address and the certificate are free.

Takeaway

Getting an MCP server off localhost takes four things: speak HTTP (streamable-http), listen on 0.0.0.0, give it an HTTPS address, and check a key before requests reach your code. Do those once and every AI tool you use shares the same tools.

Go further on Hubfly space

  • Give your tools real data: add a PostgreSQL database from the templates with a volume so data survives redeploys, and connect to it over the private network. See the Compose docs and Docker health checks that catch real problems for a Compose setup.
  • Let your team in with their login instead of keys: switch the route to Login or API Key so teammates sign in in the browser while tools keep using keys. See Identity Access.
  • Let your coding agent deploy for you: the Hubfly space AI skill teaches Claude Code, Codex and Cursor to deploy and update projects with the hubfly CLI.

Try it on Hubfly space → Free tier, 360 compute hours a month, no card.

來源:DEV Community · dev.to