s3-mcp-server
A remote, OAuth2-authenticated MCP server that provides read-only S3 tools (list buckets, list objects, check public access, get bucket size) designed to run on ECS Fargate with Auth0 as the identity provider.
README
S3 MCP server on ECS Fargate
A remote, OAuth2-authenticated MCP server exposing read-only S3 tools
(list_buckets, list_objects, get_bucket_public_access,
get_bucket_size), designed to run as an ECS Fargate task behind an
ALB and CloudFront, with Auth0 as the identity provider.
How it fits together
- CloudFront terminates TLS at the edge and forwards every header
(including
Authorization) to an internal ALB over plain HTTP. - ALB hands the request to the ECS Fargate task on port 8080.
- The task (
server.py) validates the bearer token against Auth0 before running any tool, then talks to S3 using the task's IAM role — no static AWS credentials anywhere. - The task reaches both Auth0's JWKS endpoint and S3's public API through a NAT Gateway, since it runs in private subnets and no S3 VPC endpoint is configured.
The OAuth handshake (login, PKCE code exchange) happens entirely between the MCP client (e.g. Claude Desktop) and Auth0 — the server is only involved at the very start (serving its own discovery document) and the very end (validating the resulting token). Keep that in mind when debugging: a failed login never shows up in this server's logs, because the server was never part of that exchange.
Auth0 setup
You need an Auth0 tenant (the free tier is enough) with two things in it:
-
An API — this is what makes Auth0 issue real JWT access tokens instead of opaque ones.
- Dashboard → Applications → APIs → Create API
- Identifier: the exact URL clients will connect to, e.g.
https://your-domain.example.com/sse— this becomesAUTH0_AUDIENCEand must match byte-for-byte what you deploy behind. - Signing algorithm: RS256 (default)
-
An Application — what the MCP client authenticates as.
- Dashboard → Applications → Applications → Create Application
- Type: Regular Web Application
- In Settings, set Allowed Callback URLs to whatever redirect
URI(s) your MCP client uses (for Claude Desktop:
https://claude.ai/api/mcp/auth_callback) - Note the Domain, Client ID, and Client Secret — these
become
AUTH0_DOMAIN/AUTH0_CLIENT_ID/ the client secret you give your MCP client (the server itself never needs the secret).
-
Authorize the Application for the API — this step is easy to miss. Creating an API and an Application separately does not link them. Go to the API → Application Access tab → toggle your Application on. Skip this and every authorization attempt fails with
invalid_request/ "Client is not authorized to access resource server", before the client ever sees a login page.
Local test (no AWS needed for the transport layer)
cp .env.example .env
# edit .env with your Auth0 tenant details if you want to test auth locally
pip install -r requirements.txt
python server.py
Server listens on http://localhost:8080/sse. /health returns 200 ok
with no auth required — that's the ALB target group health check path.
Every other route requires Authorization: Bearer <token> where the
token is an Auth0 access token (JWT, aud matching AUTH0_AUDIENCE)
for an app client matching AUTH0_CLIENT_ID.
Docker
docker build -t s3-mcp-server .
docker run -p 8080:8080 --env-file .env s3-mcp-server
Deploying to Fargate
cd infra
pip install -r requirements.txt # into a venv
cdk bootstrap # first time only, per account/region
cdk deploy \
-c auth0_domain=your-tenant.us.auth0.com \
-c auth0_client_id=your-application-client-id \
-c auth0_audience=https://your-domain.example.com/sse
auth0_audience should match the CloudFront domain CDK is about to
create, with /sse appended — you likely won't know it on the very
first deploy. Deploy once to get the DistributionURL output, then
redeploy with the real audience value (this only needs to happen
once; the CloudFront domain is stable across subsequent deploys of
the same stack).
Instead of passing -c flags every time, you can put them in a
gitignored infra/cdk.context.json:
{
"auth0_domain": "your-tenant.us.auth0.com",
"auth0_client_id": "your-application-client-id",
"auth0_audience": "https://your-domain.example.com/sse"
}
Things that bite people
-
ALB idle timeout. SSE connections are long-lived. The ALB's default idle timeout (60s) will kill them. This stack sets it to
300(5 min) — raise it further if your client doesn't send periodic pings. -
Health check path. Target group health check points at
/health, not/sse—/sserequires auth and is a streaming response, neither of which the ALB health checker expects. -
Credentials. The container never sets AWS credentials — boto3 picks them up automatically from the Fargate task's IAM role via the container credentials endpoint. Don't bake keys into the image or env vars.
-
Egress. The task fetches Auth0's JWKS over HTTPS on first request (then caches for an hour), and calls S3's public API for every tool call — both go out through the NAT Gateway, since this stack doesn't configure an S3 VPC Gateway Endpoint. Make sure the task's subnet actually has a NAT route, or add a Gateway Endpoint for S3 (free, and keeps that traffic off the public internet).
-
The MCP SDK's own DNS-rebinding protection will silently break this behind any real domain.
FastMCP'sTransportSecuritySettingsdefaults to an empty allowed-hosts list, which rejects every request that arrives with a realHostheader (like your CloudFront domain) — even after auth succeeds — with421 Misdirected Request. This happens after the OAuth flow completes, so it's easy to mistake for an auth bug. This server disables it inserver.py, sinceAuth0AuthMiddlewarealready gates every route with bearer-token auth:mcp = FastMCP( "s3-mcp-server", transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False), )
Minimal IAM task role policy (read-only tools above)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3ReadOnly",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:GetBucketAcl",
"s3:GetBucketPolicyStatus",
"s3:GetBucketPolicy"
],
"Resource": "*"
}
]
}
Scope Resource down to specific bucket ARNs once you know which
buckets this agent should actually be allowed to see. If you later add
a write tool (e.g. lifecycle policy changes), give it its own narrower
statement rather than widening this one.
MCP client config
Point any MCP client that supports remote SSE servers at your deployed URL, with the Auth0 Application's Client ID and Secret from the setup steps above:
{
"mcpServers": {
"s3": {
"url": "https://your-domain.example.com/sse",
"oauth_client_id": "your-application-client-id",
"oauth_client_secret": "your-application-client-secret"
}
}
}
The exact config shape depends on the client — Claude Desktop exposes these as form fields under its Connectors settings rather than raw JSON. Either way, the OAuth redirect/token exchange is handled by the client per the MCP authorization spec; this server only validates the resulting bearer token on each request.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。