@aws/run-mcp-servers-with-aws-lambda 0.2.3 → 0.3.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/dist/client/index.d.ts +2 -27
- package/dist/client/index.js +4 -51
- package/dist/client/lambdaFunction.d.ts +27 -0
- package/dist/client/lambdaFunction.js +51 -0
- package/dist/client/streamableHttpWithSigV4.d.ts +88 -0
- package/dist/client/streamableHttpWithSigV4.js +116 -0
- package/dist/client/streamableHttpWithSigV4.test.js +442 -0
- package/dist/handlers/{api_gateway_proxy_event_handler.d.ts → apiGatewayProxyEventHandler.d.ts} +2 -2
- package/dist/handlers/{api_gateway_proxy_event_handler.js → apiGatewayProxyEventHandler.js} +1 -1
- package/dist/handlers/{api_gateway_proxy_event_v2_handler.d.ts → apiGatewayProxyEventV2Handler.d.ts} +2 -2
- package/dist/handlers/{api_gateway_proxy_event_v2_handler.js → apiGatewayProxyEventV2Handler.js} +1 -1
- package/dist/handlers/index.d.ts +4 -4
- package/dist/handlers/index.js +3 -3
- package/dist/handlers/{lambda_function_url_event_handler.d.ts → lambdaFunctionUrlEventHandler.d.ts} +2 -2
- package/dist/handlers/{lambda_function_url_event_handler.js → lambdaFunctionUrlEventHandler.js} +1 -1
- package/dist/handlers/{streamable_http_handler.d.ts → streamableHttpHandler.d.ts} +1 -1
- package/dist/server-adapter/index.d.ts +2 -2
- package/dist/server-adapter/index.js +2 -2
- package/dist/server-adapter/{stdio_server_adapter.test.js → stdioServerAdapter.test.js} +2 -2
- package/dist/server-adapter/{stdio_server_adapter_request_handler.d.ts → stdioServerAdapterRequestHandler.d.ts} +1 -1
- package/dist/server-adapter/{stdio_server_adapter_request_handler.js → stdioServerAdapterRequestHandler.js} +1 -1
- package/dist/server-adapter/stdioServerAdapterRequestHandler.test.d.ts +1 -0
- package/dist/server-adapter/{stdio_server_adapter_request_handler.test.js → stdioServerAdapterRequestHandler.test.js} +53 -53
- package/package.json +7 -1
- /package/dist/client/{index.test.d.ts → lambdaFunction.test.d.ts} +0 -0
- /package/dist/client/{index.test.js → lambdaFunction.test.js} +0 -0
- /package/dist/{handlers/request_handler.js → client/streamableHttpWithSigV4.test.d.ts} +0 -0
- /package/dist/handlers/{request_handler.d.ts → requestHandler.d.ts} +0 -0
- /package/dist/{server-adapter/stdio_server_adapter.test.d.ts → handlers/requestHandler.js} +0 -0
- /package/dist/handlers/{streamable_http_handler.js → streamableHttpHandler.js} +0 -0
- /package/dist/server-adapter/{stdio_server_adapter.d.ts → stdioServerAdapter.d.ts} +0 -0
- /package/dist/server-adapter/{stdio_server_adapter.js → stdioServerAdapter.js} +0 -0
- /package/dist/server-adapter/{stdio_server_adapter_request_handler.test.d.ts → stdioServerAdapter.test.d.ts} +0 -0
package/dist/client/index.d.ts
CHANGED
|
@@ -1,27 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export type LambdaFunctionParameters = {
|
|
4
|
-
/**
|
|
5
|
-
* The name or ARN of the Lambda function, version, or alias.
|
|
6
|
-
*/
|
|
7
|
-
functionName: string;
|
|
8
|
-
/**
|
|
9
|
-
* The AWS region of the Lambda function.
|
|
10
|
-
*/
|
|
11
|
-
regionName?: string;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Client transport for Lambda functions:
|
|
15
|
-
* this will connect to a server by calling the Lambda Invoke API.
|
|
16
|
-
*/
|
|
17
|
-
export declare class LambdaFunctionClientTransport implements Transport {
|
|
18
|
-
private _serverParams;
|
|
19
|
-
private _lambdaClient;
|
|
20
|
-
onclose?: () => void;
|
|
21
|
-
onerror?: (error: Error) => void;
|
|
22
|
-
onmessage?: (message: JSONRPCMessage) => void;
|
|
23
|
-
constructor(server: LambdaFunctionParameters);
|
|
24
|
-
start(): Promise<void>;
|
|
25
|
-
close(): Promise<void>;
|
|
26
|
-
send(message: JSONRPCMessage): Promise<void>;
|
|
27
|
-
}
|
|
1
|
+
export * from "./lambdaFunction.js";
|
|
2
|
+
export * from "./streamableHttpWithSigV4.js";
|
package/dist/client/index.js
CHANGED
|
@@ -1,51 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
*/
|
|
6
|
-
export class LambdaFunctionClientTransport {
|
|
7
|
-
_serverParams;
|
|
8
|
-
_lambdaClient;
|
|
9
|
-
onclose;
|
|
10
|
-
onerror;
|
|
11
|
-
onmessage;
|
|
12
|
-
constructor(server) {
|
|
13
|
-
this._serverParams = server;
|
|
14
|
-
this._lambdaClient = new LambdaClient({
|
|
15
|
-
region: this._serverParams.regionName,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
async start() {
|
|
19
|
-
// no-op
|
|
20
|
-
}
|
|
21
|
-
async close() {
|
|
22
|
-
// no-op
|
|
23
|
-
}
|
|
24
|
-
async send(message) {
|
|
25
|
-
try {
|
|
26
|
-
const invokeCommand = new InvokeCommand({
|
|
27
|
-
FunctionName: this._serverParams.functionName,
|
|
28
|
-
InvocationType: "RequestResponse",
|
|
29
|
-
Payload: JSON.stringify(message),
|
|
30
|
-
});
|
|
31
|
-
const invokeCommandOutput = await this._lambdaClient.send(invokeCommand);
|
|
32
|
-
if (invokeCommandOutput.Payload) {
|
|
33
|
-
const responseMessage = Buffer.from(invokeCommandOutput.Payload).toString("utf-8");
|
|
34
|
-
if (invokeCommandOutput.FunctionError) {
|
|
35
|
-
throw new Error(`${invokeCommandOutput.FunctionError} ${responseMessage}`);
|
|
36
|
-
}
|
|
37
|
-
if (responseMessage === "{}") {
|
|
38
|
-
// Assume we sent a notification and do not expect a response
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
this.onmessage?.(JSON.parse(responseMessage));
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
throw new Error("No payload returned from Lambda function");
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
this.onerror?.(error);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
1
|
+
// Export Lambda function client transport
|
|
2
|
+
export * from "./lambdaFunction.js";
|
|
3
|
+
// Export SigV4 HTTP client transport
|
|
4
|
+
export * from "./streamableHttpWithSigV4.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
+
import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
export type LambdaFunctionParameters = {
|
|
4
|
+
/**
|
|
5
|
+
* The name or ARN of the Lambda function, version, or alias.
|
|
6
|
+
*/
|
|
7
|
+
functionName: string;
|
|
8
|
+
/**
|
|
9
|
+
* The AWS region of the Lambda function.
|
|
10
|
+
*/
|
|
11
|
+
regionName?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Client transport for Lambda functions:
|
|
15
|
+
* this will connect to a server by calling the Lambda Invoke API.
|
|
16
|
+
*/
|
|
17
|
+
export declare class LambdaFunctionClientTransport implements Transport {
|
|
18
|
+
private _serverParams;
|
|
19
|
+
private _lambdaClient;
|
|
20
|
+
onclose?: () => void;
|
|
21
|
+
onerror?: (error: Error) => void;
|
|
22
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
23
|
+
constructor(server: LambdaFunctionParameters);
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
|
|
2
|
+
/**
|
|
3
|
+
* Client transport for Lambda functions:
|
|
4
|
+
* this will connect to a server by calling the Lambda Invoke API.
|
|
5
|
+
*/
|
|
6
|
+
export class LambdaFunctionClientTransport {
|
|
7
|
+
_serverParams;
|
|
8
|
+
_lambdaClient;
|
|
9
|
+
onclose;
|
|
10
|
+
onerror;
|
|
11
|
+
onmessage;
|
|
12
|
+
constructor(server) {
|
|
13
|
+
this._serverParams = server;
|
|
14
|
+
this._lambdaClient = new LambdaClient({
|
|
15
|
+
region: this._serverParams.regionName,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async start() {
|
|
19
|
+
// no-op
|
|
20
|
+
}
|
|
21
|
+
async close() {
|
|
22
|
+
// no-op
|
|
23
|
+
}
|
|
24
|
+
async send(message) {
|
|
25
|
+
try {
|
|
26
|
+
const invokeCommand = new InvokeCommand({
|
|
27
|
+
FunctionName: this._serverParams.functionName,
|
|
28
|
+
InvocationType: "RequestResponse",
|
|
29
|
+
Payload: JSON.stringify(message),
|
|
30
|
+
});
|
|
31
|
+
const invokeCommandOutput = await this._lambdaClient.send(invokeCommand);
|
|
32
|
+
if (invokeCommandOutput.Payload) {
|
|
33
|
+
const responseMessage = Buffer.from(invokeCommandOutput.Payload).toString("utf-8");
|
|
34
|
+
if (invokeCommandOutput.FunctionError) {
|
|
35
|
+
throw new Error(`${invokeCommandOutput.FunctionError} ${responseMessage}`);
|
|
36
|
+
}
|
|
37
|
+
if (responseMessage === "{}") {
|
|
38
|
+
// Assume we sent a notification and do not expect a response
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.onmessage?.(JSON.parse(responseMessage));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
throw new Error("No payload returned from Lambda function");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
this.onerror?.(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { StreamableHTTPClientTransport, StreamableHTTPClientTransportOptions } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
2
|
+
import { FetchLike } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
3
|
+
import { AwsCredentialIdentity, Provider } from "@aws-sdk/types";
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for SigV4FetchLike
|
|
6
|
+
*/
|
|
7
|
+
export interface SigV4FetchLikeOptions {
|
|
8
|
+
/**
|
|
9
|
+
* AWS credentials (provider), if not provided, default provider will be used
|
|
10
|
+
*/
|
|
11
|
+
credentials?: AwsCredentialIdentity | Provider<AwsCredentialIdentity>;
|
|
12
|
+
/**
|
|
13
|
+
* AWS region for signing requests
|
|
14
|
+
*/
|
|
15
|
+
region: string;
|
|
16
|
+
/**
|
|
17
|
+
* AWS service name for signing requests (e.g., 'lambda', 'execute-api')
|
|
18
|
+
*/
|
|
19
|
+
service: string;
|
|
20
|
+
/**
|
|
21
|
+
* Base fetch implementation to use for actual HTTP requests
|
|
22
|
+
* If not provided, the global fetch will be used
|
|
23
|
+
*/
|
|
24
|
+
baseFetch?: FetchLike;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A FetchLike implementation that signs HTTP requests using AWS Signature Version 4.
|
|
28
|
+
*
|
|
29
|
+
* This class can be passed to any transport that accepts a FetchLike parameter.
|
|
30
|
+
* It wraps a base fetch implementation and automatically signs all requests with AWS SigV4.
|
|
31
|
+
*/
|
|
32
|
+
export declare class SigV4FetchLike {
|
|
33
|
+
private _signer;
|
|
34
|
+
private _baseFetch;
|
|
35
|
+
constructor(options: SigV4FetchLikeOptions);
|
|
36
|
+
/**
|
|
37
|
+
* Fetch implementation that signs requests with AWS SigV4
|
|
38
|
+
*/
|
|
39
|
+
fetch(url: string | URL, init?: RequestInit): Promise<Response>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates a FetchLike function that signs HTTP requests using AWS Signature Version 4.
|
|
43
|
+
*
|
|
44
|
+
* This function can be passed to any transport that accepts a FetchLike parameter,
|
|
45
|
+
* such as StreamableHTTPClientTransport.
|
|
46
|
+
*
|
|
47
|
+
* @param options Configuration options for AWS SigV4 signing
|
|
48
|
+
* @returns A FetchLike function that automatically signs requests
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const sigV4Fetch = createSigV4Fetch({
|
|
53
|
+
* region: 'us-east-1',
|
|
54
|
+
* service: 'lambda'
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* const transport = new StreamableHTTPClientTransport(url, {
|
|
58
|
+
* fetch: sigV4Fetch
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function createSigV4Fetch(options: SigV4FetchLikeOptions): FetchLike;
|
|
63
|
+
/**
|
|
64
|
+
* Extended options for StreamableHTTPClientWithSigV4Transport
|
|
65
|
+
*/
|
|
66
|
+
export interface StreamableHTTPClientWithSigV4TransportOptions extends StreamableHTTPClientTransportOptions {
|
|
67
|
+
/**
|
|
68
|
+
* AWS credentials (provider), if not provided, default provider will be used
|
|
69
|
+
*/
|
|
70
|
+
credentials?: AwsCredentialIdentity | Provider<AwsCredentialIdentity>;
|
|
71
|
+
/**
|
|
72
|
+
* AWS region for signing requests
|
|
73
|
+
*/
|
|
74
|
+
region: string;
|
|
75
|
+
/**
|
|
76
|
+
* AWS service name for signing requests (e.g., 'lambda', 'execute-api')
|
|
77
|
+
*/
|
|
78
|
+
service: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Streamable HTTP client transport with AWS SigV4 signing support.
|
|
82
|
+
*
|
|
83
|
+
* This transport enables communication with MCP servers that authenticate using AWS IAM,
|
|
84
|
+
* such as servers behind a Lambda function URL or API Gateway.
|
|
85
|
+
*/
|
|
86
|
+
export declare class StreamableHTTPClientWithSigV4Transport extends StreamableHTTPClientTransport {
|
|
87
|
+
constructor(url: URL, options: StreamableHTTPClientWithSigV4TransportOptions);
|
|
88
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { StreamableHTTPClientTransport, } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
2
|
+
import { SignatureV4 } from "@smithy/signature-v4";
|
|
3
|
+
import { defaultProvider } from "@aws-sdk/credential-provider-node";
|
|
4
|
+
import { Sha256 } from "@aws-crypto/sha256-js";
|
|
5
|
+
import { HttpRequest } from "@aws-sdk/protocol-http";
|
|
6
|
+
/**
|
|
7
|
+
* A FetchLike implementation that signs HTTP requests using AWS Signature Version 4.
|
|
8
|
+
*
|
|
9
|
+
* This class can be passed to any transport that accepts a FetchLike parameter.
|
|
10
|
+
* It wraps a base fetch implementation and automatically signs all requests with AWS SigV4.
|
|
11
|
+
*/
|
|
12
|
+
export class SigV4FetchLike {
|
|
13
|
+
_signer;
|
|
14
|
+
_baseFetch;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this._baseFetch = options.baseFetch ?? fetch;
|
|
17
|
+
this._signer = new SignatureV4({
|
|
18
|
+
service: options.service,
|
|
19
|
+
region: options.region,
|
|
20
|
+
credentials: options.credentials ?? defaultProvider(),
|
|
21
|
+
sha256: Sha256,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fetch implementation that signs requests with AWS SigV4
|
|
26
|
+
*/
|
|
27
|
+
async fetch(url, init) {
|
|
28
|
+
const urlObj = typeof url === "string" ? new URL(url) : url;
|
|
29
|
+
const requestInit = init || {};
|
|
30
|
+
// Convert fetch RequestInit to HttpRequest for signing
|
|
31
|
+
const headers = {
|
|
32
|
+
host: urlObj.hostname,
|
|
33
|
+
};
|
|
34
|
+
if (requestInit.headers) {
|
|
35
|
+
if (requestInit.headers instanceof Headers) {
|
|
36
|
+
requestInit.headers.forEach((value, key) => {
|
|
37
|
+
headers[key.toLowerCase()] = value;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else if (Array.isArray(requestInit.headers)) {
|
|
41
|
+
requestInit.headers.forEach(([key, value]) => {
|
|
42
|
+
headers[key.toLowerCase()] = value;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
Object.entries(requestInit.headers).forEach(([key, value]) => {
|
|
47
|
+
headers[key.toLowerCase()] = value;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const unsignedRequest = new HttpRequest({
|
|
52
|
+
method: requestInit.method || "GET",
|
|
53
|
+
protocol: urlObj.protocol,
|
|
54
|
+
hostname: urlObj.hostname,
|
|
55
|
+
port: urlObj.port ? parseInt(urlObj.port) : undefined,
|
|
56
|
+
path: urlObj.pathname + urlObj.search,
|
|
57
|
+
headers,
|
|
58
|
+
body: requestInit.body,
|
|
59
|
+
});
|
|
60
|
+
// Sign the request
|
|
61
|
+
const signedRequest = await this._signer.sign(unsignedRequest);
|
|
62
|
+
// Convert signed request back to fetch format
|
|
63
|
+
const signedInit = {
|
|
64
|
+
...requestInit,
|
|
65
|
+
method: signedRequest.method,
|
|
66
|
+
headers: signedRequest.headers,
|
|
67
|
+
body: signedRequest.body,
|
|
68
|
+
};
|
|
69
|
+
return this._baseFetch(urlObj, signedInit);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a FetchLike function that signs HTTP requests using AWS Signature Version 4.
|
|
74
|
+
*
|
|
75
|
+
* This function can be passed to any transport that accepts a FetchLike parameter,
|
|
76
|
+
* such as StreamableHTTPClientTransport.
|
|
77
|
+
*
|
|
78
|
+
* @param options Configuration options for AWS SigV4 signing
|
|
79
|
+
* @returns A FetchLike function that automatically signs requests
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const sigV4Fetch = createSigV4Fetch({
|
|
84
|
+
* region: 'us-east-1',
|
|
85
|
+
* service: 'lambda'
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* const transport = new StreamableHTTPClientTransport(url, {
|
|
89
|
+
* fetch: sigV4Fetch
|
|
90
|
+
* });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function createSigV4Fetch(options) {
|
|
94
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
95
|
+
return sigV4Fetch.fetch.bind(sigV4Fetch);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Streamable HTTP client transport with AWS SigV4 signing support.
|
|
99
|
+
*
|
|
100
|
+
* This transport enables communication with MCP servers that authenticate using AWS IAM,
|
|
101
|
+
* such as servers behind a Lambda function URL or API Gateway.
|
|
102
|
+
*/
|
|
103
|
+
export class StreamableHTTPClientWithSigV4Transport extends StreamableHTTPClientTransport {
|
|
104
|
+
constructor(url, options) {
|
|
105
|
+
const sigV4Fetch = createSigV4Fetch({
|
|
106
|
+
credentials: options.credentials,
|
|
107
|
+
region: options.region,
|
|
108
|
+
service: options.service,
|
|
109
|
+
baseFetch: options.fetch,
|
|
110
|
+
});
|
|
111
|
+
super(url, {
|
|
112
|
+
...options,
|
|
113
|
+
fetch: sigV4Fetch,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { SignatureV4 } from "@smithy/signature-v4";
|
|
2
|
+
import { defaultProvider } from "@aws-sdk/credential-provider-node";
|
|
3
|
+
import { HttpRequest } from "@aws-sdk/protocol-http";
|
|
4
|
+
import { StreamableHTTPClientWithSigV4Transport, SigV4FetchLike, createSigV4Fetch, } from "./streamableHttpWithSigV4.js";
|
|
5
|
+
// Mock dependencies
|
|
6
|
+
jest.mock("@smithy/signature-v4");
|
|
7
|
+
jest.mock("@aws-sdk/credential-provider-node");
|
|
8
|
+
jest.mock("@aws-sdk/protocol-http");
|
|
9
|
+
jest.mock("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
10
|
+
describe("StreamableHTTPClientWithSigV4Transport", () => {
|
|
11
|
+
const mockSign = jest.fn();
|
|
12
|
+
const mockDefaultProvider = jest.fn();
|
|
13
|
+
const mockCredentials = {
|
|
14
|
+
accessKeyId: "test-access-key",
|
|
15
|
+
secretAccessKey: "test-secret-key",
|
|
16
|
+
};
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
// Mock SignatureV4
|
|
20
|
+
SignatureV4.mockImplementation(() => ({
|
|
21
|
+
sign: mockSign,
|
|
22
|
+
}));
|
|
23
|
+
// Mock defaultProvider
|
|
24
|
+
defaultProvider.mockReturnValue(mockDefaultProvider);
|
|
25
|
+
mockDefaultProvider.mockResolvedValue(mockCredentials);
|
|
26
|
+
});
|
|
27
|
+
describe("constructor", () => {
|
|
28
|
+
test("should initialize with required options", () => {
|
|
29
|
+
const url = new URL("https://example.com/mcp");
|
|
30
|
+
const options = {
|
|
31
|
+
region: "us-east-1",
|
|
32
|
+
service: "lambda",
|
|
33
|
+
};
|
|
34
|
+
new StreamableHTTPClientWithSigV4Transport(url, options);
|
|
35
|
+
expect(SignatureV4).toHaveBeenCalledWith({
|
|
36
|
+
service: "lambda",
|
|
37
|
+
region: "us-east-1",
|
|
38
|
+
credentials: mockDefaultProvider,
|
|
39
|
+
sha256: expect.any(Function),
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
test("should use provided credentials instead of default provider", () => {
|
|
43
|
+
const url = new URL("https://example.com/mcp");
|
|
44
|
+
const customCredentials = {
|
|
45
|
+
accessKeyId: "custom-access-key",
|
|
46
|
+
secretAccessKey: "custom-secret-key",
|
|
47
|
+
};
|
|
48
|
+
const options = {
|
|
49
|
+
region: "us-west-2",
|
|
50
|
+
service: "execute-api",
|
|
51
|
+
credentials: customCredentials,
|
|
52
|
+
};
|
|
53
|
+
new StreamableHTTPClientWithSigV4Transport(url, options);
|
|
54
|
+
expect(SignatureV4).toHaveBeenCalledWith({
|
|
55
|
+
service: "execute-api",
|
|
56
|
+
region: "us-west-2",
|
|
57
|
+
credentials: customCredentials,
|
|
58
|
+
sha256: expect.any(Function),
|
|
59
|
+
});
|
|
60
|
+
expect(defaultProvider).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
test("should use provided fetch function", () => {
|
|
63
|
+
const url = new URL("https://example.com/mcp");
|
|
64
|
+
const customFetch = jest.fn();
|
|
65
|
+
const options = {
|
|
66
|
+
region: "us-east-1",
|
|
67
|
+
service: "lambda",
|
|
68
|
+
fetch: customFetch,
|
|
69
|
+
};
|
|
70
|
+
new StreamableHTTPClientWithSigV4Transport(url, options);
|
|
71
|
+
// The custom fetch should be passed to the SigV4FetchLike as baseFetch
|
|
72
|
+
expect(customFetch).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("SigV4FetchLike", () => {
|
|
76
|
+
let mockFetch;
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
mockFetch = jest.fn();
|
|
79
|
+
});
|
|
80
|
+
it("should sign and execute GET request", async () => {
|
|
81
|
+
const testUrl = "https://example.com/test";
|
|
82
|
+
const signedHeaders = {
|
|
83
|
+
authorization: "AWS4-HMAC-SHA256 ...",
|
|
84
|
+
"x-amz-date": "20231201T120000Z",
|
|
85
|
+
};
|
|
86
|
+
mockSign.mockResolvedValue({
|
|
87
|
+
method: "GET",
|
|
88
|
+
headers: signedHeaders,
|
|
89
|
+
body: undefined,
|
|
90
|
+
});
|
|
91
|
+
const mockResponse = new Response("test response");
|
|
92
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
93
|
+
const options = {
|
|
94
|
+
region: "us-east-1",
|
|
95
|
+
service: "lambda",
|
|
96
|
+
baseFetch: mockFetch,
|
|
97
|
+
};
|
|
98
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
99
|
+
const result = await sigV4Fetch.fetch(testUrl, undefined);
|
|
100
|
+
expect(HttpRequest).toHaveBeenCalledWith({
|
|
101
|
+
method: "GET",
|
|
102
|
+
protocol: "https:",
|
|
103
|
+
hostname: "example.com",
|
|
104
|
+
path: "/test",
|
|
105
|
+
headers: {
|
|
106
|
+
host: "example.com",
|
|
107
|
+
},
|
|
108
|
+
body: undefined,
|
|
109
|
+
});
|
|
110
|
+
expect(mockSign).toHaveBeenCalledWith(expect.any(HttpRequest));
|
|
111
|
+
expect(mockFetch).toHaveBeenCalledWith(new URL(testUrl), {
|
|
112
|
+
method: "GET",
|
|
113
|
+
headers: signedHeaders,
|
|
114
|
+
body: undefined,
|
|
115
|
+
});
|
|
116
|
+
expect(result).toBe(mockResponse);
|
|
117
|
+
});
|
|
118
|
+
it("should sign and execute POST request with body", async () => {
|
|
119
|
+
const testUrl = "https://example.com/api";
|
|
120
|
+
const requestInit = {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
"content-type": "application/json",
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify({ test: "data" }),
|
|
126
|
+
};
|
|
127
|
+
const signedHeaders = {
|
|
128
|
+
authorization: "AWS4-HMAC-SHA256 ...",
|
|
129
|
+
"x-amz-date": "20231201T120000Z",
|
|
130
|
+
"content-type": "application/json",
|
|
131
|
+
};
|
|
132
|
+
mockSign.mockResolvedValue({
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: signedHeaders,
|
|
135
|
+
body: requestInit.body,
|
|
136
|
+
});
|
|
137
|
+
const mockResponse = new Response("success");
|
|
138
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
139
|
+
const options = {
|
|
140
|
+
region: "us-east-1",
|
|
141
|
+
service: "lambda",
|
|
142
|
+
baseFetch: mockFetch,
|
|
143
|
+
};
|
|
144
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
145
|
+
const result = await sigV4Fetch.fetch(testUrl, requestInit);
|
|
146
|
+
expect(HttpRequest).toHaveBeenCalledWith({
|
|
147
|
+
method: "POST",
|
|
148
|
+
protocol: "https:",
|
|
149
|
+
hostname: "example.com",
|
|
150
|
+
path: "/api",
|
|
151
|
+
headers: {
|
|
152
|
+
host: "example.com",
|
|
153
|
+
"content-type": "application/json",
|
|
154
|
+
},
|
|
155
|
+
body: requestInit.body,
|
|
156
|
+
});
|
|
157
|
+
expect(mockSign).toHaveBeenCalledWith(expect.any(HttpRequest));
|
|
158
|
+
expect(mockFetch).toHaveBeenCalledWith(new URL(testUrl), {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: signedHeaders,
|
|
161
|
+
body: requestInit.body,
|
|
162
|
+
});
|
|
163
|
+
expect(result).toBe(mockResponse);
|
|
164
|
+
});
|
|
165
|
+
it("should handle URL with port and query parameters", async () => {
|
|
166
|
+
const testUrl = "https://example.com:8443/path?param=value";
|
|
167
|
+
mockSign.mockResolvedValue({
|
|
168
|
+
method: "GET",
|
|
169
|
+
headers: { authorization: "AWS4-HMAC-SHA256 ..." },
|
|
170
|
+
body: undefined,
|
|
171
|
+
});
|
|
172
|
+
mockFetch.mockResolvedValue(new Response("test"));
|
|
173
|
+
const options = {
|
|
174
|
+
region: "us-east-1",
|
|
175
|
+
service: "lambda",
|
|
176
|
+
baseFetch: mockFetch,
|
|
177
|
+
};
|
|
178
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
179
|
+
await sigV4Fetch.fetch(testUrl, undefined);
|
|
180
|
+
expect(HttpRequest).toHaveBeenCalledWith({
|
|
181
|
+
method: "GET",
|
|
182
|
+
protocol: "https:",
|
|
183
|
+
hostname: "example.com",
|
|
184
|
+
port: 8443,
|
|
185
|
+
path: "/path?param=value",
|
|
186
|
+
headers: {
|
|
187
|
+
host: "example.com",
|
|
188
|
+
},
|
|
189
|
+
body: undefined,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
it("should handle Headers object in request init", async () => {
|
|
193
|
+
const testUrl = "https://example.com/test";
|
|
194
|
+
const headers = new Headers();
|
|
195
|
+
headers.set("Content-Type", "application/json");
|
|
196
|
+
headers.set("X-Custom-Header", "custom-value");
|
|
197
|
+
const requestInit = {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers,
|
|
200
|
+
body: "test body",
|
|
201
|
+
};
|
|
202
|
+
mockSign.mockResolvedValue({
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: { authorization: "AWS4-HMAC-SHA256 ..." },
|
|
205
|
+
body: "test body",
|
|
206
|
+
});
|
|
207
|
+
mockFetch.mockResolvedValue(new Response("test"));
|
|
208
|
+
const options = {
|
|
209
|
+
region: "us-east-1",
|
|
210
|
+
service: "lambda",
|
|
211
|
+
baseFetch: mockFetch,
|
|
212
|
+
};
|
|
213
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
214
|
+
await sigV4Fetch.fetch(testUrl, requestInit);
|
|
215
|
+
expect(HttpRequest).toHaveBeenCalledWith({
|
|
216
|
+
method: "POST",
|
|
217
|
+
protocol: "https:",
|
|
218
|
+
hostname: "example.com",
|
|
219
|
+
path: "/test",
|
|
220
|
+
headers: {
|
|
221
|
+
host: "example.com",
|
|
222
|
+
"content-type": "application/json",
|
|
223
|
+
"x-custom-header": "custom-value",
|
|
224
|
+
},
|
|
225
|
+
body: "test body",
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
it("should handle array headers in request init", async () => {
|
|
229
|
+
const testUrl = "https://example.com/test";
|
|
230
|
+
const requestInit = {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: [
|
|
233
|
+
["Content-Type", "application/json"],
|
|
234
|
+
["X-Custom-Header", "custom-value"],
|
|
235
|
+
],
|
|
236
|
+
body: "test body",
|
|
237
|
+
};
|
|
238
|
+
mockSign.mockResolvedValue({
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers: { authorization: "AWS4-HMAC-SHA256 ..." },
|
|
241
|
+
body: "test body",
|
|
242
|
+
});
|
|
243
|
+
mockFetch.mockResolvedValue(new Response("test"));
|
|
244
|
+
const options = {
|
|
245
|
+
region: "us-east-1",
|
|
246
|
+
service: "lambda",
|
|
247
|
+
baseFetch: mockFetch,
|
|
248
|
+
};
|
|
249
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
250
|
+
await sigV4Fetch.fetch(testUrl, requestInit);
|
|
251
|
+
expect(HttpRequest).toHaveBeenCalledWith({
|
|
252
|
+
method: "POST",
|
|
253
|
+
protocol: "https:",
|
|
254
|
+
hostname: "example.com",
|
|
255
|
+
path: "/test",
|
|
256
|
+
headers: {
|
|
257
|
+
host: "example.com",
|
|
258
|
+
"content-type": "application/json",
|
|
259
|
+
"x-custom-header": "custom-value",
|
|
260
|
+
},
|
|
261
|
+
body: "test body",
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
it("should handle URL object as input", async () => {
|
|
265
|
+
const testUrl = new URL("https://example.com/test");
|
|
266
|
+
mockSign.mockResolvedValue({
|
|
267
|
+
method: "GET",
|
|
268
|
+
headers: { authorization: "AWS4-HMAC-SHA256 ..." },
|
|
269
|
+
body: undefined,
|
|
270
|
+
});
|
|
271
|
+
mockFetch.mockResolvedValue(new Response("test"));
|
|
272
|
+
const options = {
|
|
273
|
+
region: "us-east-1",
|
|
274
|
+
service: "lambda",
|
|
275
|
+
baseFetch: mockFetch,
|
|
276
|
+
};
|
|
277
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
278
|
+
await sigV4Fetch.fetch(testUrl, undefined);
|
|
279
|
+
expect(HttpRequest).toHaveBeenCalledWith({
|
|
280
|
+
method: "GET",
|
|
281
|
+
protocol: "https:",
|
|
282
|
+
hostname: "example.com",
|
|
283
|
+
path: "/test",
|
|
284
|
+
headers: {
|
|
285
|
+
host: "example.com",
|
|
286
|
+
},
|
|
287
|
+
body: undefined,
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
it("should propagate signing errors", async () => {
|
|
291
|
+
const testUrl = "https://example.com/test";
|
|
292
|
+
mockSign.mockRejectedValue(new Error("Signing failed"));
|
|
293
|
+
const options = {
|
|
294
|
+
region: "us-east-1",
|
|
295
|
+
service: "lambda",
|
|
296
|
+
baseFetch: mockFetch,
|
|
297
|
+
};
|
|
298
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
299
|
+
await expect(sigV4Fetch.fetch(testUrl, undefined)).rejects.toThrow("Signing failed");
|
|
300
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
301
|
+
});
|
|
302
|
+
it("should propagate fetch errors", async () => {
|
|
303
|
+
const testUrl = "https://example.com/test";
|
|
304
|
+
mockSign.mockResolvedValue({
|
|
305
|
+
method: "GET",
|
|
306
|
+
headers: { authorization: "AWS4-HMAC-SHA256 ..." },
|
|
307
|
+
body: undefined,
|
|
308
|
+
});
|
|
309
|
+
mockFetch.mockRejectedValue(new Error("Network error"));
|
|
310
|
+
const options = {
|
|
311
|
+
region: "us-east-1",
|
|
312
|
+
service: "lambda",
|
|
313
|
+
baseFetch: mockFetch,
|
|
314
|
+
};
|
|
315
|
+
const sigV4Fetch = new SigV4FetchLike(options);
|
|
316
|
+
await expect(sigV4Fetch.fetch(testUrl, undefined)).rejects.toThrow("Network error");
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
describe("createSigV4Fetch", () => {
|
|
320
|
+
let mockFetch;
|
|
321
|
+
beforeEach(() => {
|
|
322
|
+
mockFetch = jest.fn();
|
|
323
|
+
});
|
|
324
|
+
it("should create a FetchLike function that signs requests", async () => {
|
|
325
|
+
const testUrl = "https://example.com/test";
|
|
326
|
+
const signedHeaders = {
|
|
327
|
+
authorization: "AWS4-HMAC-SHA256 ...",
|
|
328
|
+
"x-amz-date": "20231201T120000Z",
|
|
329
|
+
};
|
|
330
|
+
mockSign.mockResolvedValue({
|
|
331
|
+
method: "GET",
|
|
332
|
+
headers: signedHeaders,
|
|
333
|
+
body: undefined,
|
|
334
|
+
});
|
|
335
|
+
const mockResponse = new Response("test response");
|
|
336
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
337
|
+
const options = {
|
|
338
|
+
region: "us-east-1",
|
|
339
|
+
service: "lambda",
|
|
340
|
+
baseFetch: mockFetch,
|
|
341
|
+
};
|
|
342
|
+
const sigV4Fetch = createSigV4Fetch(options);
|
|
343
|
+
const result = await sigV4Fetch(testUrl, undefined);
|
|
344
|
+
expect(HttpRequest).toHaveBeenCalledWith({
|
|
345
|
+
method: "GET",
|
|
346
|
+
protocol: "https:",
|
|
347
|
+
hostname: "example.com",
|
|
348
|
+
path: "/test",
|
|
349
|
+
headers: {
|
|
350
|
+
host: "example.com",
|
|
351
|
+
},
|
|
352
|
+
body: undefined,
|
|
353
|
+
});
|
|
354
|
+
expect(mockSign).toHaveBeenCalledWith(expect.any(HttpRequest));
|
|
355
|
+
expect(mockFetch).toHaveBeenCalledWith(new URL(testUrl), {
|
|
356
|
+
method: "GET",
|
|
357
|
+
headers: signedHeaders,
|
|
358
|
+
body: undefined,
|
|
359
|
+
});
|
|
360
|
+
expect(result).toBe(mockResponse);
|
|
361
|
+
});
|
|
362
|
+
it("should work with StreamableHTTPClientTransport", async () => {
|
|
363
|
+
const testUrl = "https://example.com/test";
|
|
364
|
+
const signedHeaders = {
|
|
365
|
+
authorization: "AWS4-HMAC-SHA256 ...",
|
|
366
|
+
"x-amz-date": "20231201T120000Z",
|
|
367
|
+
};
|
|
368
|
+
mockSign.mockResolvedValue({
|
|
369
|
+
method: "GET",
|
|
370
|
+
headers: signedHeaders,
|
|
371
|
+
body: undefined,
|
|
372
|
+
});
|
|
373
|
+
const mockResponse = new Response("test response");
|
|
374
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
375
|
+
const options = {
|
|
376
|
+
region: "us-east-1",
|
|
377
|
+
service: "lambda",
|
|
378
|
+
baseFetch: mockFetch,
|
|
379
|
+
};
|
|
380
|
+
const sigV4Fetch = createSigV4Fetch(options);
|
|
381
|
+
// This demonstrates how users can now use the SigV4 fetch with any transport
|
|
382
|
+
const result = await sigV4Fetch(testUrl, { method: "GET" });
|
|
383
|
+
expect(result).toBe(mockResponse);
|
|
384
|
+
expect(mockSign).toHaveBeenCalled();
|
|
385
|
+
expect(mockFetch).toHaveBeenCalled();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
describe("credential provider integration", () => {
|
|
389
|
+
test("should use default provider when no credentials provided", () => {
|
|
390
|
+
const url = new URL("https://example.com/mcp");
|
|
391
|
+
const options = {
|
|
392
|
+
region: "us-east-1",
|
|
393
|
+
service: "lambda",
|
|
394
|
+
};
|
|
395
|
+
new StreamableHTTPClientWithSigV4Transport(url, options);
|
|
396
|
+
expect(defaultProvider).toHaveBeenCalled();
|
|
397
|
+
expect(SignatureV4).toHaveBeenCalledWith({
|
|
398
|
+
service: "lambda",
|
|
399
|
+
region: "us-east-1",
|
|
400
|
+
credentials: mockDefaultProvider,
|
|
401
|
+
sha256: expect.any(Function),
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
test("should use provided credential provider function", () => {
|
|
405
|
+
const url = new URL("https://example.com/mcp");
|
|
406
|
+
const customProvider = jest.fn().mockResolvedValue(mockCredentials);
|
|
407
|
+
const options = {
|
|
408
|
+
region: "us-east-1",
|
|
409
|
+
service: "lambda",
|
|
410
|
+
credentials: customProvider,
|
|
411
|
+
};
|
|
412
|
+
new StreamableHTTPClientWithSigV4Transport(url, options);
|
|
413
|
+
expect(SignatureV4).toHaveBeenCalledWith({
|
|
414
|
+
service: "lambda",
|
|
415
|
+
region: "us-east-1",
|
|
416
|
+
credentials: customProvider,
|
|
417
|
+
sha256: expect.any(Function),
|
|
418
|
+
});
|
|
419
|
+
expect(defaultProvider).not.toHaveBeenCalled();
|
|
420
|
+
});
|
|
421
|
+
test("should use provided static credentials", () => {
|
|
422
|
+
const url = new URL("https://example.com/mcp");
|
|
423
|
+
const staticCredentials = {
|
|
424
|
+
accessKeyId: "static-key",
|
|
425
|
+
secretAccessKey: "static-secret",
|
|
426
|
+
};
|
|
427
|
+
const options = {
|
|
428
|
+
region: "us-east-1",
|
|
429
|
+
service: "lambda",
|
|
430
|
+
credentials: staticCredentials,
|
|
431
|
+
};
|
|
432
|
+
new StreamableHTTPClientWithSigV4Transport(url, options);
|
|
433
|
+
expect(SignatureV4).toHaveBeenCalledWith({
|
|
434
|
+
service: "lambda",
|
|
435
|
+
region: "us-east-1",
|
|
436
|
+
credentials: staticCredentials,
|
|
437
|
+
sha256: expect.any(Function),
|
|
438
|
+
});
|
|
439
|
+
expect(defaultProvider).not.toHaveBeenCalled();
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
});
|
package/dist/handlers/{api_gateway_proxy_event_handler.d.ts → apiGatewayProxyEventHandler.d.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
|
|
2
|
-
import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./
|
|
3
|
-
import { RequestHandler } from "./
|
|
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
|
*
|
package/dist/handlers/{api_gateway_proxy_event_v2_handler.d.ts → apiGatewayProxyEventV2Handler.d.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
|
|
2
|
-
import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./
|
|
3
|
-
import { RequestHandler } from "./
|
|
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
|
*
|
package/dist/handlers/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { RequestHandler } from "./
|
|
2
|
-
export { APIGatewayProxyEventHandler } from "./
|
|
3
|
-
export { APIGatewayProxyEventV2Handler } from "./
|
|
4
|
-
export { LambdaFunctionURLEventHandler } from "./
|
|
1
|
+
export { RequestHandler } from "./requestHandler.js";
|
|
2
|
+
export { APIGatewayProxyEventHandler } from "./apiGatewayProxyEventHandler.js";
|
|
3
|
+
export { APIGatewayProxyEventV2Handler } from "./apiGatewayProxyEventV2Handler.js";
|
|
4
|
+
export { LambdaFunctionURLEventHandler } from "./lambdaFunctionUrlEventHandler.js";
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { APIGatewayProxyEventHandler } from "./
|
|
2
|
-
export { APIGatewayProxyEventV2Handler } from "./
|
|
3
|
-
export { LambdaFunctionURLEventHandler } from "./
|
|
1
|
+
export { APIGatewayProxyEventHandler } from "./apiGatewayProxyEventHandler.js";
|
|
2
|
+
export { APIGatewayProxyEventV2Handler } from "./apiGatewayProxyEventV2Handler.js";
|
|
3
|
+
export { LambdaFunctionURLEventHandler } from "./lambdaFunctionUrlEventHandler.js";
|
package/dist/handlers/{lambda_function_url_event_handler.d.ts → lambdaFunctionUrlEventHandler.d.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
|
|
2
|
-
import { StreamableHttpHandler, ParsedHttpRequest, HttpResponse } from "./
|
|
3
|
-
import { RequestHandler } from "./
|
|
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,6 +1,6 @@
|
|
|
1
1
|
import { Context } from "aws-lambda";
|
|
2
2
|
import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { RequestHandler } from "./
|
|
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 "./
|
|
2
|
-
export { StdioServerAdapterRequestHandler } from "./
|
|
1
|
+
export { stdioServerAdapter } from "./stdioServerAdapter.js";
|
|
2
|
+
export { StdioServerAdapterRequestHandler } from "./stdioServerAdapterRequestHandler.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { stdioServerAdapter } from "./
|
|
2
|
-
export { StdioServerAdapterRequestHandler } from "./
|
|
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 './
|
|
2
|
+
import { stdioServerAdapter } from './stdioServerAdapter.js';
|
|
3
3
|
const serverParameters = {
|
|
4
4
|
command: 'npx',
|
|
5
|
-
args: ['tsx', 'test-stdio-server/
|
|
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/
|
|
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 './
|
|
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(),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { ErrorCode, } from
|
|
2
|
-
import { StdioServerAdapterRequestHandler } from
|
|
3
|
-
import * as stdioServerAdapter from
|
|
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(
|
|
5
|
+
jest.mock("./stdioServerAdapter.js", () => ({
|
|
6
6
|
stdioServerAdapter: jest.fn(),
|
|
7
7
|
}));
|
|
8
8
|
const mockStdioServerAdapter = stdioServerAdapter.stdioServerAdapter;
|
|
9
9
|
const mockServerParams = {
|
|
10
|
-
command:
|
|
11
|
-
args: [
|
|
10
|
+
command: "node",
|
|
11
|
+
args: ["test-server.js"],
|
|
12
12
|
};
|
|
13
13
|
const mockContext = {
|
|
14
14
|
callbackWaitsForEmptyEventLoop: true,
|
|
15
|
-
functionName:
|
|
16
|
-
functionVersion:
|
|
17
|
-
invokedFunctionArn:
|
|
18
|
-
memoryLimitInMB:
|
|
19
|
-
awsRequestId:
|
|
20
|
-
logGroupName:
|
|
21
|
-
logStreamName:
|
|
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(
|
|
27
|
+
describe("StdioServerAdapterRequestHandler", () => {
|
|
28
28
|
let handler;
|
|
29
29
|
beforeEach(() => {
|
|
30
30
|
handler = new StdioServerAdapterRequestHandler(mockServerParams);
|
|
31
31
|
jest.clearAllMocks();
|
|
32
32
|
});
|
|
33
|
-
describe(
|
|
34
|
-
test(
|
|
33
|
+
describe("handleRequest", () => {
|
|
34
|
+
test("should return JSONRPCResponse when stdioServerAdapter returns valid response", async () => {
|
|
35
35
|
const request = {
|
|
36
|
-
jsonrpc:
|
|
36
|
+
jsonrpc: "2.0",
|
|
37
37
|
id: 1,
|
|
38
|
-
method:
|
|
39
|
-
params: { arg:
|
|
38
|
+
method: "test",
|
|
39
|
+
params: { arg: "value" },
|
|
40
40
|
};
|
|
41
41
|
const expectedResponse = {
|
|
42
|
-
jsonrpc:
|
|
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(
|
|
51
|
+
test("should return JSONRPCError when stdioServerAdapter returns error", async () => {
|
|
52
52
|
const request = {
|
|
53
|
-
jsonrpc:
|
|
53
|
+
jsonrpc: "2.0",
|
|
54
54
|
id: 1,
|
|
55
|
-
method:
|
|
55
|
+
method: "test",
|
|
56
56
|
};
|
|
57
57
|
const expectedError = {
|
|
58
|
-
jsonrpc:
|
|
58
|
+
jsonrpc: "2.0",
|
|
59
59
|
error: {
|
|
60
60
|
code: ErrorCode.MethodNotFound,
|
|
61
|
-
message:
|
|
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(
|
|
70
|
+
test("should return internal error when stdioServerAdapter returns unexpected format", async () => {
|
|
71
71
|
const request = {
|
|
72
|
-
jsonrpc:
|
|
72
|
+
jsonrpc: "2.0",
|
|
73
73
|
id: 1,
|
|
74
|
-
method:
|
|
74
|
+
method: "test",
|
|
75
75
|
};
|
|
76
|
-
const unexpectedResponse = { invalid:
|
|
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:
|
|
80
|
+
jsonrpc: "2.0",
|
|
81
81
|
error: {
|
|
82
82
|
code: ErrorCode.InternalError,
|
|
83
|
-
message:
|
|
84
|
-
data:
|
|
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(
|
|
89
|
+
test("should return internal error when stdioServerAdapter throws exception", async () => {
|
|
90
90
|
const request = {
|
|
91
|
-
jsonrpc:
|
|
91
|
+
jsonrpc: "2.0",
|
|
92
92
|
id: 1,
|
|
93
|
-
method:
|
|
93
|
+
method: "test",
|
|
94
94
|
};
|
|
95
|
-
const error = new Error(
|
|
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:
|
|
99
|
+
jsonrpc: "2.0",
|
|
100
100
|
error: {
|
|
101
101
|
code: ErrorCode.InternalError,
|
|
102
|
-
message:
|
|
103
|
-
data:
|
|
102
|
+
message: "Internal error",
|
|
103
|
+
data: "Connection failed",
|
|
104
104
|
},
|
|
105
105
|
id: 1,
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
|
-
test(
|
|
108
|
+
test("should handle non-Error exceptions", async () => {
|
|
109
109
|
const request = {
|
|
110
|
-
jsonrpc:
|
|
110
|
+
jsonrpc: "2.0",
|
|
111
111
|
id: 1,
|
|
112
|
-
method:
|
|
112
|
+
method: "test",
|
|
113
113
|
};
|
|
114
|
-
mockStdioServerAdapter.mockRejectedValue(
|
|
114
|
+
mockStdioServerAdapter.mockRejectedValue("String error");
|
|
115
115
|
const result = await handler.handleRequest(request, mockContext);
|
|
116
116
|
expect(result).toEqual({
|
|
117
|
-
jsonrpc:
|
|
117
|
+
jsonrpc: "2.0",
|
|
118
118
|
error: {
|
|
119
119
|
code: ErrorCode.InternalError,
|
|
120
|
-
message:
|
|
121
|
-
data:
|
|
120
|
+
message: "Internal error",
|
|
121
|
+
data: "Unknown error",
|
|
122
122
|
},
|
|
123
123
|
id: 1,
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
|
-
test(
|
|
126
|
+
test("should pass server parameters correctly to stdioServerAdapter", async () => {
|
|
127
127
|
const customServerParams = {
|
|
128
|
-
command:
|
|
129
|
-
args: [
|
|
130
|
-
env: {
|
|
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:
|
|
134
|
+
jsonrpc: "2.0",
|
|
135
135
|
id: 1,
|
|
136
|
-
method:
|
|
136
|
+
method: "test",
|
|
137
137
|
};
|
|
138
138
|
const response = {
|
|
139
|
-
jsonrpc:
|
|
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.
|
|
4
|
+
"version": "0.3.0",
|
|
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",
|
|
@@ -53,8 +54,13 @@
|
|
|
53
54
|
"typescript-eslint": "^8.35.1"
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
57
|
+
"@aws-crypto/sha256-js": "^5.2.0",
|
|
56
58
|
"@aws-sdk/client-lambda": "^3.840.0",
|
|
59
|
+
"@aws-sdk/credential-provider-node": "^3.840.0",
|
|
60
|
+
"@aws-sdk/protocol-http": "^3.370.0",
|
|
61
|
+
"@aws-sdk/types": "^3.840.0",
|
|
57
62
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
63
|
+
"@smithy/signature-v4": "^5.1.2",
|
|
58
64
|
"@types/aws-lambda": "^8.10.149",
|
|
59
65
|
"winston": "^3.17.0"
|
|
60
66
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|