@aws/run-mcp-servers-with-aws-lambda 0.2.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/README.md +418 -92
  2. package/dist/handlers/{api_gateway_proxy_event_handler.d.ts → apiGatewayProxyEventHandler.d.ts} +2 -2
  3. package/dist/handlers/{api_gateway_proxy_event_handler.js → apiGatewayProxyEventHandler.js} +1 -1
  4. package/dist/handlers/{api_gateway_proxy_event_v2_handler.d.ts → apiGatewayProxyEventV2Handler.d.ts} +2 -2
  5. package/dist/handlers/{api_gateway_proxy_event_v2_handler.js → apiGatewayProxyEventV2Handler.js} +1 -1
  6. package/dist/handlers/index.d.ts +4 -4
  7. package/dist/handlers/index.js +3 -3
  8. package/dist/handlers/{lambda_function_url_event_handler.d.ts → lambdaFunctionUrlEventHandler.d.ts} +2 -2
  9. package/dist/handlers/{lambda_function_url_event_handler.js → lambdaFunctionUrlEventHandler.js} +1 -1
  10. package/dist/handlers/{streamable_http_handler.d.ts → streamableHttpHandler.d.ts} +1 -1
  11. package/dist/server-adapter/index.d.ts +2 -2
  12. package/dist/server-adapter/index.js +2 -2
  13. package/dist/server-adapter/{stdio_server_adapter.test.js → stdioServerAdapter.test.js} +2 -2
  14. package/dist/server-adapter/{stdio_server_adapter_request_handler.d.ts → stdioServerAdapterRequestHandler.d.ts} +1 -1
  15. package/dist/server-adapter/{stdio_server_adapter_request_handler.js → stdioServerAdapterRequestHandler.js} +1 -1
  16. package/dist/server-adapter/{stdio_server_adapter_request_handler.test.js → stdioServerAdapterRequestHandler.test.js} +53 -53
  17. package/package.json +2 -1
  18. /package/dist/handlers/{request_handler.d.ts → requestHandler.d.ts} +0 -0
  19. /package/dist/handlers/{request_handler.js → requestHandler.js} +0 -0
  20. /package/dist/handlers/{streamable_http_handler.js → streamableHttpHandler.js} +0 -0
  21. /package/dist/server-adapter/{stdio_server_adapter.d.ts → stdioServerAdapter.d.ts} +0 -0
  22. /package/dist/server-adapter/{stdio_server_adapter.js → stdioServerAdapter.js} +0 -0
  23. /package/dist/server-adapter/{stdio_server_adapter.test.d.ts → stdioServerAdapter.test.d.ts} +0 -0
  24. /package/dist/server-adapter/{stdio_server_adapter_request_handler.test.d.ts → stdioServerAdapterRequestHandler.test.d.ts} +0 -0
package/README.md CHANGED
@@ -17,13 +17,11 @@ flowchart LR
17
17
  end
18
18
  ```
19
19
 
20
- This MCP server adapter for AWS Lambda helps you to wrap existing stdio MCP servers into Lambda functions.
20
+ This library helps you to wrap existing stdio MCP servers into Lambda functions.
21
21
  You can invoke these function-based MCP servers from your application using the MCP protocol
22
- over short-lived connections.
22
+ over short-lived HTTPS connections.
23
23
  Your application can then be a desktop-based app, a distributed system running in the cloud,
24
24
  or any other architecture.
25
- Your application must have access to invoke your Lambda functions,
26
- and use the custom MCP client transport that invokes the Lambda functions.
27
25
 
28
26
  ```mermaid
29
27
  flowchart LR
@@ -31,62 +29,356 @@ flowchart LR
31
29
  App["Your Application<br>with MCP Clients"]
32
30
  S3["MCP Server A<br>(Lambda function)"]
33
31
  S4["MCP Server B<br>(Lambda function)"]
34
- App <-->|"MCP Protocol<br>with custom transport<br>(invoke function)"| S3
35
- App <-->|"MCP Protocol<br>with custom transport<br>(invoke function)"| S4
32
+ App <-->|"MCP Protocol<br>(over HTTPS connection)"| S3
33
+ App <-->|"MCP Protocol<br>(over HTTPS connection)"| S4
36
34
  end
37
35
  ```
38
36
 
39
- ## Considerations
37
+ Using this library, the Lambda function will manage the lifecycle of your stdio MCP server.
38
+ Each Lambda function invocation will:
40
39
 
41
- - If you are looking for a way to invoke existing Lambda functions as tools through MCP,
42
- see the [AWS Lambda MCP Server project](https://awslabs.github.io/mcp/servers/lambda-mcp-server/).
43
- - This package currently requires using a custom MCP client transport to communicate with the MCP
44
- server by invoking the Lambda function. Existing applications with MCP support such as
45
- Amazon Q Developer CLI, Cline, etc do not have this custom transport, and cannot communicate with
46
- MCP servers adapted into Lambda functions.
47
- Note: with [upcoming changes to the MCP protocol](https://github.com/modelcontextprotocol/specification/pull/206),
48
- we expect that this limitation will be removed in the future.
49
- - This package currently supports MCP servers and clients written in Python and Typescript.
50
- Other languages such as Kotlin are not supported.
51
- - The server adapters only adapt stdio MCP servers, not servers written for other protocols such as SSE.
52
- - The server adapters do not maintain any MCP server state across Lambda function invocations.
53
- Only stateless MCP servers are a good fit for using this adapter. For example, MCP servers
54
- that invoke stateless tools like the [time MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/time)
55
- or make stateless web requests like the [fetch MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch).
56
- Stateful MCP servers are not a good fit, because they will lose their state on every request.
57
- For example, MCP servers that manage data on disk or in memory such as
58
- the [sqlite MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite),
59
- the [filesystem MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
60
- and the [git MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/git).
61
- - The server adapters ignore any MCP protocol notifications from the client to the server.
62
- - The server adapters do not provide mechanisms for managing any secrets needed by the wrapped
63
- MCP server. For example, the [GitHub MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/github)
64
- and the [Brave search MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search)
65
- require API keys to make requests to third-party APIs.
66
- You can configure these API keys as
67
- [encrypted environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html)
68
- in the Lambda function's configuration. However, note that anyone with access to invoke the Lambda function
69
- will then have access to use your API key to call the third-party APIs by invoking the function.
70
- We recommend limiting access to the Lambda function using
71
- [least-privilege IAM policies](https://docs.aws.amazon.com/lambda/latest/dg/security-iam.html).
40
+ 1. Start the stdio MCP server as a child process
41
+ 1. Initialize the MCP server
42
+ 1. Forward the incoming request to the local server
43
+ 1. Return the server's response to the function caller
44
+ 1. Shut down the MCP server child process
45
+
46
+ This library supports connecting to Lambda-based MCP servers in three ways:
47
+
48
+ 1. The [MCP Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http), using Amazon API Gateway. Typically authenticated using OAuth.
49
+ 2. A custom Streamable HTTP transport with support for SigV4, using a Lambda function URL. Authenticated with AWS IAM.
50
+ 3. A custom Lambda invocation transport, using the Lambda Invoke API directly. Authenticated with AWS IAM.
51
+
52
+ ## Using API Gateway
53
+
54
+ ```mermaid
55
+ flowchart LR
56
+ App["MCP Client"]
57
+ T1["MCP Server<br>(Lambda function)"]
58
+ T2["API Gateway"]
59
+ T3["OAuth Server<br>(Cognito or similar)"]
60
+ App -->|"MCP Streamable<br>HTTP Transport"| T2
61
+ T2 -->|"Invoke"| T1
62
+ T2 -->|"Authorize"| T3
63
+ ```
64
+
65
+ This solution is compatible with most MCP clients that support the streamable HTTP transport.
66
+ MCP servers deployed with this architecture can typically be used with off-the-shelf
67
+ MCP-compatible applications such as Cursor, Cline, Claude Desktop, etc.
68
+
69
+ You can choose your desired OAuth server provider for this solution. The examples in this
70
+ repository use Amazon Cognito, or you can use third-party providers such as Okta or Auth0
71
+ with API Gateway custom authorization.
72
+
73
+ <details>
74
+
75
+ <summary><b>Python server example</b></summary>
76
+
77
+ ```python
78
+ import sys
79
+ from mcp.client.stdio import StdioServerParameters
80
+ from mcp_lambda import APIGatewayProxyEventHandler, StdioServerAdapterRequestHandler
81
+
82
+ server_params = StdioServerParameters(
83
+ command=sys.executable,
84
+ args=[
85
+ "-m",
86
+ "my_mcp_server_python_module",
87
+ "--my-server-command-line-parameter",
88
+ "some_value",
89
+ ],
90
+ )
91
+
92
+
93
+ request_handler = StdioServerAdapterRequestHandler(server_params)
94
+ event_handler = APIGatewayProxyEventHandler(request_handler)
95
+
96
+
97
+ def handler(event, context):
98
+ return event_handler.handle(event, context)
99
+ ```
100
+
101
+ See a full, deployable example [here](examples/servers/dad-jokes/).
102
+
103
+ </details>
104
+
105
+ <details>
106
+
107
+ <summary><b>Typescript server example</b></summary>
108
+
109
+ ```typescript
110
+ import {
111
+ Handler,
112
+ Context,
113
+ APIGatewayProxyWithCognitoAuthorizerEvent,
114
+ APIGatewayProxyResult,
115
+ } from "aws-lambda";
116
+ import {
117
+ APIGatewayProxyEventHandler,
118
+ StdioServerAdapterRequestHandler,
119
+ } from "@aws/run-mcp-servers-with-aws-lambda";
120
+
121
+ const serverParams = {
122
+ command: "npx",
123
+ args: [
124
+ "--offline",
125
+ "my-mcp-server-typescript-module",
126
+ "--my-server-command-line-parameter",
127
+ "some_value",
128
+ ],
129
+ };
130
+
131
+ const requestHandler = new APIGatewayProxyEventHandler(
132
+ new StdioServerAdapterRequestHandler(serverParams)
133
+ );
134
+
135
+ export const handler: Handler = async (
136
+ event: APIGatewayProxyWithCognitoAuthorizerEvent,
137
+ context: Context
138
+ ): Promise<APIGatewayProxyResult> => {
139
+ return requestHandler.handle(event, context);
140
+ };
141
+ ```
142
+
143
+ See a full, deployable example [here](examples/servers/dog-facts/).
144
+
145
+ </details>
146
+
147
+ <details>
148
+
149
+ <summary><b>Python client example</b></summary>
150
+
151
+ ```python
152
+ from mcp import ClientSession
153
+ from mcp.client.streamable_http import streamablehttp_client
154
+
155
+ # Create OAuth client provider here
156
+
157
+ async with streamablehttp_client(
158
+ url="https://abc123.execute-api.us-east-2.amazonaws.com/prod/mcp",
159
+ auth=oauth_client_provider,
160
+ ) as (
161
+ read_stream,
162
+ write_stream,
163
+ _,
164
+ ):
165
+ async with ClientSession(read_stream, write_stream) as session:
166
+ await session.initialize()
167
+ tool_result = await session.call_tool("echo", {"message": "hello"})
168
+ ```
169
+
170
+ See a full example as part of the sample chatbot [here](examples/chatbots/python/server_clients/interactive_oauth.py).
171
+
172
+ </details>
173
+
174
+ <details>
175
+
176
+ <summary><b>Typescript client example</b></summary>
177
+
178
+ ```typescript
179
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
180
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
181
+
182
+ const client = new Client(
183
+ {
184
+ name: "my-client",
185
+ version: "0.0.1",
186
+ },
187
+ {
188
+ capabilities: {
189
+ sampling: {},
190
+ },
191
+ }
192
+ );
193
+
194
+ // Create OAuth client provider here
195
+
196
+ const transport = new StreamableHTTPClientTransport(
197
+ "https://abc123.execute-api.us-east-2.amazonaws.com/prod/mcp",
198
+ {
199
+ authProvider: oauthProvider,
200
+ }
201
+ );
202
+ await client.connect(transport);
203
+ ```
204
+
205
+ See a full example as part of the sample chatbot [here](examples/chatbots/typescript/src/server_clients/interactive_oauth.ts).
206
+
207
+ </details>
208
+
209
+ ## Using a Lambda function URL
210
+
211
+ ```mermaid
212
+ flowchart LR
213
+ App["MCP Client"]
214
+ T1["MCP Server<br>(Lambda function)"]
215
+ T2["Lambda function URL"]
216
+ App -->|"Custom Streamable HTTP<br>Transport with AWS Auth"| T2
217
+ T2 -->|"Invoke"| T1
218
+ ```
219
+
220
+ This solution uses AWS IAM for authentication, and relies on granting
221
+ [Lambda InvokeFunctionUrl permission](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html#urls-auth-iam) to your
222
+ IAM users and roles to enable access to the MCP server. Clients must use an extension to the MCP Streamable
223
+ HTTP transport that signs requests with [AWS SigV4](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html).
224
+ Off-the-shelf MCP-compatible applications are unlikely to have support for this custom transport,
225
+ so this solution is more appropriate for service-to-service communication rather than for end users.
72
226
 
73
- ## Examples
227
+ <details>
74
228
 
75
- ### Python server example
229
+ <summary><b>Python server example</b></summary>
76
230
 
77
- This project includes an
78
- [example Python Lambda function](examples/servers/time/function/index.py)
79
- that runs the simple
80
- [MCP 'time' reference server](https://github.com/modelcontextprotocol/servers/tree/main/src/time).
81
- The Lambda function bundles the [mcp-server-time package](https://pypi.org/project/mcp-server-time/).
82
- On each function invocation, the Lambda function will manage the lifecycle of the bundled MCP server.
83
- It will:
231
+ ```python
232
+ import sys
233
+ from mcp.client.stdio import StdioServerParameters
234
+ from mcp_lambda import LambdaFunctionURLEventHandler, StdioServerAdapterRequestHandler
235
+
236
+ server_params = StdioServerParameters(
237
+ command=sys.executable,
238
+ args=[
239
+ "-m",
240
+ "my_mcp_server_python_module",
241
+ "--my-server-command-line-parameter",
242
+ "some_value",
243
+ ],
244
+ )
245
+
246
+
247
+ request_handler = StdioServerAdapterRequestHandler(server_params)
248
+ event_handler = LambdaFunctionURLEventHandler(request_handler)
249
+
250
+
251
+ def handler(event, context):
252
+ return event_handler.handle(event, context)
253
+ ```
254
+
255
+ See a full, deployable example [here](examples/servers/mcpdoc/).
256
+
257
+ </details>
258
+
259
+ <details>
260
+
261
+ <summary><b>Typescript server example</b></summary>
262
+
263
+ ```typescript
264
+ import {
265
+ Handler,
266
+ Context,
267
+ APIGatewayProxyEventV2WithIAMAuthorizer,
268
+ APIGatewayProxyResultV2,
269
+ } from "aws-lambda";
270
+ import {
271
+ LambdaFunctionURLEventHandler,
272
+ StdioServerAdapterRequestHandler,
273
+ } from "@aws/run-mcp-servers-with-aws-lambda";
274
+
275
+ const serverParams = {
276
+ command: "npx",
277
+ args: [
278
+ "--offline",
279
+ "my-mcp-server-typescript-module",
280
+ "--my-server-command-line-parameter",
281
+ "some_value",
282
+ ],
283
+ };
284
+
285
+ const requestHandler = new LambdaFunctionURLEventHandler(
286
+ new StdioServerAdapterRequestHandler(serverParams)
287
+ );
288
+
289
+ export const handler: Handler = async (
290
+ event: APIGatewayProxyEventV2WithIAMAuthorizer,
291
+ context: Context
292
+ ): Promise<APIGatewayProxyResultV2> => {
293
+ return requestHandler.handle(event, context);
294
+ };
295
+ ```
296
+
297
+ See a full, deployable example [here](examples/servers/cat-facts/).
298
+
299
+ </details>
300
+
301
+ <details>
302
+
303
+ <summary><b>Python client example</b></summary>
304
+
305
+ ```python
306
+ from mcp import ClientSession
307
+ from mcp_lambda.client.streamable_http_sigv4 import streamablehttp_client_with_sigv4
308
+
309
+ async with streamablehttp_client_with_sigv4(
310
+ url="https://url-id-12345.lambda-url.us-east-2.on.aws",
311
+ service="lambda",
312
+ region="us-east-2",
313
+ ) as (
314
+ read_stream,
315
+ write_stream,
316
+ _,
317
+ ):
318
+ async with ClientSession(read_stream, write_stream) as session:
319
+ await session.initialize()
320
+ tool_result = await session.call_tool("echo", {"message": "hello"})
321
+ ```
322
+
323
+ See a full example as part of the sample chatbot [here](examples/chatbots/python/server_clients/lambda_function_url.py).
324
+
325
+ </details>
326
+
327
+ <details>
328
+
329
+ <summary><b>Typescript client example</b></summary>
330
+
331
+ ```typescript
332
+ import { StreamableHTTPClientWithSigV4Transport } from "@aws/run-mcp-servers-with-aws-lambda";
333
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
334
+
335
+ const client = new Client(
336
+ {
337
+ name: "my-client",
338
+ version: "0.0.1",
339
+ },
340
+ {
341
+ capabilities: {
342
+ sampling: {},
343
+ },
344
+ }
345
+ );
346
+
347
+ const transport = new StreamableHTTPClientWithSigV4Transport(
348
+ new URL("https://url-id-12345.lambda-url.us-east-2.on.aws"),
349
+ {
350
+ service: "lambda",
351
+ region: "us-east-2",
352
+ }
353
+ );
354
+ await client.connect(transport);
355
+ ```
84
356
 
85
- 1. start the 'time' MCP server as a child process
86
- 1. initialize the MCP server
87
- 1. forward the incoming request to the local server
88
- 1. return the server's response to the function caller
89
- 1. shut down the MCP server child process
357
+ See a full example as part of the sample chatbot [here](examples/chatbots/typescript/src/server_clients/lambda_function_url.ts).
358
+
359
+ </details>
360
+
361
+ ## Using the Lambda Invoke API
362
+
363
+ ```mermaid
364
+ flowchart LR
365
+ App["MCP Client"]
366
+ T1["MCP Server<br>(Lambda function)"]
367
+ App -->|"Custom MCP Transport<br>(Lambda Invoke API)"| T1
368
+ ```
369
+
370
+ Like the Lambda function URL approach, this solution uses AWS IAM for authentication.
371
+ It relies on granting
372
+ [Lambda InvokeFunction permission](https://docs.aws.amazon.com/lambda/latest/dg/lambda-api-permissions-ref.html)
373
+ to your IAM users and roles to enable access to the MCP server.
374
+ Clients must use a custom MCP transport that directly calls the
375
+ [Lambda Invoke API](https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html).
376
+ Off-the-shelf MCP-compatible applications are unlikely to have support for this custom transport,
377
+ so this solution is more appropriate for service-to-service communication rather than for end users.
378
+
379
+ <details>
380
+
381
+ <summary><b>Python server example</b></summary>
90
382
 
91
383
  ```python
92
384
  import sys
@@ -97,9 +389,9 @@ server_params = StdioServerParameters(
97
389
  command=sys.executable,
98
390
  args=[
99
391
  "-m",
100
- "mcp_server_time",
101
- "--local-timezone",
102
- "America/New_York",
392
+ "my_mcp_server_python_module",
393
+ "--my-server-command-line-parameter",
394
+ "some_value",
103
395
  ],
104
396
  )
105
397
 
@@ -108,69 +400,66 @@ def handler(event, context):
108
400
  return stdio_server_adapter(server_params, event, context)
109
401
  ```
110
402
 
111
- ### Typescript server example
403
+ See a full, deployable example [here](examples/servers/time/).
404
+
405
+ </details>
112
406
 
113
- This project includes an
114
- [example Node.js Lambda function](examples/servers/weather-alerts/lib/weather-alerts-mcp-server.function.ts)
115
- that runs an [OpenAPI MCP server](https://github.com/snaggle-ai/openapi-mcp-server/)
116
- to provide a single API from [weather.gov](https://www.weather.gov/documentation/services-web-api) as a tool.
117
- The Lambda function bundles the [openapi-mcp-server package](https://www.npmjs.com/package/openapi-mcp-server).
118
- On each function invocation, the Lambda function will manage the lifecycle of the bundled MCP server.
119
- It will:
407
+ <details>
120
408
 
121
- 1. start the 'openapi-mcp-server' MCP server as a child process
122
- 1. initialize the MCP server
123
- 1. forward the incoming request to the local server
124
- 1. return the server's response to the function caller
125
- 1. shut down the MCP server child process
409
+ <summary><b>Typescript server example</b></summary>
126
410
 
127
411
  ```typescript
128
412
  import { Handler, Context } from "aws-lambda";
413
+ import { stdioServerAdapter } from "@aws/run-mcp-servers-with-aws-lambda";
129
414
 
130
415
  const serverParams = {
131
416
  command: "npx",
132
- args: ["--offline", "openapi-mcp-server", "./weather-alerts-openapi.json"],
417
+ args: [
418
+ "--offline",
419
+ "my-mcp-server-typescript-module",
420
+ "--my-server-command-line-parameter",
421
+ "some_value",
422
+ ],
133
423
  };
134
424
 
135
425
  export const handler: Handler = async (event, context: Context) => {
136
- // Dynamically import ES module into CommonJS Lambda function
137
- const { stdioServerAdapter } = await import(
138
- "@aws/run-mcp-servers-with-aws-lambda"
139
- );
140
-
141
426
  return await stdioServerAdapter(serverParams, event, context);
142
427
  };
143
428
  ```
144
429
 
145
- ### Python client example
430
+ See a full, deployable example [here](examples/servers/weather-alerts/).
431
+
432
+ </details>
433
+
434
+ <details>
146
435
 
147
- This project includes an
148
- [example Python MCP client](examples/chatbots/python/server_clients/lambda_function.py)
149
- that invokes the 'time' MCP server function from above.
150
- The client invokes a Lambda function named "mcp-server-time" with a payload that is compliant
151
- with the MCP protocol and returns the function's response to the caller.
436
+ <summary><b>Python client example</b></summary>
152
437
 
153
438
  ```python
154
439
  from mcp import ClientSession
155
440
  from mcp_lambda import LambdaFunctionParameters, lambda_function_client
156
441
 
157
442
  server_params = LambdaFunctionParameters(
158
- function_name="mcp-server-time",
443
+ function_name="my-mcp-server-function",
159
444
  region_name="us-east-2",
160
445
  )
161
446
 
162
- read, write = await lambda_function_client(server_params)
163
- session = ClientSession(read, write)
164
- await session.initialize()
447
+ async with lambda_function_client(server_params) as (
448
+ read_stream,
449
+ write_stream,
450
+ ):
451
+ async with ClientSession(read_stream, write_stream) as session:
452
+ await session.initialize()
453
+ tool_result = await session.call_tool("echo", {"message": "hello"})
165
454
  ```
166
455
 
167
- ### Typescript client example
456
+ See a full example as part of the sample chatbot [here](examples/chatbots/python/server_clients/lambda_function.py).
168
457
 
169
- This project includes an
170
- [example Typescript MCP client](examples/chatbots/typescript/src/server_clients/lambda_function.ts)
171
- that invokes the 'time' MCP server function from above.
172
- The client invokes a Lambda function named "mcp-server-time" with a payload that is compliant
173
- with the MCP protocol and returns the function's response to the caller.
458
+ </details>
459
+
460
+ <details>
461
+
462
+ <summary><b>Typescript client example</b></summary>
174
463
 
175
464
  ```typescript
176
465
  import {
@@ -180,7 +469,7 @@ import {
180
469
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
181
470
 
182
471
  const serverParams: LambdaFunctionParameters = {
183
- functionName: "mcp-server-time",
472
+ functionName: "my-mcp-server-function",
184
473
  regionName: "us-east-2",
185
474
  };
186
475
 
@@ -200,7 +489,44 @@ const transport = new LambdaFunctionClientTransport(serverParams);
200
489
  await client.connect(transport);
201
490
  ```
202
491
 
203
- ### Deploy and run the examples
492
+ See a full example as part of the sample chatbot [here](examples/chatbots/typescript/src/server_clients/lambda_function.ts).
493
+
494
+ </details>
495
+
496
+ ## Related projects
497
+
498
+ - To write custom MCP servers in Lambda functions,
499
+ see the [MCP Lambda Handler](https://github.com/awslabs/mcp/tree/main/src/mcp-lambda-handler) project.
500
+ - To invoke existing Lambda functions as tools through a stdio MCP server,
501
+ see the [AWS Lambda Tool MCP Server](https://awslabs.github.io/mcp/servers/lambda-tool-mcp-server/) project.
502
+
503
+ ## Considerations
504
+
505
+ - This library currently supports MCP servers and clients written in Python and Typescript.
506
+ Other languages such as Kotlin are not supported.
507
+ - This library only adapts stdio MCP servers for Lambda, not servers written for other protocols such as SSE.
508
+ - This library does not maintain any MCP server state or sessions across Lambda function invocations.
509
+ Only stateless MCP servers are a good fit for using this adapter. For example, MCP servers
510
+ that invoke stateless tools like the [time MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/time)
511
+ or make stateless web requests like the [fetch MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch).
512
+ Stateful MCP servers are not a good fit, because they will lose their state on every request.
513
+ For example, MCP servers that manage data on disk or in memory such as
514
+ the [sqlite MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite),
515
+ the [filesystem MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
516
+ and the [git MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/git).
517
+ - This library does not provide mechanisms for managing any secrets needed by the wrapped
518
+ MCP server. For example, the [GitHub MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/github)
519
+ and the [Brave search MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search)
520
+ require API keys to make requests to third-party APIs.
521
+ You may configure these API keys as
522
+ [encrypted environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html)
523
+ in the Lambda function's configuration. However, note that anyone with access to invoke the Lambda function
524
+ will then have access to use your API key to call the third-party APIs by invoking the function.
525
+ We recommend limiting access to the Lambda function using
526
+ [least-privilege IAM policies](https://docs.aws.amazon.com/lambda/latest/dg/security-iam.html).
527
+ If you use an identity-based authentication mechanism such as OAuth, you could also store and retrieve API keys per user but there are no implementation examples in this repository.
528
+
529
+ ## Deploy and run the examples
204
530
 
205
531
  See the [development guide](DEVELOP.md) for instructions to deploy and run the examples in this repository.
206
532
 
@@ -1,6 +1,6 @@
1
1
  import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
2
- import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./streamable_http_handler.js";
3
- import { RequestHandler } from "./request_handler.js";
2
+ import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./streamableHttpHandler.js";
3
+ import { RequestHandler } from "./requestHandler.js";
4
4
  /**
5
5
  * Handler for API Gateway V1 events (REST APIs)
6
6
  *
@@ -1,4 +1,4 @@
1
- import { StreamableHttpHandler, } from "./streamable_http_handler.js";
1
+ import { StreamableHttpHandler, } from "./streamableHttpHandler.js";
2
2
  /**
3
3
  * Handler for API Gateway V1 events (REST APIs)
4
4
  *
@@ -1,6 +1,6 @@
1
1
  import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
2
- import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./streamable_http_handler.js";
3
- import { RequestHandler } from "./request_handler.js";
2
+ import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./streamableHttpHandler.js";
3
+ import { RequestHandler } from "./requestHandler.js";
4
4
  /**
5
5
  * Handler for API Gateway V2 events (HTTP APIs)
6
6
  *
@@ -1,4 +1,4 @@
1
- import { StreamableHttpHandler, } from "./streamable_http_handler.js";
1
+ import { StreamableHttpHandler, } from "./streamableHttpHandler.js";
2
2
  /**
3
3
  * Handler for API Gateway V2 events (HTTP APIs)
4
4
  *
@@ -1,4 +1,4 @@
1
- export { RequestHandler } from "./request_handler.js";
2
- export { APIGatewayProxyEventHandler } from "./api_gateway_proxy_event_handler.js";
3
- export { APIGatewayProxyEventV2Handler } from "./api_gateway_proxy_event_v2_handler.js";
4
- export { LambdaFunctionURLEventHandler } from "./lambda_function_url_event_handler.js";
1
+ export { RequestHandler } from "./requestHandler.js";
2
+ export { APIGatewayProxyEventHandler } from "./apiGatewayProxyEventHandler.js";
3
+ export { APIGatewayProxyEventV2Handler } from "./apiGatewayProxyEventV2Handler.js";
4
+ export { LambdaFunctionURLEventHandler } from "./lambdaFunctionUrlEventHandler.js";
@@ -1,3 +1,3 @@
1
- export { APIGatewayProxyEventHandler } from "./api_gateway_proxy_event_handler.js";
2
- export { APIGatewayProxyEventV2Handler } from "./api_gateway_proxy_event_v2_handler.js";
3
- export { LambdaFunctionURLEventHandler } from "./lambda_function_url_event_handler.js";
1
+ export { APIGatewayProxyEventHandler } from "./apiGatewayProxyEventHandler.js";
2
+ export { APIGatewayProxyEventV2Handler } from "./apiGatewayProxyEventV2Handler.js";
3
+ export { LambdaFunctionURLEventHandler } from "./lambdaFunctionUrlEventHandler.js";
@@ -1,6 +1,6 @@
1
1
  import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
2
- import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./streamable_http_handler.js";
3
- import { RequestHandler } from "./request_handler.js";
2
+ import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./streamableHttpHandler.js";
3
+ import { RequestHandler } from "./requestHandler.js";
4
4
  /**
5
5
  * Handler for Lambda Function URL requests
6
6
  *
@@ -1,4 +1,4 @@
1
- import { StreamableHttpHandler, } from "./streamable_http_handler.js";
1
+ import { StreamableHttpHandler, } from "./streamableHttpHandler.js";
2
2
  /**
3
3
  * Handler for Lambda Function URL requests
4
4
  *
@@ -1,6 +1,6 @@
1
1
  import { Context } from "aws-lambda";
2
2
  import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
3
- import { RequestHandler } from "./request_handler.js";
3
+ import { RequestHandler } from "./requestHandler.js";
4
4
  /**
5
5
  * Parsed HTTP request data extracted from various Lambda event types
6
6
  */
@@ -1,2 +1,2 @@
1
- export { stdioServerAdapter } from "./stdio_server_adapter.js";
2
- export { StdioServerAdapterRequestHandler } from "./stdio_server_adapter_request_handler.js";
1
+ export { stdioServerAdapter } from "./stdioServerAdapter.js";
2
+ export { StdioServerAdapterRequestHandler } from "./stdioServerAdapterRequestHandler.js";
@@ -1,2 +1,2 @@
1
- export { stdioServerAdapter } from "./stdio_server_adapter.js";
2
- export { StdioServerAdapterRequestHandler } from "./stdio_server_adapter_request_handler.js";
1
+ export { stdioServerAdapter } from "./stdioServerAdapter.js";
2
+ export { StdioServerAdapterRequestHandler } from "./stdioServerAdapterRequestHandler.js";
@@ -1,8 +1,8 @@
1
1
  import { ErrorCode, } from '@modelcontextprotocol/sdk/types.js';
2
- import { stdioServerAdapter } from './stdio_server_adapter.js';
2
+ import { stdioServerAdapter } from './stdioServerAdapter.js';
3
3
  const serverParameters = {
4
4
  command: 'npx',
5
- args: ['tsx', 'test-stdio-server/echo_server.ts'],
5
+ args: ['tsx', 'test-stdio-server/echoServer.ts'],
6
6
  };
7
7
  const mockContext = {
8
8
  callbackWaitsForEmptyEventLoop: true,
@@ -1,7 +1,7 @@
1
1
  import { Context } from 'aws-lambda';
2
2
  import { StdioServerParameters } from '@modelcontextprotocol/sdk/client/stdio.js';
3
3
  import { JSONRPCRequest, JSONRPCResponse, JSONRPCError } from '@modelcontextprotocol/sdk/types.js';
4
- import { RequestHandler } from '../handlers/request_handler.js';
4
+ import { RequestHandler } from '../handlers/requestHandler.js';
5
5
  /**
6
6
  * Generic Request Handler for MCP Stdio Server Adapter
7
7
  *
@@ -1,6 +1,6 @@
1
1
  import { isJSONRPCResponse, isJSONRPCError, ErrorCode, } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { createLogger, format, transports } from 'winston';
3
- import { stdioServerAdapter } from './stdio_server_adapter.js';
3
+ import { stdioServerAdapter } from './stdioServerAdapter.js';
4
4
  const logger = createLogger({
5
5
  level: process.env.LOG_LEVEL?.toLowerCase() || 'info',
6
6
  format: format.simple(),
@@ -1,45 +1,45 @@
1
- import { ErrorCode, } from '@modelcontextprotocol/sdk/types.js';
2
- import { StdioServerAdapterRequestHandler } from './stdio_server_adapter_request_handler.js';
3
- import * as stdioServerAdapter from './stdio_server_adapter.js';
1
+ import { ErrorCode, } from "@modelcontextprotocol/sdk/types.js";
2
+ import { StdioServerAdapterRequestHandler } from "./stdioServerAdapterRequestHandler.js";
3
+ import * as stdioServerAdapter from "./stdioServerAdapter.js";
4
4
  // Mock the stdioServerAdapter function
5
- jest.mock('./stdio_server_adapter.js', () => ({
5
+ jest.mock("./stdioServerAdapter.js", () => ({
6
6
  stdioServerAdapter: jest.fn(),
7
7
  }));
8
8
  const mockStdioServerAdapter = stdioServerAdapter.stdioServerAdapter;
9
9
  const mockServerParams = {
10
- command: 'node',
11
- args: ['test-server.js'],
10
+ command: "node",
11
+ args: ["test-server.js"],
12
12
  };
13
13
  const mockContext = {
14
14
  callbackWaitsForEmptyEventLoop: true,
15
- functionName: 'test-function',
16
- functionVersion: '1',
17
- invokedFunctionArn: 'test-arn',
18
- memoryLimitInMB: '128',
19
- awsRequestId: 'test-id',
20
- logGroupName: 'test-group',
21
- logStreamName: 'test-stream',
15
+ functionName: "test-function",
16
+ functionVersion: "1",
17
+ invokedFunctionArn: "test-arn",
18
+ memoryLimitInMB: "128",
19
+ awsRequestId: "test-id",
20
+ logGroupName: "test-group",
21
+ logStreamName: "test-stream",
22
22
  getRemainingTimeInMillis: () => 1000,
23
23
  done: () => { },
24
24
  fail: () => { },
25
25
  succeed: () => { },
26
26
  };
27
- describe('StdioServerAdapterRequestHandler', () => {
27
+ describe("StdioServerAdapterRequestHandler", () => {
28
28
  let handler;
29
29
  beforeEach(() => {
30
30
  handler = new StdioServerAdapterRequestHandler(mockServerParams);
31
31
  jest.clearAllMocks();
32
32
  });
33
- describe('handleRequest', () => {
34
- test('should return JSONRPCResponse when stdioServerAdapter returns valid response', async () => {
33
+ describe("handleRequest", () => {
34
+ test("should return JSONRPCResponse when stdioServerAdapter returns valid response", async () => {
35
35
  const request = {
36
- jsonrpc: '2.0',
36
+ jsonrpc: "2.0",
37
37
  id: 1,
38
- method: 'test',
39
- params: { arg: 'value' },
38
+ method: "test",
39
+ params: { arg: "value" },
40
40
  };
41
41
  const expectedResponse = {
42
- jsonrpc: '2.0',
42
+ jsonrpc: "2.0",
43
43
  id: 1,
44
44
  result: { success: true },
45
45
  };
@@ -48,17 +48,17 @@ describe('StdioServerAdapterRequestHandler', () => {
48
48
  expect(mockStdioServerAdapter).toHaveBeenCalledWith(mockServerParams, request, mockContext);
49
49
  expect(result).toEqual(expectedResponse);
50
50
  });
51
- test('should return JSONRPCError when stdioServerAdapter returns error', async () => {
51
+ test("should return JSONRPCError when stdioServerAdapter returns error", async () => {
52
52
  const request = {
53
- jsonrpc: '2.0',
53
+ jsonrpc: "2.0",
54
54
  id: 1,
55
- method: 'test',
55
+ method: "test",
56
56
  };
57
57
  const expectedError = {
58
- jsonrpc: '2.0',
58
+ jsonrpc: "2.0",
59
59
  error: {
60
60
  code: ErrorCode.MethodNotFound,
61
- message: 'Method not found',
61
+ message: "Method not found",
62
62
  },
63
63
  id: 1,
64
64
  };
@@ -67,76 +67,76 @@ describe('StdioServerAdapterRequestHandler', () => {
67
67
  expect(mockStdioServerAdapter).toHaveBeenCalledWith(mockServerParams, request, mockContext);
68
68
  expect(result).toEqual(expectedError);
69
69
  });
70
- test('should return internal error when stdioServerAdapter returns unexpected format', async () => {
70
+ test("should return internal error when stdioServerAdapter returns unexpected format", async () => {
71
71
  const request = {
72
- jsonrpc: '2.0',
72
+ jsonrpc: "2.0",
73
73
  id: 1,
74
- method: 'test',
74
+ method: "test",
75
75
  };
76
- const unexpectedResponse = { invalid: 'response' };
76
+ const unexpectedResponse = { invalid: "response" };
77
77
  mockStdioServerAdapter.mockResolvedValue(unexpectedResponse);
78
78
  const result = await handler.handleRequest(request, mockContext);
79
79
  expect(result).toEqual({
80
- jsonrpc: '2.0',
80
+ jsonrpc: "2.0",
81
81
  error: {
82
82
  code: ErrorCode.InternalError,
83
- message: 'Internal error: Unexpected response format from MCP server',
84
- data: 'Expected JSONRPCResponse or JSONRPCError',
83
+ message: "Internal error: Unexpected response format from MCP server",
84
+ data: "Expected JSONRPCResponse or JSONRPCError",
85
85
  },
86
86
  id: 1,
87
87
  });
88
88
  });
89
- test('should return internal error when stdioServerAdapter throws exception', async () => {
89
+ test("should return internal error when stdioServerAdapter throws exception", async () => {
90
90
  const request = {
91
- jsonrpc: '2.0',
91
+ jsonrpc: "2.0",
92
92
  id: 1,
93
- method: 'test',
93
+ method: "test",
94
94
  };
95
- const error = new Error('Connection failed');
95
+ const error = new Error("Connection failed");
96
96
  mockStdioServerAdapter.mockRejectedValue(error);
97
97
  const result = await handler.handleRequest(request, mockContext);
98
98
  expect(result).toEqual({
99
- jsonrpc: '2.0',
99
+ jsonrpc: "2.0",
100
100
  error: {
101
101
  code: ErrorCode.InternalError,
102
- message: 'Internal error',
103
- data: 'Connection failed',
102
+ message: "Internal error",
103
+ data: "Connection failed",
104
104
  },
105
105
  id: 1,
106
106
  });
107
107
  });
108
- test('should handle non-Error exceptions', async () => {
108
+ test("should handle non-Error exceptions", async () => {
109
109
  const request = {
110
- jsonrpc: '2.0',
110
+ jsonrpc: "2.0",
111
111
  id: 1,
112
- method: 'test',
112
+ method: "test",
113
113
  };
114
- mockStdioServerAdapter.mockRejectedValue('String error');
114
+ mockStdioServerAdapter.mockRejectedValue("String error");
115
115
  const result = await handler.handleRequest(request, mockContext);
116
116
  expect(result).toEqual({
117
- jsonrpc: '2.0',
117
+ jsonrpc: "2.0",
118
118
  error: {
119
119
  code: ErrorCode.InternalError,
120
- message: 'Internal error',
121
- data: 'Unknown error',
120
+ message: "Internal error",
121
+ data: "Unknown error",
122
122
  },
123
123
  id: 1,
124
124
  });
125
125
  });
126
- test('should pass server parameters correctly to stdioServerAdapter', async () => {
126
+ test("should pass server parameters correctly to stdioServerAdapter", async () => {
127
127
  const customServerParams = {
128
- command: 'python',
129
- args: ['custom-server.py', '--port', '8080'],
130
- env: { NODE_ENV: 'test' },
128
+ command: "python",
129
+ args: ["custom-server.py", "--port", "8080"],
130
+ env: { hello: "world" },
131
131
  };
132
132
  const customHandler = new StdioServerAdapterRequestHandler(customServerParams);
133
133
  const request = {
134
- jsonrpc: '2.0',
134
+ jsonrpc: "2.0",
135
135
  id: 1,
136
- method: 'test',
136
+ method: "test",
137
137
  };
138
138
  const response = {
139
- jsonrpc: '2.0',
139
+ jsonrpc: "2.0",
140
140
  id: 1,
141
141
  result: {},
142
142
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aws/run-mcp-servers-with-aws-lambda",
3
3
  "description": "Run Model Context Protocol (MCP) servers with AWS Lambda",
4
- "version": "0.2.4",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "author": {
@@ -46,6 +46,7 @@
46
46
  "aws-sdk-client-mock": "^4.1.0",
47
47
  "aws-sdk-client-mock-jest": "^4.1.0",
48
48
  "eslint": "^9.30.1",
49
+ "eslint-plugin-check-file": "^3.3.0",
49
50
  "jest": "^30.0.4",
50
51
  "ts-jest": "^29.4.0",
51
52
  "tsx": "^4.20.3",