@aws/run-mcp-servers-with-aws-lambda 0.3.5 → 0.4.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.
package/README.md CHANGED
@@ -43,11 +43,12 @@ Each Lambda function invocation will:
43
43
  1. Return the server's response to the function caller
44
44
  1. Shut down the MCP server child process
45
45
 
46
- This library supports connecting to Lambda-based MCP servers in three ways:
46
+ This library supports connecting to Lambda-based MCP servers in four ways:
47
47
 
48
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.
49
+ 1. The MCP Streamable HTTP transport, using Amazon Bedrock AgentCore Gateway (currently in Preview). Authenticated using OAuth.
50
+ 1. A custom Streamable HTTP transport with support for SigV4, using a Lambda function URL. Authenticated with AWS IAM.
51
+ 1. A custom Lambda invocation transport, using the Lambda Invoke API directly. Authenticated with AWS IAM.
51
52
 
52
53
  ## Use API Gateway
53
54
 
@@ -155,7 +156,7 @@ from mcp.client.streamable_http import streamablehttp_client
155
156
  # Create OAuth client provider here
156
157
 
157
158
  async with streamablehttp_client(
158
- url="https://abc123.execute-api.us-east-2.amazonaws.com/prod/mcp",
159
+ url="https://abc123.execute-api.us-west-2.amazonaws.com/prod/mcp",
159
160
  auth=oauth_client_provider,
160
161
  ) as (
161
162
  read_stream,
@@ -194,7 +195,172 @@ const client = new Client(
194
195
  // Create OAuth client provider here
195
196
 
196
197
  const transport = new StreamableHTTPClientTransport(
197
- "https://abc123.execute-api.us-east-2.amazonaws.com/prod/mcp",
198
+ "https://abc123.execute-api.us-west-2.amazonaws.com/prod/mcp",
199
+ {
200
+ authProvider: oauthProvider,
201
+ }
202
+ );
203
+ await client.connect(transport);
204
+ ```
205
+
206
+ See a full example as part of the sample chatbot [here](examples/chatbots/typescript/src/server_clients/interactive_oauth.ts).
207
+
208
+ </details>
209
+
210
+ ## Use Bedrock AgentCore Gateway
211
+
212
+ ```mermaid
213
+ flowchart LR
214
+ App["MCP Client"]
215
+ T1["MCP Server<br>(Lambda function)"]
216
+ T2["Bedrock AgentCore Gateway"]
217
+ T3["OAuth Server<br>(Cognito or similar)"]
218
+ App -->|"MCP Streamable<br>HTTP Transport"| T2
219
+ T2 -->|"Invoke"| T1
220
+ T2 -->|"Authorize"| T3
221
+ ```
222
+
223
+ This solution is compatible with most MCP clients that support the streamable HTTP transport.
224
+ MCP servers deployed with this architecture can typically be used with off-the-shelf
225
+ MCP-compatible applications such as Cursor, Cline, Claude Desktop, etc.
226
+
227
+ You can choose your desired OAuth server provider with Bedrock AgentCore Gateway,
228
+ such as Amazon Cognito, Okta, or Auth0.
229
+
230
+ Using Bedrock AgentCore Gateway in front of your stdio-based MCP server requires that
231
+ you retrieve the MCP server's tool schema, and provide it in the
232
+ [AgentCore Gateway Lambda target configuration](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-add-target-lambda.html#gateway-building-lambda-multiple-tools).
233
+ AgentCore Gateway can then advertise the schema to HTTP clients and validate request inputs and outputs.
234
+
235
+ To retrieve and save your stdio-based MCP server's tool schema to a file, run:
236
+
237
+ ```bash
238
+ npx @modelcontextprotocol/inspector --cli --method tools/list <your MCP server command and arguments> > tool-schema.json
239
+
240
+ # For example:
241
+ npx @modelcontextprotocol/inspector --cli --method tools/list uvx mcp-server-time > tool-schema.json
242
+ ```
243
+
244
+ <details>
245
+
246
+ <summary><b>Python server example</b></summary>
247
+
248
+ ```python
249
+ import sys
250
+ from mcp.client.stdio import StdioServerParameters
251
+ from mcp_lambda import BedrockAgentCoreGatewayTargetHandler, StdioServerAdapterRequestHandler
252
+
253
+ server_params = StdioServerParameters(
254
+ command=sys.executable,
255
+ args=[
256
+ "-m",
257
+ "my_mcp_server_python_module",
258
+ "--my-server-command-line-parameter",
259
+ "some_value",
260
+ ],
261
+ )
262
+
263
+
264
+ request_handler = StdioServerAdapterRequestHandler(server_params)
265
+ event_handler = BedrockAgentCoreGatewayTargetHandler(request_handler)
266
+
267
+
268
+ def handler(event, context):
269
+ return event_handler.handle(event, context)
270
+ ```
271
+
272
+ See a full, deployable example [here](examples/servers/book-search/).
273
+
274
+ </details>
275
+
276
+ <details>
277
+
278
+ <summary><b>Typescript server example</b></summary>
279
+
280
+ ```typescript
281
+ import { Handler, Context } from "aws-lambda";
282
+ import {
283
+ BedrockAgentCoreGatewayTargetHandler,
284
+ StdioServerAdapterRequestHandler,
285
+ } from "@aws/run-mcp-servers-with-aws-lambda";
286
+
287
+ const serverParams = {
288
+ command: "npx",
289
+ args: [
290
+ "--offline",
291
+ "my-mcp-server-typescript-module",
292
+ "--my-server-command-line-parameter",
293
+ "some_value",
294
+ ],
295
+ };
296
+
297
+ const requestHandler = new BedrockAgentCoreGatewayTargetHandler(
298
+ new StdioServerAdapterRequestHandler(serverParams)
299
+ );
300
+
301
+ export const handler: Handler = async (
302
+ event: Record<string, unknown>,
303
+ context: Context
304
+ ): Promise<Record<string, unknown>> => {
305
+ return requestHandler.handle(event, context);
306
+ };
307
+ ```
308
+
309
+ See a full, deployable example [here](examples/servers/dictionary/).
310
+
311
+ </details>
312
+
313
+ <details>
314
+
315
+ <summary><b>Python client example</b></summary>
316
+
317
+ ```python
318
+ from mcp import ClientSession
319
+ from mcp.client.streamable_http import streamablehttp_client
320
+
321
+ # Create OAuth client provider here
322
+
323
+ async with streamablehttp_client(
324
+ url="https://abc123.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
325
+ auth=oauth_client_provider,
326
+ ) as (
327
+ read_stream,
328
+ write_stream,
329
+ _,
330
+ ):
331
+ async with ClientSession(read_stream, write_stream) as session:
332
+ await session.initialize()
333
+ tool_result = await session.call_tool("echo", {"message": "hello"})
334
+ ```
335
+
336
+ See a full example as part of the sample chatbot [here](examples/chatbots/python/server_clients/interactive_oauth.py).
337
+
338
+ </details>
339
+
340
+ <details>
341
+
342
+ <summary><b>Typescript client example</b></summary>
343
+
344
+ ```typescript
345
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
346
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
347
+
348
+ const client = new Client(
349
+ {
350
+ name: "my-client",
351
+ version: "0.0.1",
352
+ },
353
+ {
354
+ capabilities: {
355
+ sampling: {},
356
+ },
357
+ }
358
+ );
359
+
360
+ // Create OAuth client provider here
361
+
362
+ const transport = new StreamableHTTPClientTransport(
363
+ "https://abc123.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
198
364
  {
199
365
  authProvider: oauthProvider,
200
366
  }
@@ -307,9 +473,9 @@ from mcp import ClientSession
307
473
  from mcp_lambda.client.streamable_http_sigv4 import streamablehttp_client_with_sigv4
308
474
 
309
475
  async with streamablehttp_client_with_sigv4(
310
- url="https://url-id-12345.lambda-url.us-east-2.on.aws",
476
+ url="https://url-id-12345.lambda-url.us-west-2.on.aws",
311
477
  service="lambda",
312
- region="us-east-2",
478
+ region="us-west-2",
313
479
  ) as (
314
480
  read_stream,
315
481
  write_stream,
@@ -345,10 +511,10 @@ const client = new Client(
345
511
  );
346
512
 
347
513
  const transport = new StreamableHTTPClientWithSigV4Transport(
348
- new URL("https://url-id-12345.lambda-url.us-east-2.on.aws"),
514
+ new URL("https://url-id-12345.lambda-url.us-west-2.on.aws"),
349
515
  {
350
516
  service: "lambda",
351
- region: "us-east-2",
517
+ region: "us-west-2",
352
518
  }
353
519
  );
354
520
  await client.connect(transport);
@@ -441,7 +607,7 @@ from mcp_lambda import LambdaFunctionParameters, lambda_function_client
441
607
 
442
608
  server_params = LambdaFunctionParameters(
443
609
  function_name="my-mcp-server-function",
444
- region_name="us-east-2",
610
+ region_name="us-west-2",
445
611
  )
446
612
 
447
613
  async with lambda_function_client(server_params) as (
@@ -470,7 +636,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
470
636
 
471
637
  const serverParams: LambdaFunctionParameters = {
472
638
  functionName: "my-mcp-server-function",
473
- regionName: "us-east-2",
639
+ regionName: "us-west-2",
474
640
  };
475
641
 
476
642
  const client = new Client(
@@ -0,0 +1,17 @@
1
+ import { Context } from "aws-lambda";
2
+ import { RequestHandler } from "./requestHandler.js";
3
+ /**
4
+ * Handler for Bedrock AgentCore Gateway Lambda targets
5
+ *
6
+ * This handler processes direct Lambda invocations from Bedrock AgentCore Gateway.
7
+ * Bedrock AgentCore Gateway passes tool arguments directly in the event and
8
+ * provides metadata through the Lambda context's client_context.custom properties.
9
+ */
10
+ export declare class BedrockAgentCoreGatewayTargetHandler {
11
+ private requestHandler;
12
+ constructor(requestHandler: RequestHandler);
13
+ /**
14
+ * Handle Lambda invocation from Bedrock AgentCore Gateway
15
+ */
16
+ handle(event: Record<string, unknown>, context: Context): Promise<unknown>;
17
+ }
@@ -0,0 +1,48 @@
1
+ import { isJSONRPCError, } from "@modelcontextprotocol/sdk/types.js";
2
+ /**
3
+ * Handler for Bedrock AgentCore Gateway Lambda targets
4
+ *
5
+ * This handler processes direct Lambda invocations from Bedrock AgentCore Gateway.
6
+ * Bedrock AgentCore Gateway passes tool arguments directly in the event and
7
+ * provides metadata through the Lambda context's client_context.custom properties.
8
+ */
9
+ export class BedrockAgentCoreGatewayTargetHandler {
10
+ requestHandler;
11
+ constructor(requestHandler) {
12
+ this.requestHandler = requestHandler;
13
+ }
14
+ /**
15
+ * Handle Lambda invocation from Bedrock AgentCore Gateway
16
+ */
17
+ async handle(event, context) {
18
+ // Extract tool metadata from context
19
+ const clientContext = context.clientContext;
20
+ const custom = clientContext?.["custom"];
21
+ const gatewayToolName = custom?.["bedrockAgentCoreToolName"];
22
+ if (!gatewayToolName) {
23
+ throw new Error("Missing bedrockAgentCoreToolName in context");
24
+ }
25
+ // Gateway names the tools like <target name>___<tool name>
26
+ const delimiter = "___";
27
+ const delimiterIndex = gatewayToolName.indexOf(delimiter);
28
+ if (delimiterIndex === -1) {
29
+ throw new Error(`Invalid gateway tool name format: ${gatewayToolName}`);
30
+ }
31
+ const toolName = gatewayToolName.substring(delimiterIndex + delimiter.length);
32
+ // Create JSON-RPC request from gateway event
33
+ const jsonRpcRequest = {
34
+ jsonrpc: "2.0",
35
+ id: 1,
36
+ method: "tools/call",
37
+ params: {
38
+ name: toolName,
39
+ arguments: event,
40
+ },
41
+ };
42
+ const result = await this.requestHandler.handleRequest(jsonRpcRequest, context);
43
+ if (isJSONRPCError(result)) {
44
+ throw new Error(result.error.message);
45
+ }
46
+ return result.result;
47
+ }
48
+ }
@@ -1,16 +1,21 @@
1
1
  import { ErrorCode, } from "@modelcontextprotocol/sdk/types.js";
2
- import { APIGatewayProxyEventHandler, APIGatewayProxyEventV2Handler, LambdaFunctionURLEventHandler, } from "./index.js";
2
+ import { APIGatewayProxyEventHandler, APIGatewayProxyEventV2Handler, LambdaFunctionURLEventHandler, BedrockAgentCoreGatewayTargetHandler, } from "./index.js";
3
3
  // Mock RequestHandler implementation for testing
4
4
  class MockRequestHandler {
5
5
  responses = new Map();
6
6
  shouldThrow = false;
7
+ lastCall = null;
7
8
  setResponse(method, response) {
8
9
  this.responses.set(method, response);
9
10
  }
10
11
  setShouldThrow(shouldThrow) {
11
12
  this.shouldThrow = shouldThrow;
12
13
  }
14
+ getLastCall() {
15
+ return this.lastCall;
16
+ }
13
17
  async handleRequest(request, _context) {
18
+ this.lastCall = request;
14
19
  if (this.shouldThrow) {
15
20
  throw new Error("Mock handler error");
16
21
  }
@@ -627,3 +632,80 @@ describe("MCP Streamable HTTP Handlers", () => {
627
632
  });
628
633
  });
629
634
  });
635
+ describe("BedrockAgentCoreGatewayTargetHandler", () => {
636
+ let mockRequestHandler;
637
+ let handler;
638
+ let mockContext;
639
+ beforeEach(() => {
640
+ mockRequestHandler = new MockRequestHandler();
641
+ handler = new BedrockAgentCoreGatewayTargetHandler(mockRequestHandler);
642
+ mockContext = {
643
+ clientContext: {
644
+ custom: {
645
+ bedrockAgentCoreToolName: "target___test_tool",
646
+ },
647
+ },
648
+ };
649
+ });
650
+ it("should handle valid tool invocation", async () => {
651
+ const expectedResponse = {
652
+ jsonrpc: "2.0",
653
+ result: { message: "Tool executed successfully" },
654
+ id: 1,
655
+ };
656
+ mockRequestHandler.setResponse("tools/call", expectedResponse);
657
+ const event = { param1: "value1", param2: "value2" };
658
+ const result = await handler.handle(event, mockContext);
659
+ expect(result).toEqual({ message: "Tool executed successfully" });
660
+ // Verify the extracted tool name is everything after the first delimiter
661
+ const lastCall = mockRequestHandler.getLastCall();
662
+ expect(lastCall?.params?.name).toBe("test_tool");
663
+ });
664
+ it("should throw error when tool name is missing", async () => {
665
+ const contextWithoutTool = { clientContext: { custom: {} } };
666
+ const event = { param1: "value1" };
667
+ await expect(handler.handle(event, contextWithoutTool)).rejects.toThrow("Missing bedrockAgentCoreToolName in context");
668
+ });
669
+ it("should throw error when tool name format is invalid", async () => {
670
+ const contextWithInvalidFormat = {
671
+ clientContext: {
672
+ custom: {
673
+ bedrockAgentCoreToolName: "invalid_format",
674
+ },
675
+ },
676
+ };
677
+ const event = { param1: "value1" };
678
+ await expect(handler.handle(event, contextWithInvalidFormat)).rejects.toThrow("Invalid gateway tool name format: invalid_format");
679
+ });
680
+ it("should handle tool name with multiple delimiters", async () => {
681
+ const contextWithMultipleDelimiters = {
682
+ clientContext: {
683
+ custom: {
684
+ bedrockAgentCoreToolName: "target___test___tool",
685
+ },
686
+ },
687
+ };
688
+ const expectedResponse = {
689
+ jsonrpc: "2.0",
690
+ result: { message: "Tool executed successfully" },
691
+ id: 1,
692
+ };
693
+ mockRequestHandler.setResponse("tools/call", expectedResponse);
694
+ const event = { param1: "value1" };
695
+ const result = await handler.handle(event, contextWithMultipleDelimiters);
696
+ expect(result).toEqual({ message: "Tool executed successfully" });
697
+ // Verify the extracted tool name is everything after the first delimiter
698
+ const lastCall = mockRequestHandler.getLastCall();
699
+ expect(lastCall?.params?.name).toBe("test___tool");
700
+ });
701
+ it("should throw error when request handler returns error", async () => {
702
+ const errorResponse = {
703
+ jsonrpc: "2.0",
704
+ error: { code: -32601, message: "Method not found" },
705
+ id: 1,
706
+ };
707
+ mockRequestHandler.setResponse("tools/call", errorResponse);
708
+ const event = { param1: "value1" };
709
+ await expect(handler.handle(event, mockContext)).rejects.toThrow("Method not found");
710
+ });
711
+ });
@@ -2,3 +2,4 @@ export { RequestHandler } from "./requestHandler.js";
2
2
  export { APIGatewayProxyEventHandler } from "./apiGatewayProxyEventHandler.js";
3
3
  export { APIGatewayProxyEventV2Handler } from "./apiGatewayProxyEventV2Handler.js";
4
4
  export { LambdaFunctionURLEventHandler } from "./lambdaFunctionUrlEventHandler.js";
5
+ export { BedrockAgentCoreGatewayTargetHandler } from "./bedrockAgentCoreGatewayTargetHandler.js";
@@ -1,3 +1,4 @@
1
1
  export { APIGatewayProxyEventHandler } from "./apiGatewayProxyEventHandler.js";
2
2
  export { APIGatewayProxyEventV2Handler } from "./apiGatewayProxyEventV2Handler.js";
3
3
  export { LambdaFunctionURLEventHandler } from "./lambdaFunctionUrlEventHandler.js";
4
+ export { BedrockAgentCoreGatewayTargetHandler } from "./bedrockAgentCoreGatewayTargetHandler.js";
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.3.5",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "author": {
@@ -39,29 +39,29 @@
39
39
  "dist"
40
40
  ],
41
41
  "devDependencies": {
42
- "@eslint/js": "^9.32.0",
42
+ "@eslint/js": "^9.33.0",
43
43
  "@tsconfig/recommended": "^1.0.10",
44
44
  "@types/aws-lambda": "^8.10.152",
45
45
  "@types/jest": "^30.0.0",
46
- "@types/node": "^24.1.0",
46
+ "@types/node": "^24.3.0",
47
47
  "aws-sdk-client-mock": "^4.1.0",
48
48
  "aws-sdk-client-mock-jest": "^4.1.0",
49
- "eslint": "^9.30.1",
49
+ "eslint": "^9.33.0",
50
50
  "eslint-plugin-check-file": "^3.3.0",
51
51
  "jest": "^30.0.5",
52
- "ts-jest": "^29.4.0",
53
- "tsx": "^4.20.3",
52
+ "ts-jest": "^29.4.1",
53
+ "tsx": "^4.20.4",
54
54
  "typescript": "^5.9.2",
55
- "typescript-eslint": "^8.39.0"
55
+ "typescript-eslint": "^8.40.0"
56
56
  },
57
57
  "dependencies": {
58
58
  "@aws-crypto/sha256-js": "^5.2.0",
59
- "@aws-sdk/client-lambda": "^3.859.0",
59
+ "@aws-sdk/client-lambda": "^3.873.0",
60
60
  "@aws-sdk/credential-provider-node": "^3.859.0",
61
61
  "@aws-sdk/protocol-http": "^3.374.0",
62
62
  "@aws-sdk/types": "^3.840.0",
63
- "@modelcontextprotocol/sdk": "^1.17.1",
64
- "@smithy/signature-v4": "^5.1.2",
63
+ "@modelcontextprotocol/sdk": "^1.17.4",
64
+ "@smithy/signature-v4": "^5.1.3",
65
65
  "winston": "^3.17.0"
66
66
  }
67
67
  }