{{ message }}
Repository navigation
fix(aws-api-mcp-server): pass allowed hosts and origins to the FastMCP HTTP guard - #4523
Merged
Merged
Conversation
Om-singhaI
requested review from
a team,
PCManticore,
arnewouters,
bidesh,
iddv and
rshevchuk-git
as code owners
August 22, 2026 07:02
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes FastMCP host validation for AWS API MCP Server HTTP deployments.
Changes:
- Forwards configured host and origin allowlists to FastMCP.
- Raises the FastMCP minimum version to 3.4.3.
- Adds coverage and changelog documentation.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
Suppressed comments (1)
src/aws-api-mcp-server/tests/test_server.py:952
- This integration test never sends an
Originheader, andALLOWED_ORIGINS='*'is therefore not exercised at runtime. A regression that drops or miswires the newallowed_originsargument would still pass; add a request with a non-same-origin value so the FastMCP origin guard is actually verified.
for host_header in ('localhost', 'my-mcp-server.example.com'):
response = client.post(
'/mcp',
json=initialize_request,
headers={
'Host': host_header,
'Accept': 'application/json, text/event-stream',
},
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4523 +/- ##
=======================================
Coverage 93.21% 93.21%
=======================================
Files 1044 1044
Lines 88387 88387
Branches 14274 14274
=======================================
Hits 82392 82392
Misses 3623 3623
Partials 2372 2372 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
|
LGTM, I have made some changes to |
…P HTTP guard FastMCP 3.x wraps the streamable HTTP app in its own HostOriginGuardMiddleware. That guard only accepts loopback Host headers, the explicit allowed_hosts list, and the bound address when that address is not unspecified. main() bound the server with host=HOST but never passed allowed_hosts or allowed_origins to server.run(), so a container listening on 0.0.0.0 behind a load balancer or ingress answered every real request with 421 Misdirected Request while only requests carrying Host: localhost got through. AWS_API_MCP_ALLOWED_HOSTS and AWS_API_MCP_ALLOWED_ORIGINS only fed the project's own header validation middleware, which runs after the FastMCP guard and therefore never saw those requests. The regression appeared when the lock file moved from fastmcp 3.0.1 to 3.4.3, which matches the 1.3.46 to 1.4.0 bisection in the report. Hand the already parsed allowed host and origin lists to server.run() for the HTTP transport. run() forwards them to run_http_async(), which installs them in the guard, so a wildcard or an explicit host list now works and the two validation layers agree. The stdio path is untouched because run_stdio_async() does not accept these keyword arguments. Those keyword arguments first appeared in fastmcp 3.4.3, the release that introduced the guard. The package accepted any fastmcp from 3.2.0, and on 3.2.0 through 3.4.2 the new call would have failed at startup with a TypeError even though those versions never had the 421 problem. Raise the fastmcp floor to 3.4.3 in pyproject.toml and regenerate uv.lock with uv lock. The resolved version was already 3.4.3, so only the requires dist specifier line in the lock changes and nothing installed moves. Extend the existing run kwargs test and add a regression test that builds the HTTP app with exactly the keyword arguments main() passes and sends an initialize request with a real DNS name in the Host header through the FastMCP guard. Fixes awslabs#4507
…MCP guard The guard test only varied the Host header, so a change that dropped or miswired the allowed_origins argument would still have passed. Every request in the test now carries an Origin from another site, which the guard rejects with 403 unless allowed_origins reaches FastMCP. Raised in review.
Om-singhaI
force-pushed
the
fix/api-mcp-allowed-hosts
branch
from
August 27, 2026 17:30
f35c7d5 to
6f40e39
Compare
Contributor
Author
arnewouters
approved these changes
Aug 28, 2026
anasstahr
approved these changes
Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fixes #4507
Summary
Changes
aws-api-mcp-serverbuilds itsFastMCPinstance without any host information and only passeshostandporttoserver.run(). FastMCP 3.4.3 (locked since release 1.4.0) wraps thestreamable-httpapp in its ownHostOriginGuardMiddleware, added in PrefectHQ/fastmcp#4405. That guard accepts loopbackHostheaders, anything in itsallowed_hostslist, and the bound address when that address is not the unspecified one. Nothing in this server populatedallowed_hosts, so a container bound to0.0.0.0behind an ALB or ingress answered every real request with421 Misdirected Request, and onlyHost: localhostgot through.AWS_API_MCP_ALLOWED_HOSTSandAWS_API_MCP_ALLOWED_ORIGINShad no effect on this because they only feed the project's ownHTTPHeaderValidationMiddleware, which runs after the FastMCP guard.The fix is deliberately small, in keeping with the end of development notice on this server:
server.pynow importsALLOWED_HOSTSandALLOWED_ORIGINSfromconfig.pyand passesallowed_hosts=ALLOWED_HOSTS.split(',')andallowed_origins=ALLOWED_ORIGINS.split(',')toserver.run()on the HTTP branch.FastMCP.run()forwards extra keyword arguments torun_http_async(), whose signature in fastmcp 3.4.3 accepts exactlyallowed_hosts: list[str] | Noneandallowed_origins: list[str] | Noneand installs them in the guard. The guard treats*as a wildcard, matching the semantics this project already documents for these variables. The stdio branch is untouched becauserun_stdio_async()does not accept these arguments.pyproject.tomlanduv.lock: thefastmcpfloor moves from>=3.2.0to>=3.4.3. The two keyword arguments do not exist before 3.4.3 (I checked thev3.2.0andv3.4.2tags of PrefectHQ/fastmcp: neitherserver/http.pynorserver/mixins/transport.pymentionsallowed_hostsorHostOriginGuard; both appear inv3.4.3). Without the bump, an install that resolved any permitted fastmcp between 3.2.0 and 3.4.2 would passallowed_hoststo arun_http_async()that does not accept it and crash at startup withTypeError: run_http_async() got an unexpected keyword argument 'allowed_hosts', even though those versions never had the 421 problem. Raising the floor keepsserver.pyand the dependency contract in step, in the same spirit as fix(deps): raise fastmcp floor to >=3.2.0 to exclude a CRITICAL advisory #4454. The lock was regenerated withuv lock(uv 0.12.3, the version that wrote the current lock) and the only change is therequires-distspecifier line; the resolved version was already 3.4.3, so nothing installed moves.tests/test_server.py:test_main_success_with_http_transportnow asserts the two new keyword arguments, and a new testtest_main_http_transport_allowed_hosts_reach_fastmcp_guardbuilds the real HTTP app with exactly the keyword argumentsmain()hands toserver.run()and sends aninitializerequest through FastMCP's guard withHost: my-mcp-server.example.comwhile the app is bound to0.0.0.0:8080. Without the two keyword arguments that test fails with{'my-mcp-server.example.com': 421} != {'my-mcp-server.example.com': 200}. Thestarlette.testclientimport lives inside that one test so itsStarletteDeprecationWarning(httpx versus httpx2 under starlette 1.3.1) stays scoped to the test that needs it.CHANGELOG.md: entry underUnreleased/Fixed, mentioning the floor bump.A note on the suggestion in the issue: passing
hostto theFastMCP()constructor does not work on fastmcp 3.x; the constructor rejectshostwith a message pointing atrun_http_async()orFASTMCP_HOST. Passingallowed_hostsandallowed_originstorun()is the supported path.Default behaviour is unchanged for local use:
ALLOWED_HOSTSdefaults toAWS_API_MCP_HOST, which defaults to127.0.0.1, a value the guard already allows. WithAWS_API_MCP_HOST=0.0.0.0and no explicit allow list, both layers now agree on allowingHost: 0.0.0.0and loopback names and rejecting everything else, so operators still have to opt in to a real DNS name or*as the README describes. The issue's expected behaviour section asks forAWS_API_MCP_HOST=0.0.0.0alone to accept real DNS names; I left that as an explicit opt in because the project's own middleware already rejects such hosts by default and silently widening the default felt like a separate decision for the maintainers.One difference between the two layers worth knowing about: the README says port numbers are stripped during validation. That is still true of the project's middleware for both headers, and of the FastMCP guard for
Host, but the guard comparesOriginvalues including any explicit port. A browser origin such ashttps://x.example.com:8443therefore needshttps://x.example.com:8443inAWS_API_MCP_ALLOWED_ORIGINSfor the guard andhttps://x.example.comfor the project middleware (or*). Such requests were rejected with 421 before this change, so this is not a regression, and I have not touched the README so the maintainers can decide how they want that documented.User experience
Before (published image 1.5.1,
AWS_API_MCP_TRANSPORT=streamable-http,AWS_API_MCP_HOST=0.0.0.0,AWS_API_MCP_ALLOWED_HOSTS=*):After, same configuration:
Reproduced in process on
mainwithstarlette.testclientagainst the real fastmcp 3.4.3 guard, building the app with the same keyword argumentsmain()uses, and confirmed fixed with the change.Test run in
src/aws-api-mcp-serverwith a venv pinned to the lock file versions (fastmcp 3.4.3, mcp 1.29.0, starlette 1.3.1):tests/test_server.py71 passed, 1 failedThe one failure,
test_call_aws_ecs_deploy_error_sanitization, is present on pristinemainin this environment (pytest reports "async def functions are not natively supported" for that single test) and is unrelated to this change.ruff checkandruff format --checkpass on the changed files.pip checkpasses with fastmcp 3.4.3 installed against the new floor.FASTMCP_HTTP_ALLOWED_HOSTSandFASTMCP_HTTP_ALLOWED_ORIGINSsettings when the keyword arguments areNone. After this change they are always passed, soAWS_API_MCP_ALLOWED_HOSTSandAWS_API_MCP_ALLOWED_ORIGINSbecome the single source of truth for the guard. Anyone who worked around this issue with theFASTMCP_*variables already had to set theAWS_API_MCP_*ones for the project's own header middleware to pass, so in practice nothing that worked before stops working.FASTMCP_HTTP_HOST_ORIGIN_PROTECTION=falsestill disables the guard entirely sincehost_origin_protectionis not passed.Checklist
If your change doesn't seem to apply, please leave them unchecked.
Is this a breaking change? (Y/N) N
The
fastmcpfloor moves to>=3.4.3. Installs from the lock file or viauvxalready resolve 3.4.3, so nothing changes for them; only an environment that pinned fastmcp between 3.2.0 and 3.4.2 would need to upgrade.RFC issue number: N/A
Checklist:
Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.