@aws/run-mcp-servers-with-aws-lambda 0.3.4 → 0.4.0
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 +193 -7
- package/dist/handlers/bedrockAgentCoreGatewayTargetHandler.d.ts +17 -0
- package/dist/handlers/bedrockAgentCoreGatewayTargetHandler.js +39 -0
- package/dist/handlers/handlers.test.js +43 -1
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,13 +43,14 @@ 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
|
|
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
|
-
|
|
50
|
-
|
|
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
|
|
|
54
55
|
```mermaid
|
|
55
56
|
flowchart LR
|
|
@@ -206,7 +207,192 @@ See a full example as part of the sample chatbot [here](examples/chatbots/typesc
|
|
|
206
207
|
|
|
207
208
|
</details>
|
|
208
209
|
|
|
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-lambda-schema).
|
|
233
|
+
AgentCore Gateway can then advertise the schema to HTTP clients and validate request inputs and outputs.
|
|
234
|
+
|
|
235
|
+
To retrieve your stdio-based MCP server's tool schema:
|
|
236
|
+
|
|
237
|
+
1. Start the MCP inspector: `npx @modelcontextprotocol/inspector`
|
|
238
|
+
2. Open the inspector tool in your web browser using the localhost link shown.
|
|
239
|
+
3. In the left-hand column, select STDIO transport type, and fill in your server's command and arguments. For example, command `uvx` and arguments `mcp-server-time`.
|
|
240
|
+
4. Click Connect.
|
|
241
|
+
5. In the main panel, select Tools and click "List Tools".
|
|
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
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
<details>
|
|
269
|
+
|
|
270
|
+
<summary><b>Python server example</b></summary>
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
import sys
|
|
274
|
+
from mcp.client.stdio import StdioServerParameters
|
|
275
|
+
from mcp_lambda import BedrockAgentCoreGatewayTargetHandler, StdioServerAdapterRequestHandler
|
|
276
|
+
|
|
277
|
+
server_params = StdioServerParameters(
|
|
278
|
+
command=sys.executable,
|
|
279
|
+
args=[
|
|
280
|
+
"-m",
|
|
281
|
+
"my_mcp_server_python_module",
|
|
282
|
+
"--my-server-command-line-parameter",
|
|
283
|
+
"some_value",
|
|
284
|
+
],
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
request_handler = StdioServerAdapterRequestHandler(server_params)
|
|
289
|
+
event_handler = BedrockAgentCoreGatewayTargetHandler(request_handler)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def handler(event, context):
|
|
293
|
+
return event_handler.handle(event, context)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
</details>
|
|
297
|
+
|
|
298
|
+
<details>
|
|
299
|
+
|
|
300
|
+
<summary><b>Typescript server example</b></summary>
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { Handler, Context } from "aws-lambda";
|
|
304
|
+
import {
|
|
305
|
+
BedrockAgentCoreGatewayTargetHandler,
|
|
306
|
+
StdioServerAdapterRequestHandler,
|
|
307
|
+
} from "@aws/run-mcp-servers-with-aws-lambda";
|
|
308
|
+
|
|
309
|
+
const serverParams = {
|
|
310
|
+
command: "npx",
|
|
311
|
+
args: [
|
|
312
|
+
"--offline",
|
|
313
|
+
"my-mcp-server-typescript-module",
|
|
314
|
+
"--my-server-command-line-parameter",
|
|
315
|
+
"some_value",
|
|
316
|
+
],
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const requestHandler = new BedrockAgentCoreGatewayTargetHandler(
|
|
320
|
+
new StdioServerAdapterRequestHandler(serverParams)
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
export const handler: Handler = async (
|
|
324
|
+
event: event: Record<string, unknown>,
|
|
325
|
+
context: Context
|
|
326
|
+
): Promise<event: Record<string, unknown>> => {
|
|
327
|
+
return requestHandler.handle(event, context);
|
|
328
|
+
};
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
</details>
|
|
332
|
+
|
|
333
|
+
<details>
|
|
334
|
+
|
|
335
|
+
<summary><b>Python client example</b></summary>
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from mcp import ClientSession
|
|
339
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
340
|
+
|
|
341
|
+
# Create OAuth client provider here
|
|
342
|
+
|
|
343
|
+
async with streamablehttp_client(
|
|
344
|
+
url="https://abc123.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
|
|
345
|
+
auth=oauth_client_provider,
|
|
346
|
+
) as (
|
|
347
|
+
read_stream,
|
|
348
|
+
write_stream,
|
|
349
|
+
_,
|
|
350
|
+
):
|
|
351
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
352
|
+
await session.initialize()
|
|
353
|
+
tool_result = await session.call_tool("echo", {"message": "hello"})
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
See a full example as part of the sample chatbot [here](examples/chatbots/python/server_clients/interactive_oauth.py).
|
|
357
|
+
|
|
358
|
+
</details>
|
|
359
|
+
|
|
360
|
+
<details>
|
|
361
|
+
|
|
362
|
+
<summary><b>Typescript client example</b></summary>
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
366
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
367
|
+
|
|
368
|
+
const client = new Client(
|
|
369
|
+
{
|
|
370
|
+
name: "my-client",
|
|
371
|
+
version: "0.0.1",
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
capabilities: {
|
|
375
|
+
sampling: {},
|
|
376
|
+
},
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
// Create OAuth client provider here
|
|
381
|
+
|
|
382
|
+
const transport = new StreamableHTTPClientTransport(
|
|
383
|
+
"https://abc123.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
|
|
384
|
+
{
|
|
385
|
+
authProvider: oauthProvider,
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
await client.connect(transport);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
See a full example as part of the sample chatbot [here](examples/chatbots/typescript/src/server_clients/interactive_oauth.ts).
|
|
392
|
+
|
|
393
|
+
</details>
|
|
394
|
+
|
|
395
|
+
## Use a Lambda function URL
|
|
210
396
|
|
|
211
397
|
```mermaid
|
|
212
398
|
flowchart LR
|
|
@@ -358,7 +544,7 @@ See a full example as part of the sample chatbot [here](examples/chatbots/typesc
|
|
|
358
544
|
|
|
359
545
|
</details>
|
|
360
546
|
|
|
361
|
-
##
|
|
547
|
+
## Use the Lambda Invoke API
|
|
362
548
|
|
|
363
549
|
```mermaid
|
|
364
550
|
flowchart LR
|
|
@@ -506,7 +692,7 @@ See a full example as part of the sample chatbot [here](examples/chatbots/typesc
|
|
|
506
692
|
Other languages such as Kotlin are not supported.
|
|
507
693
|
- This library only adapts stdio MCP servers for Lambda, not servers written for other protocols such as SSE.
|
|
508
694
|
- 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
|
|
695
|
+
Only stateless MCP servers are a good fit for using this library. For example, MCP servers
|
|
510
696
|
that invoke stateless tools like the [time MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/time)
|
|
511
697
|
or make stateless web requests like the [fetch MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch).
|
|
512
698
|
Stateful MCP servers are not a good fit, because they will lose their state on every request.
|
|
@@ -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
|
+
handleEvent(event: Record<string, unknown>, context: Context): Promise<unknown>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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 handleEvent(event, context) {
|
|
18
|
+
// Extract tool metadata from context
|
|
19
|
+
const toolName = context.clientContext?.Custom?.["bedrockagentcoreToolName"];
|
|
20
|
+
if (!toolName) {
|
|
21
|
+
throw new Error("Missing bedrockagentcoreToolName in context");
|
|
22
|
+
}
|
|
23
|
+
// Create JSON-RPC request from gateway event
|
|
24
|
+
const jsonRpcRequest = {
|
|
25
|
+
jsonrpc: "2.0",
|
|
26
|
+
id: 1,
|
|
27
|
+
method: "tools/call",
|
|
28
|
+
params: {
|
|
29
|
+
name: toolName,
|
|
30
|
+
arguments: event,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const result = await this.requestHandler.handleRequest(jsonRpcRequest, context);
|
|
34
|
+
if (isJSONRPCError(result)) {
|
|
35
|
+
throw new Error(result.error.message);
|
|
36
|
+
}
|
|
37
|
+
return result.result;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
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();
|
|
@@ -627,3 +627,45 @@ describe("MCP Streamable HTTP Handlers", () => {
|
|
|
627
627
|
});
|
|
628
628
|
});
|
|
629
629
|
});
|
|
630
|
+
describe("BedrockAgentCoreGatewayTargetHandler", () => {
|
|
631
|
+
let mockRequestHandler;
|
|
632
|
+
let handler;
|
|
633
|
+
let mockContext;
|
|
634
|
+
beforeEach(() => {
|
|
635
|
+
mockRequestHandler = new MockRequestHandler();
|
|
636
|
+
handler = new BedrockAgentCoreGatewayTargetHandler(mockRequestHandler);
|
|
637
|
+
mockContext = {
|
|
638
|
+
clientContext: {
|
|
639
|
+
Custom: {
|
|
640
|
+
bedrockagentcoreToolName: "test_tool",
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
};
|
|
644
|
+
});
|
|
645
|
+
it("should handle valid tool invocation", async () => {
|
|
646
|
+
const expectedResponse = {
|
|
647
|
+
jsonrpc: "2.0",
|
|
648
|
+
result: { message: "Tool executed successfully" },
|
|
649
|
+
id: 1,
|
|
650
|
+
};
|
|
651
|
+
mockRequestHandler.setResponse("tools/call", expectedResponse);
|
|
652
|
+
const event = { param1: "value1", param2: "value2" };
|
|
653
|
+
const result = await handler.handleEvent(event, mockContext);
|
|
654
|
+
expect(result).toEqual({ message: "Tool executed successfully" });
|
|
655
|
+
});
|
|
656
|
+
it("should throw error when tool name is missing", async () => {
|
|
657
|
+
const contextWithoutTool = { clientContext: { Custom: {} } };
|
|
658
|
+
const event = { param1: "value1" };
|
|
659
|
+
await expect(handler.handleEvent(event, contextWithoutTool)).rejects.toThrow("Missing bedrockagentcoreToolName in context");
|
|
660
|
+
});
|
|
661
|
+
it("should throw error when request handler returns error", async () => {
|
|
662
|
+
const errorResponse = {
|
|
663
|
+
jsonrpc: "2.0",
|
|
664
|
+
error: { code: -32601, message: "Method not found" },
|
|
665
|
+
id: 1,
|
|
666
|
+
};
|
|
667
|
+
mockRequestHandler.setResponse("tools/call", errorResponse);
|
|
668
|
+
const event = { param1: "value1" };
|
|
669
|
+
await expect(handler.handleEvent(event, mockContext)).rejects.toThrow("Method not found");
|
|
670
|
+
});
|
|
671
|
+
});
|
package/dist/handlers/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/handlers/index.js
CHANGED
|
@@ -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";
|