I'm connecting an MCP client (Strands Agents TypeScript SDK, which wraps
`@modelcontextprotocol/sdk`) to an **Amazon Bedrock AgentCore Gateway**
configured with `authorizerType: AWS_IAM` and `protocol: MCP` (StreamableHTTP).
## What works in Python
AWS ships a first-party helper, [`mcp-proxy-for-aws`](https://pypi.org/project/mcp-proxy-for-aws/),
whose `aws_iam_streamablehttp_client` is a drop-in replacement for the MCP SDK's
`streamablehttp_client` and SigV4-signs every request:
from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client
from strands.tools.mcp.mcp_client import MCPClient
MCPClient(lambda: aws_iam_streamablehttp_client(
endpoint=GATEWAY_URL, aws_region="us-east-1", aws_service="bedrock-agentcore",
))
## The problem in TypeScript / Node
I can't find something similar. Connecting with just a URL (the SDK builds a
`StreamableHTTPClientTransport` automatically) sends **unsigned** requests, and
the gateway rejects them:
import { McpClient } from "@strands-agents/sdk";
const client = new McpClient({ url: "https://
await client.connect(); // and listTools()
{"jsonrpc":"2.0","id":0,"error":{"code":-32001,"message":"Authentication error - Invalid credentials"}}
Same result whether the client runs on my laptop or inside the AgentCore runtime
itself — so "running in AWS" doesn't sign the request for me. The MCP
`StreamableHTTPClientTransport` is plaess, and the
SDK's only auth options are OAuth (`auth` / `authProvider` / `headers`), not IAM.
## What I ended up doing
Injecting a SigV4-signing `fetch` into the transport, using the AWS SDK signer:
import { SignatureV4 } from "@smithy/s
import { HttpRequest } from "@smithy/protocol-http";
import { Sha256 } from "@aws-crypto/sh
import { defaultProvider } from "@aws-sdk/credential-provider-node";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const signer = new SignatureV4({
service: "bedrock-agentcore",
region: "us-east-1",
credentials: defaultProvider(),
sha256: Sha256,
});
const signedFetch: typeof fetch = async (input, init) => {
const url = new URL(input.toString()
const method = (init?.method ?? "GET").toUpperCase();
const body = method === "GET" || method === "HEAD" ? undefined : (init?.body as string | undefined);
const headers: Record
new Headers(init?.headers).forEach((v, k) => (headers[k] = v));
const signed = await signer.sign(new HttpRequest({
method, protocol: url.protocol, horl.pathname,
query: Object.fromEntries(url.searchParams), headers,
...(body !== undefined && { body }),
}));
return fetch(url, { ...init, method, headers: signed.headers });
};
const transport = new StreamableHTTPCl_URL), { fetch: signedFetch });
This works — the gateway authenticates and `tools/list` returns the tools.
## Question
Is hand-rolling a SigV4 `fetch` the only way to do this, or is there a first-party / maintained TypeScript equivalent? like a TS option of `mcp-proxy-for-aws` (or a built-in option on the MCP / Strands SDK) that I've missed?