Skip to navigationSkip to main contentSkip to footerScaleway Docs HomepageAsk our AI
Ask our AI

Deploying a remote MCP server on Serverless Containers

MCP
model
context
protocol
serverless
containers
go
docker
opencode

Model Context Protocol (External link) (MCP) is an open protocol that standardizes how AI assistants and agents interact with external tools and data sources. An MCP server exposes tools, resources, and prompts over a JSON-RPC interface, allowing clients such as OpenCode (External link), Claude, or Cursor to call them securely.

This tutorial shows you how to build a lightweight MCP server in Go, containerize it, push its image to the Scaleway Container Registry, deploy it on Serverless Containers, and connect it to OpenCode. You use the Scaleway console for most of the deployment steps, and Docker to build and push the image.

Before you start

To complete the actions presented below, you must have:

Why host an MCP server on Serverless Containers

Local MCP servers communicate with AI clients over standard input/output (stdio). Remote MCP servers instead expose an HTTP endpoint that AI agents can reach over the network.

Hosting an MCP server on Serverless Containers offers several advantages:

  • Scale-to-zero cost: AI agents invoke tools in intermittent bursts. Serverless Containers scale to zero when idle, so you only pay for active execution time.
  • Stateless architecture: Remote MCP uses HTTP transports where each request carries its own execution context, so containers scale horizontally without sticky sessions.
  • Built-in security: You can make your container private, and the Scaleway API Gateway enforces X-Auth-Token authentication before any request reaches your container.
  • Zero maintenance: Scaleway handles automated OS patching, SSL termination, and horizontal scaling.

What the MCP server does

The example server is a single Go binary built on the official MCP Go SDK (External link). It exposes the following tools over the Streamable HTTP transport.

ToolDescription
get_server_timeReturns the current UTC time from the container.
echoEchoes back the input message.
get_app_nameReturns the value of the SCW_APPLICATION_NAME environment variable.

The server exposes the following endpoints:

  • /mcp - the JSON-RPC endpoint that MCP clients call (Streamable HTTP/SSE transport)
  • /health - a health check endpoint that returns OK

Prepare the MCP server source code

  1. In a terminal, create a new directory and navigate into it:

    mkdir mcp-go-server
    cd mcp-go-server
  2. Create a main.go file, then add the code below. It defines the 3 tools, registers them on an MCP server, and serves them on the /mcp endpoint using the official Go SDK:

    package main
    
    import (
        "context"
        "log/slog"
        "net/http"
        "os"
        "time"
    
        "github.com/modelcontextprotocol/go-sdk/mcp"
    )
    
    type echoArgs struct {
        Message string `json:"message" jsonschema:"The string to echo back"`
    }
    
    func main() {
        port := os.Getenv("PORT")
        if port == "" {
            port = "8080"
        }
    
        server := mcp.NewServer(&mcp.Implementation{
            Name:    "scaleway-test-mcp",
            Version: "1.1.0",
        }, nil)
    
        mcp.AddTool(server, &mcp.Tool{
            Name:        "get_server_time",
            Description: "Returns current UTC time from the Scaleway container",
        }, func(_ context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) {
            return &mcp.CallToolResult{
                Content: []mcp.Content{&mcp.TextContent{
                    Text: "Scaleway Container UTC Time: " + time.Now().UTC().Format(time.RFC3339),
                }},
            }, nil, nil
        })
    
        mcp.AddTool(server, &mcp.Tool{
            Name:        "echo",
            Description: "Echoes back the input message",
        }, func(_ context.Context, req *mcp.CallToolRequest, in echoArgs) (*mcp.CallToolResult, any, error) {
            return &mcp.CallToolResult{
                Content: []mcp.Content{&mcp.TextContent{
                    Text: "Echo from Scaleway: " + in.Message,
                }},
            }, nil, nil
        })
    
        mcp.AddTool(server, &mcp.Tool{
            Name:        "get_app_name",
            Description: "Returns the SCW_APPLICATION_NAME environment variable from the container",
        }, func(_ context.Context, _ *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) {
            appName := os.Getenv("SCW_APPLICATION_NAME")
            if appName == "" {
                appName = "[NOT_SET]"
            }
    
            return &mcp.CallToolResult{
                Content: []mcp.Content{&mcp.TextContent{
                    Text: "SCW_APPLICATION_NAME: " + appName,
                }},
            }, nil, nil
        })
    
        mux := http.NewServeMux()
    
        mux.HandleFunc("GET /health", handleHealth)
        mux.Handle("/mcp", mcp.NewStreamableHTTPHandler(func(_ *http.Request) *mcp.Server {
            return server
        }, &mcp.StreamableHTTPOptions{
            Stateless:                  true,
            DisableLocalhostProtection: true, // auth is enforced upstream by the Scaleway platform
        }))
    
        slog.Info("Starting MCP server", "port", port)
    
        httpServer := &http.Server{
            Addr:              ":" + port,
            ReadHeaderTimeout: 10 * time.Second,
            Handler:           mux,
        }
    
        err := httpServer.ListenAndServe()
        if err != nil {
            slog.Error("Server crashed", "error", err)
            os.Exit(1)
        }
    }
    
    func handleHealth(w http.ResponseWriter, _ *http.Request) {
        w.WriteHeader(http.StatusOK)
        _, _ = w.Write([]byte("OK"))
    }
    Note

    The server disables localhost protection because authentication is enforced upstream by the Scaleway platform when you make the container private.

  3. Initialize the Go module and download the MCP Go SDK dependency:

    go mod init mcp-go-server
    go get github.com/modelcontextprotocol/go-sdk
    go mod tidy
  4. Create a Dockerfile at the root of your project:

    # syntax=docker/dockerfile:1
    
    # Step 1: Build binary using modern Go toolchain
    FROM golang:1.27-alpine AS builder
    
    WORKDIR /app
    
    COPY go.mod go.sum ./
    
    RUN go mod download
    
    COPY main.go .
    
    RUN CGO_ENABLED=0 GOOS=linux go build \
        -trimpath \
        -ldflags="-w -s" \
        -o mcp-server main.go
    
    # Step 2: Minimal non-root distroless runtime (~5MB final image)
    FROM gcr.io/distroless/static-debian13:nonroot
    
    WORKDIR /
    
    COPY --from=builder /app/mcp-server /mcp-server
    
    EXPOSE 8080
    ENV PORT=8080
    
    USER nonroot:nonroot
    
    ENTRYPOINT ["/mcp-server"]
    Note

    Building in 2 stages helps reduce the final image size and improve security:

    • The first stage compiles a statically linked Go binary.
    • The second stage copies only that binary into a minimal distroless (External link) image that runs as a non-root user.

    The resulting image is about 5 MB, which keeps cold starts fast.

  5. Test the image locally:

    docker build --platform linux/amd64 -t mcp-server:latest .
    docker run --rm -p 8080:8080 mcp-server:latest

    Open http://localhost:8080/health (External link) in your web browser to check the health endpoint.

Create a Container Registry namespace

To store your container image, you need a Container Registry namespace. Namespaces let you to manage and organize your images.

  1. In the Scaleway console (External link) side menu, go to Storage or Containers > Container Registry.
  2. Click Create namespace.
  3. Enter a name for your namespace (for example, mcp-go-server), select the region, and click Create namespace.
  4. Copy the namespace endpoint from the Settings tab of your namespace (for example, rg.fr-par.scw.cloud/mcp-go-server).
Note

See How to create a namespace for more information.

Build and push the image

  1. Sign in to your Container Registry namespace with the Docker CLI, using your Scaleway IAM secret key as the password:

    echo <SCW_SECRET_KEY> | docker login rg.<region>.scw.cloud/<namespace> -u nologin --password-stdin

    The message Login Succeeded appears once you are signed in.

  2. Build the image and tag it with your registry endpoint:

    docker build --platform linux/amd64 -t rg.<region>.scw.cloud/<namespace>/mcp-server:latest .

    Replace <region> and <namespace> with the region and name of the registry namespace you created.

  3. Push the image to the Container Registry:

    docker push rg.<region>.scw.cloud/<namespace>/mcp-server:latest
Note

Use Scaleway Container Registry to avoid rate limiting errors and risks linked to pricing changes of external registries.

Deploy the container on Serverless Containers

Tip

Deploy your container in the same region as your Container Registry namespace so Serverless Containers can pull the image.

  1. In the Scaleway console (External link) side menu, go to Serverless Compute > Containers.
  2. Click Create namespace, enter a name for your namespace (for example, mcp-go-server-ns), and click Create namespace and add container. The container creation wizard opens.
  3. In Container image configuration, configure the following:
    • Select Scaleway Container Registry.
    • Select the region and namespace of the registry where you pushed the image.
    • Select the mcp-server image and the latest tag.
    • Enter 8080 as the container port (matches the PORT environment variable in the Dockerfile).
    • Enter a name for your container (for example, mcp-server).
  4. In Container resources and scaling, configure the following:
    • CPU: 250 mVCPU.
    • Memory: 256 MB. Go binaries are lightweight.
    • Keep the default autoscaling values, or set the minimum instances to 1 to avoid cold starts.
  5. In Advanced options > Security, set the Privacy policy to Private. A private container rejects any request without a valid X-Auth-Token header, so only your AI agent can call it.
  6. Click Deploy container. The deployment can take up to a minute.

Test the deployment

  1. Once the container is ready, copy the container endpoint from the Endpoints tab of your container.
  2. Check the health endpoint:
    curl -H "X-Auth-Token: <SCW_SECRET_KEY>" https://<YOUR_CONTAINER_ENDPOINT>/health
    The response is OK.
  3. List the available tools by calling the /mcp endpoint:
    curl -X POST https://<YOUR_CONTAINER_ENDPOINT>/mcp \
      -H "Content-Type: application/json" \
      -H "X-Auth-Token: <SCW_SECRET_KEY>" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
    The response lists the three tools defined in the server.
  4. Call the get_server_time tool:
    curl -X POST https://<YOUR_CONTAINER_ENDPOINT>/mcp \
      -H "Content-Type: application/json" \
      -H "X-Auth-Token: <SCW_SECRET_KEY>" \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_server_time","arguments":{}}}'
    The response contains the current UTC time returned by the container.

Connect OpenCode to the MCP server

  1. Open your OpenCode configuration file (opencode.json in your project, or ~/.config/opencode/opencode.json).
  2. Add the following configuration, replacing the placeholders:
    {
      "$schema": "https://opencode.ai/config.json",
      "mcp": {
        "scaleway-mcp-server": {
          "type": "remote",
          "url": "https://<YOUR_CONTAINER_ENDPOINT>/mcp",
          "headers": {
            "X-Auth-Token": "<YOUR_SCALEWAY_IAM_SECRET_KEY>"
          }
        }
      }
    }
    The X-Auth-Token header passes your Scaleway IAM secret key, which the API Gateway uses to authorize calls to your private container.
  3. Restart OpenCode, then ask the assistant to use one of the tools, for example:
    • "What time is it?"
    • "What is the application name?"

Going further

Clean up

To avoid incurring costs, delete the resources you created:

  1. In the Serverless Compute > Containers section of the console, delete the container namespace mcp-go-server-ns.
  2. Go to Storage or Containers > Container Registry, and delete the registry namespace mcp-go-server.
Questions?

Visit our Help Center and find the answers to your most frequent questions.

Visit Help Center
No Results