Deploying a remote MCP server on Serverless Containers
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:
- A Scaleway account logged into the console (External link)
- Owner status or IAM permissions allowing you to perform actions in the intended Organization
- Docker (External link) or Docker Engine (External link) installed on your local machine
- A Scaleway IAM API key used to authenticate against the Container Registry
- Go 1.27 or newer (External link) installed to generate the Go module files
- OpenCode (External link) installed to test the remote MCP server
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-Tokenauthentication 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.
| Tool | Description |
|---|---|
get_server_time | Returns the current UTC time from the container. |
echo | Echoes back the input message. |
get_app_name | Returns 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 returnsOK
Prepare the MCP server source code
-
In a terminal, create a new directory and navigate into it:
mkdir mcp-go-server cd mcp-go-server -
Create a
main.gofile, then add the code below. It defines the 3 tools, registers them on an MCP server, and serves them on the/mcpendpoint 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")) } -
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 -
Create a
Dockerfileat 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 /app/mcp-server /mcp-server EXPOSE 8080 ENV PORT=8080 USER nonroot:nonroot ENTRYPOINT ["/mcp-server"] -
Test the image locally:
docker build --platform linux/amd64 -t mcp-server:latest . docker run --rm -p 8080:8080 mcp-server:latestOpen 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.
- In the Scaleway console (External link) side menu, go to Storage or Containers > Container Registry.
- Click Create namespace.
- Enter a name for your namespace (for example,
mcp-go-server), select the region, and click Create namespace. - Copy the namespace endpoint from the Settings tab of your namespace (for example,
rg.fr-par.scw.cloud/mcp-go-server).
Build and push the image
-
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-stdinThe message
Login Succeededappears once you are signed in. -
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. -
Push the image to the Container Registry:
docker push rg.<region>.scw.cloud/<namespace>/mcp-server:latest
Deploy the container on Serverless Containers
- In the Scaleway console (External link) side menu, go to Serverless Compute > Containers.
- 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. - 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-serverimage and thelatesttag. - Enter 8080 as the container port (matches the
PORTenvironment variable in the Dockerfile). - Enter a name for your container (for example,
mcp-server).
- 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.
- In Advanced options > Security, set the Privacy policy to Private. A private container rejects any request without a valid
X-Auth-Tokenheader, so only your AI agent can call it. - Click Deploy container. The deployment can take up to a minute.
Test the deployment
- Once the container is ready, copy the container endpoint from the Endpoints tab of your container.
- Check the health endpoint:
The response is
curl -H "X-Auth-Token: <SCW_SECRET_KEY>" https://<YOUR_CONTAINER_ENDPOINT>/healthOK. - List the available tools by calling the
/mcpendpoint:The response lists the three tools defined in the server.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"}' - Call the
get_server_timetool:The response contains the current UTC time returned by the container.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":{}}}'
Connect OpenCode to the MCP server
- Open your OpenCode configuration file (
opencode.jsonin your project, or~/.config/opencode/opencode.json). - Add the following configuration, replacing the placeholders:
The
{ "$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>" } } } }X-Auth-Tokenheader passes your Scaleway IAM secret key, which the API Gateway uses to authorize calls to your private container. - 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
- Add a custom domain to your container.
- Monitor logs and metrics of your container.
- Learn how to build and push a container image in a CI/CD pipeline.
Clean up
To avoid incurring costs, delete the resources you created:
- In the Serverless Compute > Containers section of the console, delete the container namespace
mcp-go-server-ns. - Go to Storage or Containers > Container Registry, and delete the registry namespace
mcp-go-server.
Visit our Help Center and find the answers to your most frequent questions.
Visit Help Center