---
title: "AWS IAM (SigV4) Authentication for MCP Servers"
description: "Sign MCP requests with AWS SigV4 so agents can reach a server behind a Lambda function URL, API Gateway, or AgentCore — and why no MCP client can sign."
canonicalUrl: "https://zuplo.com/learn/mcp/authentication/aws-iam-sigv4"
pageType: "mcp-auth-guide"
method: "cloud-iam"
platform: "aws"
lastVerified: "2026-07-29"
specVersion: "2026-07-28"
---

# Authenticate an MCP Server with AWS IAM (SigV4)

> When your agent runs in AWS, it can authenticate to an MCP server by signing each request with the credentials of the role it already has, so there is no token to issue and no secret to store.

**AWS IAM (SigV4) · AWS**

## The exchange

The agent signs each request with SigV4 using its IAM role credentials and sends it to the AWS service in front of the MCP server. That service recomputes the signature, evaluates IAM, and forwards the request with the caller's ARN in the request context. No token is issued and no identity provider is involved.

**Participants:** Your agent (task or Lambda role), AWS (function URL or AgentCore), MCP server (reads the caller ARN)

1. **Your agent** (internal step) — Sign the canonical request — `SHA256 of the JSON-RPC body`
2. **Your agent → AWS** — `POST /mcp` — `Authorization: AWS4-HMAC-SHA256 …` _(carries the credential)_
3. **AWS** (internal step) — Recompute the signature, check IAM — `lambda:InvokeFunctionUrl + InvokeFunction`
4. **AWS → MCP server** — `POST /mcp` — `requestContext.authorizer.iam.userArn`
5. **MCP server → AWS** — `200 OK` — `tool result` _(response)_
6. **AWS → Your agent** — `200 OK` — `tool result` _(response)_

Notes:

- **Steps 1–2** — No token is fetched. The signature is derived from role credentials already in the environment, so there is nothing to store and nothing to rotate, and the body hash means every message is signed separately.
- **Steps 3–4** — AWS verifies the signature and the IAM policy before your code runs. The caller arrives as an ARN in the request context rather than as a claim your handler has to validate.

## How it works

The agent signs every HTTP request with SigV4 and sends `Authorization: AWS4-HMAC-SHA256 Credential=…, SignedHeaders=…, Signature=…`. The AWS service in front of the MCP server — a Lambda function URL with `AWS_IAM` auth, API Gateway, or Bedrock AgentCore — recomputes the signature and evaluates IAM before your code runs. There is no token to mint, cache or rotate: the signature covers a SHA-256 of the request body, so every message is signed on its own. Behind a function URL the caller reaches your handler as an IAM ARN on the event.

_MCP Specification 2026-07-28._

### At a glance

- **Best for** — Agents you write that run in AWS, calling a server behind Lambda, API Gateway, or AgentCore
- **MCP spec** — Platform request signing rather than MCP authorization; the spec does not cover it
- **Works with** — Your own agents. No MCP client signs SigV4; AWS ships a local proxy that does
- **Effort** — An afternoon with AWS's transport, a day with the IAM policies

## Connect your agent

Each sample connects to the server, sends the credential, and exposes the tools to the model.

### Python

#### MCP Python SDK

```python title="agent.py"
from mcp import ClientSession
from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client

# pip install mcp-proxy-for-aws. aws_service is the SigV4 signing
# name of whatever fronts the server — "lambda" for a function URL,
# "bedrock-agentcore" for AgentCore — never "mcp".
async with aws_iam_streamablehttp_client(
    endpoint="https://url-id-12345.lambda-url.us-west-2.on.aws",
    aws_service="lambda",
    aws_region="us-west-2",
) as (read_stream, write_stream, _):
    async with ClientSession(read_stream, write_stream) as session:
        await session.initialize()
        tool_result = await session.call_tool("echo", {"message": "hello"})
```

#### Strands Agents

```python title="agent.py"
from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client
from strands import Agent
from strands.tools.mcp import MCPClient

# For AgentCore Runtime the endpoint is the invocations URL built from
# the URL-encoded runtime ARN, and the signing name is
# "bedrock-agentcore" rather than the host's first label.
mcp_client = MCPClient(lambda: aws_iam_streamablehttp_client(
    endpoint="https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/ENCODED_ARN/invocations",
    aws_region="us-east-1",
    aws_service="bedrock-agentcore"))

# The tools only work inside the context manager.
with mcp_client:
    agent = Agent(tools=mcp_client.list_tools_sync())
```

#### Lambda Invoke API

```python title="agent.py"
from mcp import ClientSession
from mcp_lambda import LambdaFunctionParameters, lambda_function_client

# No HTTP endpoint at all: this signs a Lambda Invoke API call, so
# there is no function URL to expose and no front door to configure.
server_params = LambdaFunctionParameters(
    function_name="my-mcp-server-function",
    region_name="us-west-2",
)

async with lambda_function_client(server_params) as (read_stream, write_stream):
    async with ClientSession(read_stream, write_stream) as session:
        await session.initialize()
        tool_result = await session.call_tool("echo", {"message": "hello"})
```

### TypeScript

#### MCP TypeScript SDK

```typescript title="agent.ts"
import { StreamableHTTPClientWithSigV4Transport } from "@aws/run-mcp-servers-with-aws-lambda";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

// First argument must be a URL object, not a string. `service` is the
// SigV4 signing name of the endpoint, and `region` has to match the
// region in the credential scope AWS will recompute.
const transport = new StreamableHTTPClientWithSigV4Transport(
  new URL("https://url-id-12345.lambda-url.us-west-2.on.aws"),
  { service: "lambda", region: "us-west-2" },
);

const client = new Client({ name: "my-client", version: "0.0.1" });
await client.connect(transport);
const { tools } = await client.listTools();
```

## When to use something else

**Use this when:**

- The agent has an IAM role and the MCP server sits behind something that verifies SigV4 — a Lambda function URL, API Gateway, or AgentCore.
- You want no stored secret: the signature is computed per request from credentials already in the environment.
- Tool calls have to resolve to an IAM principal, which a function URL hands your code as an ARN on the event.

**Use something else when:**

- The server is behind an Application Load Balancer or a raw EC2 endpoint. AWS names both as incompatible with IAM signing, because neither natively verifies SigV4 signatures.
- A person's chat client has to connect. No MCP client signs requests, so the answer is a local signing proxy or an OAuth front door.
- You need SigV4 and OAuth on one AgentCore Runtime: a runtime accepts either IAM SigV4 or JWT bearer inbound auth, not both at once.

> This is platform request signing, not MCP authorization, and the specification does not describe it. Authorization is OPTIONAL there — "Implementations using an HTTP-based transport SHOULD conform to this specification" — so a signed request sits outside the OAuth profile rather than in conflict with it. That sentence is word-for-word identical in the 2025-11-25 revision and in 2026-07-28, the current protocol version. The cost is reach: it is the strongest option inside your own account and unavailable to everything else.

## What lands on you next

SigV4 answers one question exactly: which IAM principal signed this request. The questions that follow are about callers with no IAM principal, and about what a principal may do once it is through the door.

- **Security wants to know which human was behind that role's tool call.** — `capability_invocation` — Each tool call emits an event carrying the authenticated subject, the capability, the outcome and the latency. An IAM role is one subject for every agent that assumes it, so the subject has to come from a user identity to be a person.
- **A partner's agent needs three of these tools. They have no AWS account.** — `mcp-cognito-oauth-inbound` — The inbound method is a policy, not server code, so a route can authenticate against Amazon Cognito, ten other named providers, or any OIDC provider. Exactly one such policy per project.
- **Each API behind this server needs its own credential. Do the agents hold all of them?** — `mcp-token-exchange-inbound` — No. The spec forbids passthrough with a MUST NOT. The inbound credential is stripped and an independent upstream credential is used per user or per gateway.
- **One agent's retry loop ate our Lambda concurrency for the afternoon.** — `rate-limit-inbound` — With `rateLimitBy: "user"` every tool call is counted against the authenticated subject rather than a shared egress IP, before the request reaches the function.

All of these attach to one MCP route's `policies.inbound` — the same policy engine on the way in and on the way out. See [Zuplo MCP Gateway](/mcp-gateway).

## FAQ

**Can Claude, ChatGPT or Cursor connect to an MCP server that requires SigV4?**

Not directly. AWS says off-the-shelf MCP-compatible applications are unlikely to support its SigV4 transport, and that standard MCP clients do not know how to sign requests with AWS credentials. Its answer is mcp-proxy-for-aws: a local proxy, run as a command with uvx, that signs from your own AWS credentials for a client like Claude Desktop. It only helps a client that can launch a local command, so it does nothing for a hosted connector.

**SigV4 vs OAuth for MCP — which should I use?**

SigV4 when the caller is your own workload inside AWS, OAuth when the caller is a person or lives outside your account. AgentCore Runtime makes the choice explicit: a runtime accepts either IAM SigV4 or JWT bearer inbound auth, and not both simultaneously.

**Does Bedrock AgentCore Gateway support SigV4 to any MCP server?**

No, and this is the detail most write-ups miss. IAM outbound authorization requires that the target verify SigV4 itself. AWS lists AgentCore Gateway, AgentCore Runtime, API Gateway and Lambda function URLs as compatible, and states that services which do not natively verify signatures — Application Load Balancer or direct EC2 endpoints — are not. Use OAuth or an API key there.

**Why do I get SignatureDoesNotMatch?**

AWS recomputes the whole canonical request and compares. The signature covers the host header, every header you listed in SignedHeaders, and a SHA-256 of the body, and the credential scope pins the date, region and service. So the usual causes are the wrong signing service or region, a proxy that rewrites the body or a signed header, or temporary credentials sent without X-Amz-Security-Token. AWS advises against signing hop-by-hop headers for that reason.

**What service name do I sign with?**

The SigV4 signing name of whatever fronts the server, not the string "mcp". It is lambda for a Lambda function URL and bedrock-agentcore for AgentCore Runtime. The service name is one of the inputs to the signing key and part of the credential scope, so getting it wrong fails the signature comparison rather than the authorization check.

**Is a Lambda function URL with AWS_IAM auth private?**

It is authenticated, which is not the same thing. Lambda still generates a dedicated HTTPS endpoint at https://<url-id>.lambda-url.<region>.on.aws that any HTTP client can reach, and the only thing standing in front of it is IAM: since October 2025, invoking a new function URL requires both lambda:InvokeFunctionUrl and lambda:InvokeFunction. Treat the auth type as authentication, not as network isolation.

**Can the MCP server tell who called it?**

Yes. With the AWS_IAM auth type, Lambda populates requestContext.authorizer.iam on the event with userArn, callerId, principalOrgId and the caller's access key, so your handler reads the caller instead of validating a token.

**Is there a token to cache or refresh?**

There is no token. Because the body hash is part of the canonical request, each JSON-RPC message is signed on its own, and the signer reads whatever credentials the environment currently holds. That is the opposite trade from Google ID tokens, where caching the client rather than the token string is what keeps a long-lived process working.

## Keep reading

- **Google Cloud IAM** — [Authenticate an MCP Server with Google Cloud IAM](/learn/mcp/authentication/google-iam) — Agents that hold Google credentials, calling a server that can verify a Google token
- **API key** — [API Key Authentication for MCP Servers](/learn/mcp/authentication/api-key) — Service-to-service calls where you control both the agent and the MCP server
- **Microsoft Entra ID** — [Authenticate an MCP Server with Microsoft Entra ID (Azure AD) Managed Identity](/learn/mcp/authentication/azure-managed-identity) — Agents and MCP servers that both run in Azure, in one Entra tenant
- **MCP Authentication** — [every authentication method](/learn/mcp/authentication)
