Hosting an MCP Server on AWS in 2026
All the four Paths and How to Choose
Series: Golden Jacket Field Notes (22 articles)
- …
- 8Hosting an MCP Server on AWS in 2026 This article
Disclaimer
Originally published on Medium as Week 9 of Golden Jacket Field Notes**
Canonical URL: https://medium.com/aws-tip/hosting-an-mcp-server-on-aws-in-2026-four-paths-and-how-to-choose-f3510ae378b8
This is a republication on AWS Builder Center for the AWS community. The piece below appeared first on Medium and is preserved here in its original form.
Golden Jacket Field Notes, Week 9
Lambda Function URL, Lambda with API Gateway, ECS on Fargate, or Bedrock AgentCore Runtime. The trade-offs nobody explains, the two-permission trap that cost me two hours, and a decision framework that holds up under production pressure.
The real problem
I had a working Model Context Protocol (MCP) server running locally over standard input/output, exposing two tools and a couple of resources to a Strands agent on my laptop. The protocol worked. The agent reasoned correctly. The demo was clean.
Then came the moment every prototype faces. Time to host this in AWS so a teammate’s agent could consume the same server remotely.
That sounds trivial. It is not.
There are four viable AWS paths to host an MCP server in 2026. They look similar at first glance. All four terminate in an HTTPS endpoint that speaks JSON-RPC 2.0 over streamable HTTP. They diverge dramatically in cost shape, security posture, operational burden, and what the official documentation will quietly let you ship into production.
I picked the simplest path first and lost two hours debugging a silent 403 Forbidden. Worth the lesson. Here is the map I wish I had before I started.
Why this matters in 2026
MCP came out of an exploratory phase in 2024 and 2025. Anthropic released the specification, the Python software development kit (SDK) matured, AWS announced Bedrock AgentCore, and a wave of MCP servers appeared on GitHub. Most of them ran locally over standard input/output, embedded inside a desktop client like Claude Desktop or Cursor.
In 2026 the question shifted. Teams started asking how to host MCP servers as shared services. One server, multiple agents, possibly multiple consumers across teams or organizations. That hosting question does not have a single canonical answer yet, and AWS now offers four distinct primitives that can host one.
If you pick the wrong one, the cost is rework. Sometimes weeks of it. The decision deserves more rigor than “the tutorial used Lambda, so I will use Lambda.”
Path 1: Lambda Function URL
A managed HTTPS endpoint attached directly to a Lambda function, no API Gateway in front of it. You get a URL like https://abc123xyz.lambda-url.us-east-1.on.aws/, and the function handles the request directly.
Setup time is measured in minutes. The CloudFormation or Serverless Application Model (SAM) resource is AWS::Lambda::Url with a TargetFunctionArn. The Lambda itself can be a few dozen lines of Python that parses incoming JSON-RPC payloads, dispatches to the right method, and returns the result. No external dependencies required if you stay within the Python standard library.
For an MCP server this is more than a starting point. With AWS_IAM as the authentication mode and SigV4 from internal callers, this is production-grade for stateless workloads consumed inside a single AWS trust boundary. Cold start under 200 milliseconds if you keep the dependency list short. No idle cost. No second service in the path. The framing of “Function URL is just a laboratory tool” undersells what it actually delivers.
The dispatch core of a working handler:
python
def lambda_handler(event, context):
raw_body = event.get("body", "{}") or "{}"
message = json.loads(raw_body)
method = message.get("method", "")
request_id = message.get("id")
params = message.get("params", {})
handler = METHOD_DISPATCH.get(method)
if handler is None:
response = jsonrpc_error(
request_id, -32601, f"Method not found: {method}"
)
else:
response = handler(request_id, params)
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(response),
}
raw_body = event.get("body", "{}") or "{}"
message = json.loads(raw_body)
method = message.get("method", "")
request_id = message.get("id")
params = message.get("params", {})
handler = METHOD_DISPATCH.get(method)
if handler is None:
response = jsonrpc_error(
request_id, -32601, f"Method not found: {method}"
)
else:
response = handler(request_id, params)
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(response),
}
That skeleton plus the per-method handlers for initialize, tools/list, and tools/call is the entire server. Fewer than 200 lines.
The constraint is binary authentication. You pick AuthType=NONE, meaning anyone with the URL can invoke, or AWS_IAM, meaning callers must sign requests with Signature Version 4. There is no Cognito option, no Lambda Authorizer, no OAuth. Stateless is also a hard constraint at the architecture level, since Lambda has no concept of long-lived sessions tied to a specific compute instance. For an MCP server consumed by an agent inside the same AWS account, AWS_IAM works well. For anything broader, or for any workload that needs sessions, you have a problem we will return to.
Path 2: Lambda + API Gateway
Same Lambda backend, but with API Gateway in front as the HTTPS frontend. You get a regional endpoint, optionally with a custom domain, optionally with AWS Web Application Firewall (WAF), optionally with rate limits per consumer, optionally with a Cognito User Pool authorizer.
Cost is one dollar per million requests on top of the Lambda cost. For an MCP server consumed by a single agent, that is likely under a dollar a month. For a server consumed by hundreds of agents, it might still be under twenty dollars. Almost never the deciding factor.
Where it earns its keep is authentication and authorization. If your MCP server needs to authenticate external callers (a partner’s agent, a customer-facing application, an OAuth-secured client), API Gateway gives you Cognito User Pool authorizers, Lambda Authorizers for custom token validation, and the foundation to build OAuth 2.1 if you implement the issuer yourself.
Where it fails to earn its keep is when none of the above is needed. If your MCP server is internal, single-tenant, and consumed by a sibling AWS resource that can sign with SigV4, the API Gateway layer adds latency, complexity, and a few dollars in monthly cost without paying for any of it.
State remains a constraint. API Gateway plus Lambda is still stateless transport. You can fake sessions with DynamoDB and a session identifier, but at that point you are reinventing what other paths give you natively.
Path 3: ECS on Fargate
Container-based hosting on managed compute. You define a Task Definition with your MCP server image, set vCPU and memory, deploy to a Fargate cluster, and put an Application Load Balancer (ALB) or Network Load Balancer (NLB) in front. The container runs continuously. Sessions live in the container’s memory or in an external store, your choice.
This is the path that gets least attention in agentic-stack tutorials, and it deserves more than it gets. For stateful MCP servers, Fargate is the most flexible option AWS offers without the constraints of AgentCore.
What it gives you that the Lambda paths cannot. Stateful sessions in container memory, with ALB sticky sessions tying a client to a specific task. Cold start absorbed by always-running tasks (the trade-off being you pay for those tasks while idle). Container-level control over the runtime, including non-Python binaries, custom system libraries, and any architecture you want. ALB-level OAuth integration via Cognito, which is more straightforward to wire than the Lambda Authorizer pattern.
What it gives you that AgentCore does not. Free choice between x86_64 and ARM64. No per-session pricing. No vendor lock-in to a specific runtime resource type. The same skill set your team already has from running other ECS workloads. Native support for non-Python runtimes (Rust, Go, Node.js) without the friction of building custom containers around the AgentCore SDK conventions.
The cost shape is the inverse of Lambda. You pay continuously for running tasks, regardless of traffic. For an always-on MCP server with steady consumption, this is more predictable and often cheaper than per-invocation pricing. For a bursty workload that sits idle most of the time, this is more expensive.
The other costs to budget for. The ALB itself is roughly sixteen dollars per month before you process any traffic. NAT Gateway charges if the container needs to reach Bedrock or other AWS services from a private subnet without a VPC endpoint. ECS task minimums (you cannot run a 0.1 vCPU task; the smallest is 0.25 vCPU paired with 0.5 gigabytes of RAM).
Where Fargate fits the MCP use case best. Stateful MCP servers where the team prefers container expertise over learning a new runtime resource. MCP servers with non-Python implementations (Rust, Go, Node.js) where the AgentCore SDK is more friction than help. Workloads with predictable, steady traffic where idle cost is not a penalty. Cases where you want full control over the container runtime, networking, or operating system base image.
Where Fargate fits poorly. Bursty workloads with long idle periods (Lambda wins on cost). Workloads that need true microVM-level tenant isolation between sessions (AgentCore wins). Teams that want a turnkey integration with Strands runtime and managed agentic primitives (AgentCore wins again).
Path 4: Bedrock AgentCore Runtime
A managed runtime designed specifically for hosting agent code. AgentCore went generally available (GA) in October 2025. The agent runtime resource was added to the Terraform AWS provider in version 6.x, and CloudFormation supports AWS::BedrockAgentCore::Runtime natively. Stateless MCP server support arrived in December 2025. Stateful microVM-isolated runtimes followed in March 2026.
Cost is pay-per-use, billed by session count and CPU consumption. There is no idle baseline charge, but the per-session pricing is meaningfully higher than a Lambda invocation for equivalent stateless work. For an always-on MCP server with steady traffic, AgentCore can cost more than Fargate when you do the math at full scale. For bursty agentic workloads with sporadic sessions, AgentCore wins on the math because there is no idle cost.
What it gives you that the other three do not. Full session isolation via microVMs (one tenant cannot influence another’s runtime, even on shared underlying hardware). Deep integration with the Strands runtime. Managed observability hooks tied into Amazon CloudWatch and AWS X-Ray. A security posture that does not require you to reinvent authentication boilerplate. Multi-tenancy primitives built into the runtime, rather than bolted on top of a generic compute service.
What it costs you that the other three do not is a set of constraints that look small until they bite. AgentCore now supports two deployment modes: direct_code_deploy (the default, packages your Python code without requiring a Docker build) and container deployment (you provide your own container image). The constraints that follow apply to container deployment. If you stay with direct_code_deploy, you skip them entirely, which is a meaningful escape hatch the original AgentCore announcement did not have.
For container deployment, ARM64 architecture is mandatory. The container image must be built for ARM64. Python wheels with native dependencies must be the aarch64-manylinux2014 variants. If you build on an x86 laptop without specifying --platform linux/arm64, the image runs but imports fail at the first call to a native module. The failure is silent until the agent invokes the broken path. I learned this once. I do not plan to learn it twice.
The runtime is regional. As of May 2026, AgentCore Runtime is available in 15+ AWS regions, with São Paulo (sa-east-1) added in early May 2026. That closes the long-standing gap that previously forced Brazilian and Latin American workloads with data residency constraints toward Fargate by elimination. Today the choice between AgentCore and Fargate is about features, operational model, and team familiarity, not region availability.
What about plain EC2?
Someone always asks. The honest answer is yes, you can absolutely host an MCP server on a plain Amazon Elastic Compute Cloud (EC2) instance. Spin up an Ubuntu box, install Python, run the server with systemd, put an Application Load Balancer in front. It works.
The reason plain EC2 does not earn a place on the four-path list is operational. With EC2 you own the operating system patching cycle, security hardening, Amazon Machine Image (AMI) rotation, instance recovery on failure, multi-Availability-Zone setup, and the auto-scaling configuration. Fargate gives you all of that as a managed wrapper around the same underlying compute, for a small premium that is almost always smaller than the engineering cost of doing it well yourself.
There are workloads where plain EC2 wins. MCP server is not one of them. If your tools are Python or Node.js making JSON-RPC calls over HTTP, there is no kernel parameter to tune, no licensing constraint that requires a non-container runtime, no driver dependency that breaks in an isolated task. Plain EC2 is rational only for teams with deep existing EC2 operational maturity, an established patching and AMI pipeline, and no container expertise to lean on. For everyone else, Fargate is the lower-friction path to the same outcome.
A note on Amazon ECS Express Mode: launched at re:Invent 2025, Express Mode is a simplified entry point into the same ECS Fargate primitive used by Path 3. A single API call provisions the ECS service, an Application Load Balancer, auto-scaling, and networking with sensible defaults. There is no additional charge beyond the underlying resources. If your MCP server is a single container with no advanced networking needs, Express Mode is the most pragmatic on-ramp to the Fargate path described above. AWS App Runner used to occupy this niche but moved to maintenance mode and stopped accepting new customers in April 2026; ECS Express Mode is the official recommended successor.
What about Amazon EKS?
Someone with a Kubernetes-shop background always asks. Yes, you can host an MCP server on Amazon Elastic Kubernetes Service (EKS), with EC2 node groups, EKS Fargate, or EKS Auto Mode. A standard Deployment plus Service plus Ingress (sticky sessions via the AWS Load Balancer Controller or NGINX) covers the same stateful ground as Path 3.
The honest question is: should you?
For a team that already operates EKS at scale, sometimes yes. Colocation makes sense when the MCP server lives alongside other services on the same cluster, shares the observability stack, and follows the same deployment conventions. The marginal cost of one more Deployment is small when the cluster is already running.
For a team without that existing investment, the answer is almost always no. You are using Kubernetes (control plane, kubectl, manifests, Helm, possibly a service mesh) to run a JSON-RPC HTTP server. ECS on Fargate produces the same outcome with a fraction of the operational surface. The “we use Kubernetes for everything” instinct is not the same as “Kubernetes is the right tool for this.”
EKS belongs on the alternative list, not on the four-path list, for the same reason as plain EC2 and ECS Express Mode. It solves the same problem the four paths solve, but with operational trade-offs that only pay off in narrow cases.
What the docs don’t tell you
Five things bit me or my colleagues during this exercise. None of them are buried in obscure forum threads. They are in the documentation if you read every page in the right order, but no single page brings them together.
The Function URL with public access requires two Permission resources, not one
This is the one that cost me two hours.
This is the one that cost me two hours.
When you create a Lambda Function URL with AuthType=NONE, AWS requires not one but two resource-based policies on the Lambda function:
lambda:InvokeFunctionUrl, which authorizes the endpoint to accept anonymous requests
lambda:InvokeFunction, which authorizes the Lambda to execute after the endpoint authorizes the request
If you create only the first, every request returns 403 Forbidden. The endpoint authorizes the call, but the Lambda refuses to run. The Lambda console literally tells you this, but only after you deploy and navigate to Configuration, then Function URL. The CloudFormation deployment succeeds without warning.
lambda:InvokeFunction, which authorizes the Lambda to execute after the endpoint authorizes the request
If you create only the first, every request returns 403 Forbidden. The endpoint authorizes the call, but the Lambda refuses to run. The Lambda console literally tells you this, but only after you deploy and navigate to Configuration, then Function URL. The CloudFormation deployment succeeds without warning.
In SAM templates this means two AWS::Lambda::Permission resources on the same function, which is unusual enough that I had to dig through the AWS Lambda Developer Guide before I trusted it.
yaml
McpServerFunctionUrlPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunctionUrl
FunctionName: !Ref McpServerFunction
Principal: ''
FunctionUrlAuthType: NONE
McpServerInvokePermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref McpServerFunction
Principal: ''
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunctionUrl
FunctionName: !Ref McpServerFunction
Principal: ''
FunctionUrlAuthType: NONE
McpServerInvokePermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref McpServerFunction
Principal: ''
Both. Always both, when AuthType=NONE.
AgentCore container deployment is ARM64-only and the failure mode is silent
If you opt into container deployment on AgentCore (instead of the default
direct_code_deploy), and you build your image on an x86 development machine without specifying the target platform, Docker happily produces an x86 image. AgentCore happily accepts the container Uniform Resource Identifier (URI). The runtime starts. The first invocation reaches a native Python dependency, the import fails, and the agent returns an opaque internal error.The fix is to always build with
--platform linux/arm64 and verify the image manifest before pushing to Elastic Container Registry (ECR). On Apple Silicon laptops this is the default. On x86 workstations it is not. If you do not need a custom container, stay on direct_code_deploy and skip this entire failure mode.Fargate sticky sessions hold until the task dies
ALB sticky sessions on Fargate use cookie-based stickiness to tie a client to a specific task for the configured duration. That works perfectly for MCP servers that hold session state in memory. Until the task dies. When ECS replaces a task (deployment, scaling event, instance failure), the cookie still routes to the old task identifier, the ALB falls back to a different task, and the client’s session is gone.
For real production stateful workloads, plan for this. Either externalize session state to ElastiCache or DynamoDB and treat tasks as fungible, or accept that mid-session task replacement is a recoverable error from the client’s perspective. The middle ground (in-memory state with sticky sessions and no failover) only works for non-critical sessions.
AgentCore arrived in sa-east-1 in May 2026, closing a long-standing gap
Until early May 2026, AgentCore Runtime was unavailable in São Paulo, which forced Brazilian regulated workloads with data residency constraints toward Fargate by elimination. That changed: AgentCore is now available in sa-east-1 with full feature parity at launch, including Runtime, Identity, Gateway, Policy, Observability, Code Interpreter, and Browser tools.
If you made an internal architecture decision in 2025 ruling out AgentCore for Brazilian workloads, revisit it. The constraint is gone. This is not historical trivia, it is an active update that should change ongoing roadmap discussions.
AuthType=NONE is not production-ready, regardless of how the tutorials read
The official MCP specification mandates OAuth 2.1 for publicly exposed MCP servers, with proper security measures for both confidential and public clients. Lambda Function URL does not support OAuth natively. The two authentication options are NONE (no auth) or AWS_IAM (SigV4, useful only for callers inside an AWS trust boundary).
The implication is straightforward. If your MCP server is consumed by an arbitrary external client (a teammate’s agent in a different AWS account, a partner’s application, a customer’s tool), Lambda Function URL is not the right hosting primitive. You either move to Lambda plus API Gateway with a custom OAuth authorizer, or you move to Fargate with ALB plus Cognito, or you move to AgentCore.
This is the part of the decision that most prototypes get wrong. The Function URL was great in dev, the demo went well, the team kept it for production, and now there is an MCP endpoint exposed to the internet with no real authentication on it. I have seen this in two different organizations in the last six months. It is a security incident waiting for a determined attacker.
A decision framework that holds up
Four honest questions, in order. If the answer to any of them tips the decision, stop reading and go build.
Question 1: Is the MCP server stateless and consumed exclusively by callers inside a single AWS trust boundary you control?
If yes, Lambda Function URL with
AuthType=AWS_IAM is the right answer for almost every workload. Cheap, fast to ship, governance-light, and the SigV4 requirement makes it secure enough for internal agents. The caller assumes a role, the role has lambda:InvokeFunctionUrl on the target function, and you are done.If no, move to question 2.
Question 2: Is the MCP server stateless but consumed by external clients with OAuth tokens, or in need of per-consumer rate limits, WAF, or a custom domain?
If yes, Lambda plus API Gateway is the right answer. The dollar-per-million-requests cost is negligible compared to the time you would spend rebuilding API Gateway features in a Function URL with custom code. Use a Lambda Authorizer for OAuth 2.1 token validation if you need to issue or accept JSON Web Tokens (JWTs).
If no (because you need stateful sessions), move to question 3.
Question 3: Does your workload need stateful sessions, AND do you want turnkey integration with Strands runtime plus microVM-level isolation?
If yes to both, AgentCore Runtime is the right answer. The pricing math works for stateful agentic workloads at any meaningful volume, and the operational simplification (no custom OAuth, no custom tenancy logic, no custom session state) usually pays for itself within the first quarter. As of May 2026, regional coverage is no longer a hard constraint for most workloads (15+ regions including sa-east-1).
If no on either (you do not need that level of isolation, or you want container-level control), move to question 4.
Question 4: You need stateful sessions and want container-level control, the freedom to use either x86_64 or ARM64, or a non-Python runtime.
ECS on Fargate is the right answer. The trade-off is paying for idle capacity in exchange for predictable cost and full control over the runtime environment. For workloads with steady traffic this is the most economical stateful path. For workloads with non-Python runtimes (Rust, Go, Node.js), this is also the most natural fit since AgentCore’s
direct_code_deploy is Python-first.When the answer is “I don’t know yet”
Sometimes the workload is exploratory, the requirements are unclear, and you genuinely do not know which path will win in six months. In that case, build on the simplest path that fits your current state model, and write the code so the transport layer is replaceable.
Concretely, that means keeping the MCP method handlers (
initialize, tools/list, tools/call) in pure Python functions that take JSON-RPC requests and return JSON-RPC responses. The transport adapter is thin. If you started on Lambda Function URL and later need OAuth, you wrap the same handlers behind API Gateway. If you started stateless and grew into stateful, you containerize the same logic and run it on Fargate or AgentCore. The code that took you the longest to write (the actual tool implementations and the MCP protocol logic) does not change.This is the same pattern I use for any AWS primitive where the path forward is uncertain. Keep the business logic separable from the transport. Pay the small cost of indirection now, save the large cost of rewriting later.
What to do today, depending on where you are
If you are starting fresh, your workload is internal, and stateless is fine: Lambda Function URL with AWS_IAM. One hour to working endpoint. Two hours if you forget the second Permission.
If you are starting fresh, your workload will be externally consumed, and stateless is fine: Lambda plus API Gateway with a Lambda Authorizer. Half a day to working endpoint. Most of that time will be on the OAuth flow, not on AWS itself.
If you are starting fresh and you need stateful sessions in a region AgentCore covers: AgentCore Runtime. Plan a week to ship the first version, including the ARM64 container build pipeline. The slope after that is much gentler than the equivalent on the other paths.
If you are starting fresh and you need stateful sessions with non-Python runtimes, x86_64 preference, or container-level control over the runtime: ECS on Fargate. Plan three to five days for the first working deployment, including the ALB, the task definition, the security groups, and the session-state decision (in-memory plus stickiness, or externalized).
If you have an existing MCP server on Lambda Function URL with
AuthType=NONE exposed to external clients, that is the work item for this week. Either restrict it to AWS_IAM and confirm the callers can sign, or migrate it behind one of the other three options with a real authorizer in front. Do not let it run another quarter as it is.Takeaway
Four paths, four honest answers, one decision per workload.
Lambda Function URL is the right answer for stateless MCP servers consumed inside an AWS trust boundary. With AWS_IAM authentication and SigV4 from internal callers, it is production-grade for that pattern, not a laboratory toy.
Lambda plus API Gateway is the bridge for stateless servers exposed externally. The right choice when OAuth, rate limits, custom domains, or WAF are part of the requirement set.
ECS on Fargate is the stateful workhorse without vendor lock-in. The right choice when stateful sessions matter, when AgentCore is unavailable in your region, when ARM64-only is a constraint you cannot accept, or when team familiarity tips toward containers.
AgentCore Runtime is the stateful path for teams committed to the AWS agentic stack. The right choice when integration with Strands runtime, microVM-level isolation, and managed governance are in the requirements list. As of May 2026, regional coverage spans 15+ AWS regions including sa-east-1, so the previous “wrong region” exclusion no longer applies.
There is no universal best answer. There is a best answer per situation, and the cost of getting it wrong is rework you can avoid by being honest about what your workload actually needs before you wire the first line of infrastructure code.
Code from this article is published as part of the aws-agentic-stack-starter companion repository, including a working SAM template for the Lambda Function URL path with both Permission resources, a Strands client that consumes it, and reference Terraform for the AgentCore Runtime path. The Fargate reference is on the roadmap for the next iteration.
Previously in Golden Jacket Field Notes:
- Week 1: Bedrock Knowledge Bases looked perfect in my demo. Production had other plans.
- Week 2: The AWS Agentic Stack Explained: Strands, AgentCore, MCP, and A2A.
- Week 3: Architecting Payment Authorization on AWS: Credit Cards vs. Pix at Scale.
- Week 4: I Connected Five MCP Servers to My IDE. My AI Agent Got Dumber.
- Week 5: AWS Frontier Agents Dissected: Security Agent and DevOps Agent Under the Hood.
- Week 6: S3 Files: What 20 Years of “You Can’t Mount S3” Looks Like When It Finally Ships.
- Week 7: Deterministic-First: GenAI Governance That Does Not Hallucinate.
- Week 8: The AWS Generative AI Professional Exam: What the Blueprint Doesn’t Reveal.
Erick Mancz is a Global AWS Solutions Architect Specialist at Compass UOL, Golden Jacket holder, and co-founder of Golden Jackets Brazil . He writes weekly Field Notes on production GenAI and cloud architecture.
Series: Golden Jacket Field Notes (22 articles)
- …
- 8Hosting an MCP Server on AWS in 2026 This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article