@aws/run-mcp-servers-with-aws-lambda 0.4.0 → 0.4.2
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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Run Model Context Protocol (MCP) servers with AWS Lambda
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/run-mcp-servers-with-aws-lambda/)
|
|
4
|
+
[](https://www.npmjs.com/package/@aws/run-mcp-servers-with-aws-lambda)
|
|
5
|
+
|
|
3
6
|
This project enables you to run [Model Context Protocol](https://modelcontextprotocol.io) stdio-based servers in AWS Lambda functions.
|
|
4
7
|
|
|
5
8
|
Currently, most implementations of MCP servers and clients are entirely local on a single machine.
|
|
@@ -156,7 +159,7 @@ from mcp.client.streamable_http import streamablehttp_client
|
|
|
156
159
|
# Create OAuth client provider here
|
|
157
160
|
|
|
158
161
|
async with streamablehttp_client(
|
|
159
|
-
url="https://abc123.execute-api.us-
|
|
162
|
+
url="https://abc123.execute-api.us-west-2.amazonaws.com/prod/mcp",
|
|
160
163
|
auth=oauth_client_provider,
|
|
161
164
|
) as (
|
|
162
165
|
read_stream,
|
|
@@ -195,7 +198,7 @@ const client = new Client(
|
|
|
195
198
|
// Create OAuth client provider here
|
|
196
199
|
|
|
197
200
|
const transport = new StreamableHTTPClientTransport(
|
|
198
|
-
"https://abc123.execute-api.us-
|
|
201
|
+
"https://abc123.execute-api.us-west-2.amazonaws.com/prod/mcp",
|
|
199
202
|
{
|
|
200
203
|
authProvider: oauthProvider,
|
|
201
204
|
}
|
|
@@ -229,40 +232,16 @@ such as Amazon Cognito, Okta, or Auth0.
|
|
|
229
232
|
|
|
230
233
|
Using Bedrock AgentCore Gateway in front of your stdio-based MCP server requires that
|
|
231
234
|
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-lambda-
|
|
235
|
+
[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
236
|
AgentCore Gateway can then advertise the schema to HTTP clients and validate request inputs and outputs.
|
|
234
237
|
|
|
235
|
-
To retrieve your stdio-based MCP server's tool schema:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
6. In the bottom "History" panel, select the `tools/list` request.
|
|
243
|
-
7. In the Response box, click the Copy clipboard icon in the upper-right corner of the box.
|
|
244
|
-
8. Paste the JSON into a new file. It should begin with `{ tools:[...`
|
|
245
|
-
9. Add a new key "arn" to your JSON file with the value of your Lambda function's ARN.
|
|
246
|
-
|
|
247
|
-
Your Lambda function schema should now look something like this:
|
|
248
|
-
|
|
249
|
-
```json
|
|
250
|
-
{
|
|
251
|
-
"arn": "arn:aws:lambda:us-west-2:123456789012:function:MyFunction",
|
|
252
|
-
"tools": [
|
|
253
|
-
{
|
|
254
|
-
"name": "process_data",
|
|
255
|
-
"description": "Process input data",
|
|
256
|
-
"inputSchema": {
|
|
257
|
-
"type": "object",
|
|
258
|
-
"properties": {
|
|
259
|
-
"input": { "type": "string" }
|
|
260
|
-
},
|
|
261
|
-
"required": ["input"]
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
]
|
|
265
|
-
}
|
|
238
|
+
To retrieve and save your stdio-based MCP server's tool schema to a file, run:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npx @modelcontextprotocol/inspector --cli --method tools/list <your MCP server command and arguments> > tool-schema.json
|
|
242
|
+
|
|
243
|
+
# For example:
|
|
244
|
+
npx @modelcontextprotocol/inspector --cli --method tools/list uvx mcp-server-time > tool-schema.json
|
|
266
245
|
```
|
|
267
246
|
|
|
268
247
|
<details>
|
|
@@ -293,6 +272,8 @@ def handler(event, context):
|
|
|
293
272
|
return event_handler.handle(event, context)
|
|
294
273
|
```
|
|
295
274
|
|
|
275
|
+
See a full, deployable example [here](examples/servers/book-search/).
|
|
276
|
+
|
|
296
277
|
</details>
|
|
297
278
|
|
|
298
279
|
<details>
|
|
@@ -321,13 +302,15 @@ const requestHandler = new BedrockAgentCoreGatewayTargetHandler(
|
|
|
321
302
|
);
|
|
322
303
|
|
|
323
304
|
export const handler: Handler = async (
|
|
324
|
-
event:
|
|
305
|
+
event: Record<string, unknown>,
|
|
325
306
|
context: Context
|
|
326
|
-
): Promise<
|
|
307
|
+
): Promise<Record<string, unknown>> => {
|
|
327
308
|
return requestHandler.handle(event, context);
|
|
328
309
|
};
|
|
329
310
|
```
|
|
330
311
|
|
|
312
|
+
See a full, deployable example [here](examples/servers/dictionary/).
|
|
313
|
+
|
|
331
314
|
</details>
|
|
332
315
|
|
|
333
316
|
<details>
|
|
@@ -493,9 +476,9 @@ from mcp import ClientSession
|
|
|
493
476
|
from mcp_lambda.client.streamable_http_sigv4 import streamablehttp_client_with_sigv4
|
|
494
477
|
|
|
495
478
|
async with streamablehttp_client_with_sigv4(
|
|
496
|
-
url="https://url-id-12345.lambda-url.us-
|
|
479
|
+
url="https://url-id-12345.lambda-url.us-west-2.on.aws",
|
|
497
480
|
service="lambda",
|
|
498
|
-
region="us-
|
|
481
|
+
region="us-west-2",
|
|
499
482
|
) as (
|
|
500
483
|
read_stream,
|
|
501
484
|
write_stream,
|
|
@@ -531,10 +514,10 @@ const client = new Client(
|
|
|
531
514
|
);
|
|
532
515
|
|
|
533
516
|
const transport = new StreamableHTTPClientWithSigV4Transport(
|
|
534
|
-
new URL("https://url-id-12345.lambda-url.us-
|
|
517
|
+
new URL("https://url-id-12345.lambda-url.us-west-2.on.aws"),
|
|
535
518
|
{
|
|
536
519
|
service: "lambda",
|
|
537
|
-
region: "us-
|
|
520
|
+
region: "us-west-2",
|
|
538
521
|
}
|
|
539
522
|
);
|
|
540
523
|
await client.connect(transport);
|
|
@@ -627,7 +610,7 @@ from mcp_lambda import LambdaFunctionParameters, lambda_function_client
|
|
|
627
610
|
|
|
628
611
|
server_params = LambdaFunctionParameters(
|
|
629
612
|
function_name="my-mcp-server-function",
|
|
630
|
-
region_name="us-
|
|
613
|
+
region_name="us-west-2",
|
|
631
614
|
)
|
|
632
615
|
|
|
633
616
|
async with lambda_function_client(server_params) as (
|
|
@@ -656,7 +639,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
656
639
|
|
|
657
640
|
const serverParams: LambdaFunctionParameters = {
|
|
658
641
|
functionName: "my-mcp-server-function",
|
|
659
|
-
regionName: "us-
|
|
642
|
+
regionName: "us-west-2",
|
|
660
643
|
};
|
|
661
644
|
|
|
662
645
|
const client = new Client(
|
|
@@ -13,5 +13,5 @@ export declare class BedrockAgentCoreGatewayTargetHandler {
|
|
|
13
13
|
/**
|
|
14
14
|
* Handle Lambda invocation from Bedrock AgentCore Gateway
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
handle(event: Record<string, unknown>, context: Context): Promise<unknown>;
|
|
17
17
|
}
|
|
@@ -14,12 +14,21 @@ export class BedrockAgentCoreGatewayTargetHandler {
|
|
|
14
14
|
/**
|
|
15
15
|
* Handle Lambda invocation from Bedrock AgentCore Gateway
|
|
16
16
|
*/
|
|
17
|
-
async
|
|
17
|
+
async handle(event, context) {
|
|
18
18
|
// Extract tool metadata from context
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
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");
|
|
22
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);
|
|
23
32
|
// Create JSON-RPC request from gateway event
|
|
24
33
|
const jsonRpcRequest = {
|
|
25
34
|
jsonrpc: "2.0",
|
|
@@ -4,13 +4,18 @@ import { APIGatewayProxyEventHandler, APIGatewayProxyEventV2Handler, LambdaFunct
|
|
|
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
|
}
|
|
@@ -636,8 +641,8 @@ describe("BedrockAgentCoreGatewayTargetHandler", () => {
|
|
|
636
641
|
handler = new BedrockAgentCoreGatewayTargetHandler(mockRequestHandler);
|
|
637
642
|
mockContext = {
|
|
638
643
|
clientContext: {
|
|
639
|
-
|
|
640
|
-
|
|
644
|
+
custom: {
|
|
645
|
+
bedrockAgentCoreToolName: "target___test_tool",
|
|
641
646
|
},
|
|
642
647
|
},
|
|
643
648
|
};
|
|
@@ -650,13 +655,48 @@ describe("BedrockAgentCoreGatewayTargetHandler", () => {
|
|
|
650
655
|
};
|
|
651
656
|
mockRequestHandler.setResponse("tools/call", expectedResponse);
|
|
652
657
|
const event = { param1: "value1", param2: "value2" };
|
|
653
|
-
const result = await handler.
|
|
658
|
+
const result = await handler.handle(event, mockContext);
|
|
654
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");
|
|
655
663
|
});
|
|
656
664
|
it("should throw error when tool name is missing", async () => {
|
|
657
|
-
const contextWithoutTool = { clientContext: {
|
|
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);
|
|
658
694
|
const event = { param1: "value1" };
|
|
659
|
-
await
|
|
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");
|
|
660
700
|
});
|
|
661
701
|
it("should throw error when request handler returns error", async () => {
|
|
662
702
|
const errorResponse = {
|
|
@@ -666,6 +706,6 @@ describe("BedrockAgentCoreGatewayTargetHandler", () => {
|
|
|
666
706
|
};
|
|
667
707
|
mockRequestHandler.setResponse("tools/call", errorResponse);
|
|
668
708
|
const event = { param1: "value1" };
|
|
669
|
-
await expect(handler.
|
|
709
|
+
await expect(handler.handle(event, mockContext)).rejects.toThrow("Method not found");
|
|
670
710
|
});
|
|
671
711
|
});
|
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.4.
|
|
4
|
+
"version": "0.4.2",
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
53
|
-
"tsx": "^4.20.
|
|
52
|
+
"ts-jest": "^29.4.1",
|
|
53
|
+
"tsx": "^4.20.4",
|
|
54
54
|
"typescript": "^5.9.2",
|
|
55
|
-
"typescript-eslint": "^8.
|
|
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.
|
|
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.
|
|
64
|
-
"@smithy/signature-v4": "^5.1.
|
|
63
|
+
"@modelcontextprotocol/sdk": "^1.17.4",
|
|
64
|
+
"@smithy/signature-v4": "^5.1.3",
|
|
65
65
|
"winston": "^3.17.0"
|
|
66
66
|
}
|
|
67
67
|
}
|