Nov 9, 20268 min read

How to Deploy an MCP Server to AWS

Host a remote MCP server on AWS over Streamable HTTP with authentication and storage behind the tools, from Encore on Fargate to Bedrock AgentCore Runtime, ECS by hand and Lambda.

The Model Context Protocol started with stdio servers that run as a child process of Claude Desktop or Cursor on one laptop. A server that a whole team, a CI job or a hosted agent can use has to be remote: an HTTPS endpoint speaking the Streamable HTTP transport, with authentication in front and the tools' data stored somewhere other than a laptop.

This guide deploys a remote MCP server written in Go to AWS with Encore, with bearer token authentication and an S3 bucket behind its tools, and then compares Amazon Bedrock AgentCore Runtime, ECS Fargate by hand and Lambda. The Encore server below was run with encore run and called with curl, and the outputs are from that run.

Ways to host an MCP server on AWS at a glance

EncoreBedrock AgentCore RuntimeECS Fargate by handLambda
Runs asContainers on Fargate (or EKS)MicroVMs per sessionContainers on FargateFunctions
TransportStreamable HTTP, statelessStreamable HTTP at 0.0.0.0:8000/mcpStreamable HTTPStreamable HTTP, stateless
AuthYour code, secret in Secrets ManagerCognito or another OAuth providerYour code or ALB with CognitoYour code or API Gateway authorizer
Storage for toolsBuckets, databases, caches declared in codeSeparate AWS resourcesSeparate AWS resourcesSeparate AWS resources
Infra to set upNone by handRuntime, ECR, execution role, identity providerVPC, ALB, ECS, IAMFunction, URL or API Gateway, IAM

How do you deploy an MCP server to AWS with Encore?

Encore

Encore combines an open source infrastructure SDK with a platform that provisions and deploys into your own AWS or GCP account. The MCP server is an ordinary endpoint in an Encore service, and the storage its tools use is declared in the same Go file, so Encore runs the bucket locally and provisions S3 for it on AWS.

Set up the app

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

~
$ encore app create runbook-mcp --example=hello-world$ cd runbook-mcp && go get github.com/modelcontextprotocol/go-sdk@v1.8.0$ mkdir runbook

The server

The tools save and read runbook notes in an object storage bucket. The official MCP Go SDK builds the server and the Streamable HTTP handler, and a raw endpoint checks the token from Encore secrets before handing the request over:

// runbook/runbook.go package runbook import ( "context" "crypto/subtle" "errors" "fmt" "io" "net/http" "strings" "encore.dev/storage/objects" "github.com/modelcontextprotocol/go-sdk/mcp" ) // Runbook notes the tools read and write. S3 on AWS, a local emulator in `encore run`. var notes = objects.NewBucket("runbook-notes", objects.BucketConfig{}) // Clients send McpToken as a bearer token. var secrets struct { McpToken string } type SaveNoteInput struct { Service string `json:"service" jsonschema:"the service the note is about"` Text string `json:"text" jsonschema:"the note in markdown"` } type GetNoteInput struct { Service string `json:"service" jsonschema:"the service to look up"` } func text(s string) *mcp.CallToolResult { return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: s}}} } func saveNote(ctx context.Context, _ *mcp.CallToolRequest, in SaveNoteInput) (*mcp.CallToolResult, any, error) { w := notes.Upload(ctx, in.Service+".md") if _, err := io.Copy(w, strings.NewReader(in.Text)); err != nil { w.Abort(err) return nil, nil, err } if err := w.Close(); err != nil { return nil, nil, err } return text("Saved note for " + in.Service), nil, nil } func getNote(ctx context.Context, _ *mcp.CallToolRequest, in GetNoteInput) (*mcp.CallToolResult, any, error) { r := notes.Download(ctx, in.Service+".md") defer r.Close() b, err := io.ReadAll(r) if errors.Is(err, objects.ErrObjectNotFound) { res := text(fmt.Sprintf("No note for %s", in.Service)) res.IsError = true return res, nil, nil } if err != nil { return nil, nil, err } return text(string(b)), nil, nil } //encore:service type Service struct { handler http.Handler } func initService() (*Service, error) { server := mcp.NewServer(&mcp.Implementation{Name: "runbook-notes", Version: "1.0.0"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "save_note", Description: "Save a runbook note for a service"}, saveNote) mcp.AddTool(server, &mcp.Tool{Name: "get_note", Description: "Read the runbook note for a service"}, getNote) // Stateless: no session state in memory, so any instance can serve any request. h := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return server }, &mcp.StreamableHTTPOptions{Stateless: true, JSONResponse: true}) return &Service{handler: h}, nil } // MCP serves the Streamable HTTP transport at /mcp. // //encore:api public raw method=POST path=/mcp func (s *Service) MCP(w http.ResponseWriter, req *http.Request) { want := "Bearer " + secrets.McpToken if subtle.ConstantTimeCompare([]byte(req.Header.Get("Authorization")), []byte(want)) != 1 { http.Error(w, "unauthorized", http.StatusUnauthorized) return } s.handler.ServeHTTP(w, req) }

The SDK derives each tool's input schema from the Go struct and its jsonschema tags. A missing note comes back as a tool result with isError set, which the model can read and react to, instead of a protocol error.

Run it locally

Put a local token in .secrets.local.cue next to encore.app (McpToken: "local-dev-token"), start the app, and call the endpoint the way an MCP client does:

~/runbook-mcp
$ encore run$ printf 'Authorization: Bearer local-dev-token\nContent-Type: application/json\nAccept: application/json, text/event-stream\n' > h.txt$ curl -X POST localhost:4000/mcp -H @h.txt -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'{"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{},"tools":{"listChanged":true}},"protocolVersion":"2025-06-18","serverInfo":{"name":"runbook-notes","version":"1.0.0"}}}$ curl -X POST localhost:4000/mcp -H @h.txt -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"save_note","arguments":{"service":"billing","text":"Restart the worker if the queue backs up."}}}'{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"Saved note for billing"}]}}$ curl -X POST localhost:4000/mcp -H @h.txt -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_note","arguments":{"service":"billing"}}}'{"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"Restart the worker if the queue backs up."}]}}$ curl -X POST localhost:4000/mcp -d '{}'unauthorized

tools/list returned both tools with their generated schemas, and a get_note call for a service without a note returned "isError":true with the message. The MCP Inspector can connect to http://localhost:4000/mcp with the same header for an interactive check.

Deploy to your AWS account

Set the production token with encore secret set --type prod McpToken. Connect your AWS account in the Encore dashboard under Integrations > Connect Cloud, as described in Connect your cloud account, create an environment on AWS and run git push encore. Encore provisions the service on Fargate behind an Application Load Balancer, the S3 bucket for the notes and a Secrets Manager entry for the token, and it provisions an IAM role for the service, as listed in the AWS infrastructure docs.

Clients connect with the deployed URL and the token, for example in Claude Code:

~
$ claude mcp add --transport http runbook https://<your-api-url>/mcp \ --header "Authorization: Bearer $MCP_TOKEN"

Adding a database or a cache for the tools later is one more declaration in Go, and the next push provisions it. The same applies when an AI coding agent extends the server: it adds a tool and the resource it needs in code, tests it under encore run, and the change reaches your AWS account through the normal deploy. That review-and-deploy path is what makes Encore the safe way for AI agents to ship backend changes to your cloud.

Best fit: teams that want a shared MCP server in their own AWS account, with the tools' storage and secrets provisioned from the same Go code and the server testable locally.

Amazon Bedrock AgentCore Runtime

AgentCore Runtime is the AWS service for hosting MCP servers and agents, and it requires the server to listen on 0.0.0.0:8000/mcp. The server ships either as code the AgentCore CLI packages or as an ARM64 container image in ECR, and runs in microVMs that requests with the same session ID are routed to. Inbound authentication uses an OAuth provider such as Cognito, and clients call a bedrock-agentcore invocation URL that contains the runtime ARN. Any storage the tools use, such as S3 or DynamoDB, is created and permissioned separately on the execution role.

Best fit: teams already building on Bedrock that want session-isolated microVMs and OAuth through Cognito managed by AWS.

ECS Fargate by hand

The general container route works for any SDK: build an image for the TypeScript, Python or Go server, push it to ECR, and run it as an ECS service behind an Application Load Balancer with a certificate from ACM. In stateless mode the target group needs no sticky sessions. You also build the VPC, the task and execution roles, the security groups, any S3 bucket or database the tools use and the Secrets Manager entry for the token, and wire the image build and service update into CI.

Best fit: teams with an existing ECS setup in Terraform or CDK who want the MCP server managed like their other services.

Lambda

A stateless Streamable HTTP server fits Lambda, since every MCP call is one POST with one response when JSON responses are turned on. A Function URL or API Gateway in front gives it an HTTPS address, and an API Gateway authorizer can check tokens before the function runs. Tools that stream progress over server-sent events or take longer than the timeout do not fit, and cold starts add latency to the first call after idle.

Best fit: low-traffic MCP servers with short, request-response tools where per-request pricing matters.

How to choose

Pick Encore when you want the MCP server, its authentication and the storage behind its tools in one Go codebase, provisioned in your own AWS account and runnable locally. Pick AgentCore Runtime when you are on Bedrock and want session isolation and OAuth handled by AWS, ECS Fargate by hand when the platform team already runs ECS in Terraform, and Lambda for small request-response servers.

Frequently asked questions

How do I host an MCP server on AWS?

Serve the MCP Streamable HTTP transport from an HTTPS endpoint and run it on Fargate behind a load balancer, on Bedrock AgentCore Runtime, or on Lambda. Use stateless mode so any instance can answer any request, put authentication in front of the endpoint, and give the tools an IAM role scoped to the resources they touch.

How do I deploy an MCP server to AWS with Encore?

Create the server with the official MCP Go SDK, wrap it in mcp.NewStreamableHTTPHandler with Stateless set to true, and serve it from a raw endpoint such as //encore:api public raw method=POST path=/mcp that checks a bearer token from Encore secrets. Declare any buckets or databases the tools use in Go, connect your AWS account in the Encore dashboard and run git push encore. Encore provisions Fargate, a load balancer, the S3 bucket and Secrets Manager in your own AWS account.

What is the difference between stdio and Streamable HTTP MCP servers?

A stdio server runs as a child process of the client on the same machine, so it cannot be shared. A Streamable HTTP server listens on a URL, which lets several users and agents connect to one deployed server, and requires authentication and hosting like any other web API.

Should an MCP server on AWS be stateless?

In most cases it should, because in stateless mode the server keeps no session in memory, so a load balancer can send each request to any instance and deploys do not break open sessions. Stateful mode is needed for features such as server-initiated sampling on older protocol versions, and then requests with the same session ID have to reach the same instance.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders