Serverless Framework plugin for deploying Python MCP servers on AWS Lambda.
It keeps the normal Serverless deployment model and adds MCP functions under
custom.pythonMcp.
This plugin works together with modmex-lambda.
The plugin is only the Serverless deployment integration; modmex-lambda is
the MCP runtime and provides MCPServer, tools, resources, prompts, MCP
validation, HTTP transports, middleware, context propagation,
APIGatewayRestResolver, APIGatewayHttpResolver, LambdaWebAdapterResolver
and LambdaWebAdapterHandler. A Python project using this plugin must depend
on modmex-lambda for those runtime capabilities.
Install the plugin in a Serverless service:
npm install --save-dev serverless-python-mcpRegister it:
plugins:
- serverless-python-mcpDefine an MCP server:
custom:
pythonMcp:
servers:
orders:
handler: app.handler
transport: httpApi
streaming: falseThe Python module exports a normal Modmex handler:
from modmex_lambda import APIGatewayHttpResolver
from modmex_lambda.mcp import MCPServer
mcp = MCPServer(name="orders", version="1.0.0")
app = APIGatewayHttpResolver()
app.include_mcp(mcp, path="/mcp")
handler = app.handlerEach entry under custom.pythonMcp.servers creates one Lambda function. The
key is the server name and is used in the generated function name.
custom:
pythonMcp:
servers:
orders:
handler: app.handler
transport: httpApi
streaming: false
path: /mcp
architecture: x86_64
authorizer: ordersJwt
environment:
ORDERS_TABLE: orders
package:
patterns:
- templates/**Required. It uses Serverless' standard module.attribute notation:
handler: app.handler
handler: src.orders.lambda_handlerThe attribute must be a Modmex handler. For streaming it must be a
LambdaWebAdapterHandler, normally exported as handler = app.handler.
handler is the only accepted entrypoint property. The plugin does not infer
module names or use a separate server property.
The value identifies the AWS front door. It does not by itself select the
Python resolver; that is determined by streaming as described below:
transport |
AWS front door | Streaming mode | Buffered mode |
|---|---|---|---|
httpApi |
API Gateway HTTP API v2 | not supported | APIGatewayHttpResolver |
http |
API Gateway REST API v1 | LambdaWebAdapterResolver |
APIGatewayRestResolver |
url |
Lambda Function URL | LambdaWebAdapterResolver |
APIGatewayHttpResolver |
If transport is omitted, the default is httpApi. The aliases
api-gateway, http-api, and lambda-url are accepted temporarily and map
to http, httpApi, and url respectively.
streaming defaults to false. It controls how the configured handler is
executed:
Supported transports are url and http only. The application must expose a
LambdaWebAdapterResolver handler:
from modmex_lambda import LambdaWebAdapterResolver
app = LambdaWebAdapterResolver()
app.include_mcp(mcp, path="/mcp")
handler = app.handlerThe plugin adds the Lambda Web Adapter layer to this function, starts the
HTTP process through its temporary launcher, and enables incremental SSE
response streaming. httpApi with streaming: true is rejected.
All three transports are supported. Serverless invokes the configured handler as a normal Lambda function:
handler = app.handlerUse the resolver matching the transport: APIGatewayHttpResolver for
httpApi, APIGatewayRestResolver for http, and APIGatewayHttpResolver
for url. No Lambda Web Adapter layer or launcher is added.
Examples:
# API Gateway HTTP API v2, buffered
transport: httpApi
streaming: false
# API Gateway REST API v1, streaming
transport: http
streaming: true
# Lambda Function URL, buffered
transport: url
streaming: falseOptional and defaults to /mcp:
path: /orders/mcpThe value must begin with /. The application must register the same path:
app.include_mcp(mcp, path="/orders/mcp")Paths must be unique among servers sharing the same http or httpApi
transport. Different Function URLs may reuse /mcp because each function has
its own hostname.
The provider-level timeout remains the default for generated MCP functions.
Set timeout on an individual server when it needs a different Lambda
timeout:
custom:
pythonMcp:
servers:
reports:
handler: app.handler
transport: url
streaming: true
timeout: 900The value must be an integer from 1 to 900 seconds.
Supported values are x86_64 and arm64. Resolution order:
server architecture → provider architecture → x86_64
For streaming, the corresponding regional Lambda Web Adapter layer version 28 is attached only to the generated MCP function:
x86_64 → LambdaAdapterLayerX86:28
arm64 → LambdaAdapterLayerArm64:28
No global provider.layers entry is required.
Function URLs support only public access or AWS IAM:
transport: url
authorizer: aws_iamOmit authorizer for a public URL. API Gateway JWT, request, token, and
Cognito authorizers are not valid for Function URLs.
Use Serverless' native global configuration:
provider:
httpApi:
authorizers:
ordersJwt:
type: jwt
identitySource: $request.header.Authorization
issuerUrl: https://issuer.example.com
audience:
- orders-client
custom:
pythonMcp:
servers:
orders:
handler: app.handler
transport: httpApi
streaming: false
authorizer: ordersJwtAn HTTP API authorizer can also be declared inline. The plugin registers it in the native Serverless authorizer collection and references it from the route:
authorizer:
type: jwt
identitySource: $request.header.Authorization
issuerUrl: https://issuer.example.com
audience:
- orders-clientAll Serverless-compatible HTTP API authorizer types are preserved, including JWT, request, IAM, existing authorizer IDs, scopes, and Lambda authorizer properties.
For transport: http, the plugin passes the authorizer configuration through
to the native REST API event compiler. Use the same authorizer shape you would
use under a regular Serverless functions[].events[].http event.
Generated Lambda functions use:
service-stage-mcp-serverName
For API Gateway transports, the shared REST API name is:
service-stage-mcp
The HTTP API follows Serverless' normal provider configuration. A Function URL belongs to its Lambda function and has no independent resource name.
For streaming, the plugin temporarily stages an internal launcher:
serverless-mcp/run.sh
It is created before deployment artifacts are built, included only in the function package, and removed on successful completion or failure. It is not part of the user's source tree or application contract.
The launcher imports the configured module.attribute, validates that it is a
LambdaWebAdapterHandler, and calls handler.run(). Application code does
not need if __name__ == "__main__" or a permanent shell script.
The examples/ directory contains isolated projects for each
supported transport/mode combination:
http-api-buffered;rest-api-buffered;function-url-buffered;rest-api-streaming;function-url-streaming.
Each example has its own serverless.yml, pyproject.toml, and entrypoint so
it can be copied into a real service without inheriting configuration from
another example.
npm install
npm test
npm run lint
npm pack --dry-run
npm publishBefore publishing, update the version, review the tarball contents, and verify the peer dependency against the Serverless Framework versions supported by the release.