From d0754e0b1b89d18dbf9ca228af9f87acde0726e5 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 13:38:15 -0500 Subject: [PATCH 1/4] feat: add FastMCP Lambda template for MCP server scaffolding Add python-fastmcp-lambda template for scaffolding MCP servers that run on AWS Lambda with function URLs. Uses FastMCP for tool definitions and Mangum as the ASGI-to-Lambda adapter. Template includes sample HTTP tools (lookup_ip, get_random_user, fetch_post) matching the existing python/ template patterns. Updates GatewayTargetRenderer to select this template when the compute host is Lambda. --- .../assets.snapshot.test.ts.snap | 174 ++++++++++++++++++ .../mcp/python-fastmcp-lambda/README.md | 27 +++ .../mcp/python-fastmcp-lambda/handler.py | 114 ++++++++++++ .../mcp/python-fastmcp-lambda/pyproject.toml | 18 ++ src/cli/templates/GatewayTargetRenderer.ts | 2 +- 5 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 src/assets/mcp/python-fastmcp-lambda/README.md create mode 100644 src/assets/mcp/python-fastmcp-lambda/handler.py create mode 100644 src/assets/mcp/python-fastmcp-lambda/pyproject.toml diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 89ef1df5..3c1a48fb 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -395,6 +395,9 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f "cdk/tsconfig.json", "container/python/Dockerfile", "container/python/dockerignore.template", + "mcp/python-fastmcp-lambda/README.md", + "mcp/python-fastmcp-lambda/handler.py", + "mcp/python-fastmcp-lambda/pyproject.toml", "mcp/python-lambda/README.md", "mcp/python-lambda/handler.py", "mcp/python-lambda/pyproject.toml", @@ -631,6 +634,177 @@ if __name__ == "__main__": " `; +exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-fastmcp-lambda/README.md should match snapshot 1`] = ` +"# {{ name }} + +FastMCP server running on AWS Lambda with a function URL, generated by the AgentCore CLI. + +Demonstrates HTTP tool patterns with proper error handling and retry logic. + +## How It Works + +This server uses [FastMCP](https://github.com/jlowin/fastmcp) to define MCP tools and +[Mangum](https://github.com/jordanh/mangum) to adapt the ASGI app for AWS Lambda. +The Lambda function URL provides the HTTP endpoint that the AgentCore gateway connects to. + +## Available Tools + +| Tool | Description | +| ----------------- | ------------------------------------------------------ | +| \`lookup_ip\` | Look up geolocation and network info for an IP address | +| \`get_random_user\` | Generate a random user profile for testing | +| \`fetch_post\` | Fetch a post by ID from JSONPlaceholder API | + +## Deployment + +\`\`\`bash +agentcore deploy +\`\`\` + +The CDK stack creates the Lambda function, function URL, and wires it to the gateway target. +" +`; + +exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-fastmcp-lambda/handler.py should match snapshot 1`] = ` +"""" +FastMCP Server for AWS Lambda with Function URL. + +This template shows: +- FastMCP server running on Lambda via Mangum ASGI adapter +- HTTP tool patterns with proper error handling +- Retry logic and response validation + +Deploy with: agentcore deploy +""" + +import logging +from typing import Any + +import httpx +from mangum import Mangum +from mcp.server.fastmcp import FastMCP + +logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + +mcp = FastMCP("tools") + +HTTP_TIMEOUT = 10.0 +MAX_RETRIES = 2 + + +async def fetch_json(url: str, headers: dict[str, str] | None = None) -> dict[str, Any] | None: + """Make an HTTP GET request with retry logic.""" + async with httpx.AsyncClient() as client: + for attempt in range(MAX_RETRIES): + try: + response = await client.get(url, headers=headers, timeout=HTTP_TIMEOUT) + response.raise_for_status() + return response.json() + except httpx.TimeoutException: + logger.warning(f"Timeout on attempt {attempt + 1} for {url}") + except httpx.HTTPStatusError as e: + logger.error(f"HTTP {e.response.status_code} for {url}") + return None + except httpx.RequestError as e: + logger.error(f"Request failed: {e}") + return None + return None + + +@mcp.tool() +async def lookup_ip(ip_address: str) -> str: + """Look up geolocation and network info for an IP address. + + Args: + ip_address: IPv4 or IPv6 address to look up + """ + data = await fetch_json(f"http://ip-api.com/json/{ip_address}") + + if not data: + return f"Failed to look up IP: {ip_address}" + + if data.get("status") == "fail": + return f"Lookup failed: {data.get('message', 'unknown error')}" + + return ( + f"IP: {data['query']}\\n" + f"Location: {data['city']}, {data['regionName']}, {data['country']}\\n" + f"ISP: {data['isp']}\\n" + f"Organization: {data['org']}\\n" + f"Timezone: {data['timezone']}" + ) + + +@mcp.tool() +async def get_random_user() -> str: + """Generate a random user profile for testing or mock data.""" + data = await fetch_json("https://randomuser.me/api/") + + if not data or "results" not in data: + return "Failed to generate random user." + + user = data["results"][0] + name = user["name"] + location = user["location"] + + return ( + f"Name: {name['first']} {name['last']}\\n" + f"Email: {user['email']}\\n" + f"Location: {location['city']}, {location['country']}\\n" + f"Phone: {user['phone']}" + ) + + +@mcp.tool() +async def fetch_post(post_id: int) -> str: + """Fetch a post by ID from JSONPlaceholder API. + + Args: + post_id: The post ID (1-100) + """ + if not 1 <= post_id <= 100: + return "Post ID must be between 1 and 100." + + data = await fetch_json(f"https://jsonplaceholder.typicode.com/posts/{post_id}") + + if not data: + return f"Failed to fetch post {post_id}." + + return ( + f"Post #{data['id']}\\n" + f"Title: {data['title']}\\n\\n" + f"{data['body']}" + ) + + +# Create ASGI app from FastMCP server and wrap with Mangum for Lambda +handler = Mangum(mcp.sse_app(), lifespan="off") +" +`; + +exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-fastmcp-lambda/pyproject.toml should match snapshot 1`] = ` +"[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "FastMCP Server on AWS Lambda" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "mcp[cli] >= 1.2.0", + "httpx >= 0.27.0", + "mangum >= 0.19.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] +" +`; + exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-lambda/README.md should match snapshot 1`] = ` "# {{ Name }} diff --git a/src/assets/mcp/python-fastmcp-lambda/README.md b/src/assets/mcp/python-fastmcp-lambda/README.md new file mode 100644 index 00000000..4a707fe1 --- /dev/null +++ b/src/assets/mcp/python-fastmcp-lambda/README.md @@ -0,0 +1,27 @@ +# {{ name }} + +FastMCP server running on AWS Lambda with a function URL, generated by the AgentCore CLI. + +Demonstrates HTTP tool patterns with proper error handling and retry logic. + +## How It Works + +This server uses [FastMCP](https://github.com/jlowin/fastmcp) to define MCP tools and +[Mangum](https://github.com/jordanh/mangum) to adapt the ASGI app for AWS Lambda. +The Lambda function URL provides the HTTP endpoint that the AgentCore gateway connects to. + +## Available Tools + +| Tool | Description | +| ----------------- | ------------------------------------------------------ | +| `lookup_ip` | Look up geolocation and network info for an IP address | +| `get_random_user` | Generate a random user profile for testing | +| `fetch_post` | Fetch a post by ID from JSONPlaceholder API | + +## Deployment + +```bash +agentcore deploy +``` + +The CDK stack creates the Lambda function, function URL, and wires it to the gateway target. diff --git a/src/assets/mcp/python-fastmcp-lambda/handler.py b/src/assets/mcp/python-fastmcp-lambda/handler.py new file mode 100644 index 00000000..fb6554cc --- /dev/null +++ b/src/assets/mcp/python-fastmcp-lambda/handler.py @@ -0,0 +1,114 @@ +""" +FastMCP Server for AWS Lambda with Function URL. + +This template shows: +- FastMCP server running on Lambda via Mangum ASGI adapter +- HTTP tool patterns with proper error handling +- Retry logic and response validation + +Deploy with: agentcore deploy +""" + +import logging +from typing import Any + +import httpx +from mangum import Mangum +from mcp.server.fastmcp import FastMCP + +logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + +mcp = FastMCP("tools") + +HTTP_TIMEOUT = 10.0 +MAX_RETRIES = 2 + + +async def fetch_json(url: str, headers: dict[str, str] | None = None) -> dict[str, Any] | None: + """Make an HTTP GET request with retry logic.""" + async with httpx.AsyncClient() as client: + for attempt in range(MAX_RETRIES): + try: + response = await client.get(url, headers=headers, timeout=HTTP_TIMEOUT) + response.raise_for_status() + return response.json() + except httpx.TimeoutException: + logger.warning(f"Timeout on attempt {attempt + 1} for {url}") + except httpx.HTTPStatusError as e: + logger.error(f"HTTP {e.response.status_code} for {url}") + return None + except httpx.RequestError as e: + logger.error(f"Request failed: {e}") + return None + return None + + +@mcp.tool() +async def lookup_ip(ip_address: str) -> str: + """Look up geolocation and network info for an IP address. + + Args: + ip_address: IPv4 or IPv6 address to look up + """ + data = await fetch_json(f"http://ip-api.com/json/{ip_address}") + + if not data: + return f"Failed to look up IP: {ip_address}" + + if data.get("status") == "fail": + return f"Lookup failed: {data.get('message', 'unknown error')}" + + return ( + f"IP: {data['query']}\n" + f"Location: {data['city']}, {data['regionName']}, {data['country']}\n" + f"ISP: {data['isp']}\n" + f"Organization: {data['org']}\n" + f"Timezone: {data['timezone']}" + ) + + +@mcp.tool() +async def get_random_user() -> str: + """Generate a random user profile for testing or mock data.""" + data = await fetch_json("https://randomuser.me/api/") + + if not data or "results" not in data: + return "Failed to generate random user." + + user = data["results"][0] + name = user["name"] + location = user["location"] + + return ( + f"Name: {name['first']} {name['last']}\n" + f"Email: {user['email']}\n" + f"Location: {location['city']}, {location['country']}\n" + f"Phone: {user['phone']}" + ) + + +@mcp.tool() +async def fetch_post(post_id: int) -> str: + """Fetch a post by ID from JSONPlaceholder API. + + Args: + post_id: The post ID (1-100) + """ + if not 1 <= post_id <= 100: + return "Post ID must be between 1 and 100." + + data = await fetch_json(f"https://jsonplaceholder.typicode.com/posts/{post_id}") + + if not data: + return f"Failed to fetch post {post_id}." + + return ( + f"Post #{data['id']}\n" + f"Title: {data['title']}\n\n" + f"{data['body']}" + ) + + +# Create ASGI app from FastMCP server and wrap with Mangum for Lambda +handler = Mangum(mcp.sse_app(), lifespan="off") diff --git a/src/assets/mcp/python-fastmcp-lambda/pyproject.toml b/src/assets/mcp/python-fastmcp-lambda/pyproject.toml new file mode 100644 index 00000000..ba5b0d8d --- /dev/null +++ b/src/assets/mcp/python-fastmcp-lambda/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{ name }}" +version = "0.1.0" +description = "FastMCP Server on AWS Lambda" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "mcp[cli] >= 1.2.0", + "httpx >= 0.27.0", + "mangum >= 0.19.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] diff --git a/src/cli/templates/GatewayTargetRenderer.ts b/src/cli/templates/GatewayTargetRenderer.ts index 12e25f4d..3505a16a 100644 --- a/src/cli/templates/GatewayTargetRenderer.ts +++ b/src/cli/templates/GatewayTargetRenderer.ts @@ -79,7 +79,7 @@ export async function renderGatewayTargetTemplate( } // Select template based on compute host - const templateSubdir = host === 'Lambda' ? 'python-lambda' : 'python'; + const templateSubdir = host === 'Lambda' ? 'python-fastmcp-lambda' : 'python'; const templateDir = getTemplatePath('mcp', templateSubdir); await copyAndRenderDir(templateDir, outputDir, { Name: toolName }); From 85577a836ac8ecda6aa147eeca3516d3c7a4e91d Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 14:41:54 -0500 Subject: [PATCH 2/4] fix: rename handler to lambda_handler to match DEFAULT_HANDLER The CLI writes handler: 'handler.lambda_handler' in compute config. The template must export lambda_handler, not handler. --- src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap | 2 +- src/assets/mcp/python-fastmcp-lambda/handler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 3c1a48fb..bdd7c567 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -779,7 +779,7 @@ async def fetch_post(post_id: int) -> str: # Create ASGI app from FastMCP server and wrap with Mangum for Lambda -handler = Mangum(mcp.sse_app(), lifespan="off") +lambda_handler = Mangum(mcp.sse_app(), lifespan="off") " `; diff --git a/src/assets/mcp/python-fastmcp-lambda/handler.py b/src/assets/mcp/python-fastmcp-lambda/handler.py index fb6554cc..28f2aef3 100644 --- a/src/assets/mcp/python-fastmcp-lambda/handler.py +++ b/src/assets/mcp/python-fastmcp-lambda/handler.py @@ -111,4 +111,4 @@ async def fetch_post(post_id: int) -> str: # Create ASGI app from FastMCP server and wrap with Mangum for Lambda -handler = Mangum(mcp.sse_app(), lifespan="off") +lambda_handler = Mangum(mcp.sse_app(), lifespan="off") From e3d19f2720426727d102d5995d7d587f875a6b06 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 21:38:34 -0500 Subject: [PATCH 3/4] fix: use correct template variable casing and switch to http_app() for Lambda compatibility --- .../__tests__/__snapshots__/assets.snapshot.test.ts.snap | 8 ++++---- src/assets/mcp/python-fastmcp-lambda/README.md | 2 +- src/assets/mcp/python-fastmcp-lambda/handler.py | 4 ++-- src/assets/mcp/python-fastmcp-lambda/pyproject.toml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index bdd7c567..95c9bd8b 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -635,7 +635,7 @@ if __name__ == "__main__": `; exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-fastmcp-lambda/README.md should match snapshot 1`] = ` -"# {{ name }} +"# {{ Name }} FastMCP server running on AWS Lambda with a function URL, generated by the AgentCore CLI. @@ -687,7 +687,7 @@ from mcp.server.fastmcp import FastMCP logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") logger = logging.getLogger(__name__) -mcp = FastMCP("tools") +mcp = FastMCP("{{ Name }}") HTTP_TIMEOUT = 10.0 MAX_RETRIES = 2 @@ -779,7 +779,7 @@ async def fetch_post(post_id: int) -> str: # Create ASGI app from FastMCP server and wrap with Mangum for Lambda -lambda_handler = Mangum(mcp.sse_app(), lifespan="off") +lambda_handler = Mangum(mcp.http_app(), lifespan="off") " `; @@ -789,7 +789,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "{{ name }}" +name = "{{ Name }}" version = "0.1.0" description = "FastMCP Server on AWS Lambda" readme = "README.md" diff --git a/src/assets/mcp/python-fastmcp-lambda/README.md b/src/assets/mcp/python-fastmcp-lambda/README.md index 4a707fe1..b525d2af 100644 --- a/src/assets/mcp/python-fastmcp-lambda/README.md +++ b/src/assets/mcp/python-fastmcp-lambda/README.md @@ -1,4 +1,4 @@ -# {{ name }} +# {{ Name }} FastMCP server running on AWS Lambda with a function URL, generated by the AgentCore CLI. diff --git a/src/assets/mcp/python-fastmcp-lambda/handler.py b/src/assets/mcp/python-fastmcp-lambda/handler.py index 28f2aef3..ac9ef5d0 100644 --- a/src/assets/mcp/python-fastmcp-lambda/handler.py +++ b/src/assets/mcp/python-fastmcp-lambda/handler.py @@ -19,7 +19,7 @@ logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") logger = logging.getLogger(__name__) -mcp = FastMCP("tools") +mcp = FastMCP("{{ Name }}") HTTP_TIMEOUT = 10.0 MAX_RETRIES = 2 @@ -111,4 +111,4 @@ async def fetch_post(post_id: int) -> str: # Create ASGI app from FastMCP server and wrap with Mangum for Lambda -lambda_handler = Mangum(mcp.sse_app(), lifespan="off") +lambda_handler = Mangum(mcp.http_app(), lifespan="off") diff --git a/src/assets/mcp/python-fastmcp-lambda/pyproject.toml b/src/assets/mcp/python-fastmcp-lambda/pyproject.toml index ba5b0d8d..169b003c 100644 --- a/src/assets/mcp/python-fastmcp-lambda/pyproject.toml +++ b/src/assets/mcp/python-fastmcp-lambda/pyproject.toml @@ -3,7 +3,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "{{ name }}" +name = "{{ Name }}" version = "0.1.0" description = "FastMCP Server on AWS Lambda" readme = "README.md" From 9c3817b79ce42048226f0d5dd928457abe8cca39 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 11:50:56 -0500 Subject: [PATCH 4/4] fix: use streamable_http_app() instead of http_app() for FastMCP Lambda --- .../assets.snapshot.test.ts.snap | 33 +++++++++++++------ .../mcp/python-fastmcp-lambda/handler.py | 9 ++--- .../mcp/python-fastmcp-lambda/pyproject.toml | 7 ++-- src/assets/mcp/python-fastmcp-lambda/run.sh | 2 ++ .../mcp/python-fastmcp-lambda/server.py | 5 +++ 5 files changed, 36 insertions(+), 20 deletions(-) create mode 100755 src/assets/mcp/python-fastmcp-lambda/run.sh create mode 100644 src/assets/mcp/python-fastmcp-lambda/server.py diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 95c9bd8b..1e91f804 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -398,6 +398,8 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f "mcp/python-fastmcp-lambda/README.md", "mcp/python-fastmcp-lambda/handler.py", "mcp/python-fastmcp-lambda/pyproject.toml", + "mcp/python-fastmcp-lambda/run.sh", + "mcp/python-fastmcp-lambda/server.py", "mcp/python-lambda/README.md", "mcp/python-lambda/handler.py", "mcp/python-lambda/pyproject.toml", @@ -670,7 +672,7 @@ exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-fastmcp-lambda FastMCP Server for AWS Lambda with Function URL. This template shows: -- FastMCP server running on Lambda via Mangum ASGI adapter +- FastMCP server running on Lambda via Lambda Web Adapter + uvicorn - HTTP tool patterns with proper error handling - Retry logic and response validation @@ -681,13 +683,12 @@ import logging from typing import Any import httpx -from mangum import Mangum from mcp.server.fastmcp import FastMCP logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") logger = logging.getLogger(__name__) -mcp = FastMCP("{{ Name }}") +mcp = FastMCP("{{ Name }}", stateless_http=True, host="0.0.0.0") HTTP_TIMEOUT = 10.0 MAX_RETRIES = 2 @@ -776,10 +777,6 @@ async def fetch_post(post_id: int) -> str: f"Title: {data['title']}\\n\\n" f"{data['body']}" ) - - -# Create ASGI app from FastMCP server and wrap with Mangum for Lambda -lambda_handler = Mangum(mcp.http_app(), lifespan="off") " `; @@ -789,15 +786,16 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "{{ Name }}" +name = "{{ name }}" version = "0.1.0" description = "FastMCP Server on AWS Lambda" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "mcp[cli] >= 1.2.0", + "mcp[cli] >= 1.18.0", "httpx >= 0.27.0", - "mangum >= 0.19.0", + "fastapi >= 0.115.0", + "uvicorn >= 0.34.0", ] [tool.hatch.build.targets.wheel] @@ -805,6 +803,21 @@ packages = ["."] " `; +exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-fastmcp-lambda/run.sh should match snapshot 1`] = ` +"#!/bin/bash +exec python -m uvicorn --port=$PORT server:app +" +`; + +exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-fastmcp-lambda/server.py should match snapshot 1`] = ` +"from fastapi import FastAPI +from handler import mcp + +app = FastAPI(lifespan=lambda app: mcp.session_manager.run()) +app.mount("/", mcp.streamable_http_app()) +" +`; + exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python-lambda/README.md should match snapshot 1`] = ` "# {{ Name }} diff --git a/src/assets/mcp/python-fastmcp-lambda/handler.py b/src/assets/mcp/python-fastmcp-lambda/handler.py index ac9ef5d0..5b40cd93 100644 --- a/src/assets/mcp/python-fastmcp-lambda/handler.py +++ b/src/assets/mcp/python-fastmcp-lambda/handler.py @@ -2,7 +2,7 @@ FastMCP Server for AWS Lambda with Function URL. This template shows: -- FastMCP server running on Lambda via Mangum ASGI adapter +- FastMCP server running on Lambda via Lambda Web Adapter + uvicorn - HTTP tool patterns with proper error handling - Retry logic and response validation @@ -13,13 +13,12 @@ from typing import Any import httpx -from mangum import Mangum from mcp.server.fastmcp import FastMCP logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") logger = logging.getLogger(__name__) -mcp = FastMCP("{{ Name }}") +mcp = FastMCP("{{ Name }}", stateless_http=True, host="0.0.0.0") HTTP_TIMEOUT = 10.0 MAX_RETRIES = 2 @@ -108,7 +107,3 @@ async def fetch_post(post_id: int) -> str: f"Title: {data['title']}\n\n" f"{data['body']}" ) - - -# Create ASGI app from FastMCP server and wrap with Mangum for Lambda -lambda_handler = Mangum(mcp.http_app(), lifespan="off") diff --git a/src/assets/mcp/python-fastmcp-lambda/pyproject.toml b/src/assets/mcp/python-fastmcp-lambda/pyproject.toml index 169b003c..244012d2 100644 --- a/src/assets/mcp/python-fastmcp-lambda/pyproject.toml +++ b/src/assets/mcp/python-fastmcp-lambda/pyproject.toml @@ -3,15 +3,16 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "{{ Name }}" +name = "{{ name }}" version = "0.1.0" description = "FastMCP Server on AWS Lambda" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "mcp[cli] >= 1.2.0", + "mcp[cli] >= 1.18.0", "httpx >= 0.27.0", - "mangum >= 0.19.0", + "fastapi >= 0.115.0", + "uvicorn >= 0.34.0", ] [tool.hatch.build.targets.wheel] diff --git a/src/assets/mcp/python-fastmcp-lambda/run.sh b/src/assets/mcp/python-fastmcp-lambda/run.sh new file mode 100755 index 00000000..bb9f89b1 --- /dev/null +++ b/src/assets/mcp/python-fastmcp-lambda/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exec python -m uvicorn --port=$PORT server:app diff --git a/src/assets/mcp/python-fastmcp-lambda/server.py b/src/assets/mcp/python-fastmcp-lambda/server.py new file mode 100644 index 00000000..653a8a18 --- /dev/null +++ b/src/assets/mcp/python-fastmcp-lambda/server.py @@ -0,0 +1,5 @@ +from fastapi import FastAPI +from handler import mcp + +app = FastAPI(lifespan=lambda app: mcp.session_manager.run()) +app.mount("/", mcp.streamable_http_app())