@aws/run-mcp-servers-with-aws-lambda 0.2.1 → 0.2.3
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/handlers/api_gateway_proxy_event_handler.d.ts +31 -0
- package/dist/handlers/api_gateway_proxy_event_handler.js +43 -0
- package/dist/handlers/api_gateway_proxy_event_v2_handler.d.ts +30 -0
- package/dist/handlers/api_gateway_proxy_event_v2_handler.js +42 -0
- package/dist/handlers/handlers.test.js +629 -0
- package/dist/handlers/index.d.ts +4 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/lambda_function_url_event_handler.d.ts +30 -0
- package/dist/handlers/lambda_function_url_event_handler.js +42 -0
- package/dist/handlers/request_handler.d.ts +43 -0
- package/dist/handlers/request_handler.js +1 -0
- package/dist/handlers/streamable_http_handler.d.ts +81 -0
- package/dist/handlers/streamable_http_handler.js +234 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/server-adapter/index.d.ts +2 -4
- package/dist/server-adapter/index.js +2 -110
- package/dist/server-adapter/stdio_server_adapter.d.ts +17 -0
- package/dist/server-adapter/stdio_server_adapter.js +118 -0
- package/dist/server-adapter/stdio_server_adapter.test.d.ts +1 -0
- package/dist/server-adapter/{index.test.js → stdio_server_adapter.test.js} +1 -1
- package/dist/server-adapter/stdio_server_adapter_request_handler.d.ts +26 -0
- package/dist/server-adapter/stdio_server_adapter_request_handler.js +66 -0
- package/dist/server-adapter/stdio_server_adapter_request_handler.test.d.ts +1 -0
- package/dist/server-adapter/stdio_server_adapter_request_handler.test.js +148 -0
- package/package.json +12 -12
- /package/dist/{server-adapter/index.test.d.ts → handlers/handlers.test.d.ts} +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { StreamableHttpHandler, } from "./streamable_http_handler.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handler for Lambda Function URL requests
|
|
4
|
+
*
|
|
5
|
+
* This handler processes APIGatewayProxyEventV2 events and returns APIGatewayProxyResultV2 responses.
|
|
6
|
+
*
|
|
7
|
+
* This class handles all the generic JSON-RPC protocol aspects of the MCP Streamable HTTP transport:
|
|
8
|
+
* - HTTP method validation (POST, OPTIONS, GET)
|
|
9
|
+
* - Content-Type and Accept header validation
|
|
10
|
+
* - JSON parsing and validation
|
|
11
|
+
* - Batch request handling
|
|
12
|
+
* - CORS headers
|
|
13
|
+
* - Error response formatting
|
|
14
|
+
* This class does not implement session management.
|
|
15
|
+
*
|
|
16
|
+
* The specific business logic is delegated to a provided RequestHandler implementation.
|
|
17
|
+
*/
|
|
18
|
+
export class LambdaFunctionURLEventHandler extends StreamableHttpHandler {
|
|
19
|
+
constructor(requestHandler) {
|
|
20
|
+
super(requestHandler);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse Lambda Function URL event (APIGatewayProxyEventV2) into common HTTP request format
|
|
24
|
+
*/
|
|
25
|
+
parseEvent(event) {
|
|
26
|
+
return {
|
|
27
|
+
method: event.requestContext.http.method,
|
|
28
|
+
headers: event.headers || {},
|
|
29
|
+
body: event.body || null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Format HTTP response as APIGatewayProxyResultV2
|
|
34
|
+
*/
|
|
35
|
+
formatResponse(response) {
|
|
36
|
+
return {
|
|
37
|
+
statusCode: response.statusCode,
|
|
38
|
+
headers: response.headers,
|
|
39
|
+
body: response.body,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Context } from "aws-lambda";
|
|
2
|
+
import { JSONRPCRequest, JSONRPCResponse, JSONRPCError } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interface for handling individual JSON-RPC requests
|
|
5
|
+
*
|
|
6
|
+
* This interface defines the contract for processing MCP (Model Context Protocol) requests
|
|
7
|
+
* in AWS Lambda functions. Implementations should contain the business logic for handling
|
|
8
|
+
* specific JSON-RPC methods.
|
|
9
|
+
*/
|
|
10
|
+
export interface RequestHandler {
|
|
11
|
+
/**
|
|
12
|
+
* Process a single JSON-RPC request and return a response or error
|
|
13
|
+
*
|
|
14
|
+
* @param request The JSON-RPC request to process
|
|
15
|
+
* @param context The AWS Lambda context providing runtime information
|
|
16
|
+
* @returns A Promise that resolves to either a JSON-RPC response (for successful requests)
|
|
17
|
+
* or a JSON-RPC error (for failed requests)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* async handleRequest(request: JSONRPCRequest, context: Context): Promise<JSONRPCResponse | JSONRPCError> {
|
|
22
|
+
* switch (request.method) {
|
|
23
|
+
* case "ping":
|
|
24
|
+
* return {
|
|
25
|
+
* jsonrpc: "2.0",
|
|
26
|
+
* result: { message: "pong" },
|
|
27
|
+
* id: request.id,
|
|
28
|
+
* };
|
|
29
|
+
* default:
|
|
30
|
+
* return {
|
|
31
|
+
* jsonrpc: "2.0",
|
|
32
|
+
* error: {
|
|
33
|
+
* code: -32601,
|
|
34
|
+
* message: "Method not found",
|
|
35
|
+
* },
|
|
36
|
+
* id: request.id,
|
|
37
|
+
* };
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
handleRequest(request: JSONRPCRequest, context: Context): Promise<JSONRPCResponse | JSONRPCError>;
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Context } from "aws-lambda";
|
|
2
|
+
import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { RequestHandler } from "./request_handler.js";
|
|
4
|
+
/**
|
|
5
|
+
* Parsed HTTP request data extracted from various Lambda event types
|
|
6
|
+
*/
|
|
7
|
+
export interface ParsedHttpRequest {
|
|
8
|
+
method: string;
|
|
9
|
+
headers: Record<string, string | undefined>;
|
|
10
|
+
body: string | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* HTTP response data that can be formatted for different Lambda event types
|
|
14
|
+
*/
|
|
15
|
+
export interface HttpResponse {
|
|
16
|
+
statusCode: number;
|
|
17
|
+
headers: Record<string, string>;
|
|
18
|
+
body: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Abstract base class for MCP Streamable HTTP protocol handlers in Lambda functions
|
|
22
|
+
*
|
|
23
|
+
* This class handles all the generic JSON-RPC protocol aspects:
|
|
24
|
+
* - HTTP method validation (POST, OPTIONS, GET)
|
|
25
|
+
* - Content-Type and Accept header validation
|
|
26
|
+
* - JSON parsing and validation
|
|
27
|
+
* - Batch request handling
|
|
28
|
+
* - CORS headers
|
|
29
|
+
* - Error response formatting
|
|
30
|
+
* This class does not implement session management.
|
|
31
|
+
*
|
|
32
|
+
* The specific business logic is delegated to a RequestHandler implementation.
|
|
33
|
+
* Event-specific parsing and response formatting is handled by concrete subclasses.
|
|
34
|
+
*/
|
|
35
|
+
export declare abstract class StreamableHttpHandler<TEvent, TResult> {
|
|
36
|
+
protected requestHandler: RequestHandler;
|
|
37
|
+
constructor(requestHandler: RequestHandler);
|
|
38
|
+
/**
|
|
39
|
+
* Main handler method that processes Lambda events.
|
|
40
|
+
* Concrete implementations should call this after parsing the event.
|
|
41
|
+
*/
|
|
42
|
+
handle(event: TEvent, context: Context): Promise<TResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Parse the Lambda event into a common HTTP request format
|
|
45
|
+
* Must be implemented by concrete subclasses
|
|
46
|
+
*/
|
|
47
|
+
protected abstract parseEvent(event: TEvent): ParsedHttpRequest;
|
|
48
|
+
/**
|
|
49
|
+
* Format the HTTP response for the specific Lambda event type
|
|
50
|
+
* Must be implemented by concrete subclasses
|
|
51
|
+
*/
|
|
52
|
+
protected abstract formatResponse(response: HttpResponse): TResult;
|
|
53
|
+
/**
|
|
54
|
+
* Process the HTTP request using shared MCP Streamable HTTP logic
|
|
55
|
+
*/
|
|
56
|
+
protected processHttpRequest(httpRequest: ParsedHttpRequest, context: Context): Promise<HttpResponse>;
|
|
57
|
+
/**
|
|
58
|
+
* Get header value in a case-insensitive way
|
|
59
|
+
*/
|
|
60
|
+
protected getHeaderValue(headers: Record<string, string | undefined>, headerName: string): string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Create a CORS preflight HTTP response
|
|
63
|
+
*/
|
|
64
|
+
protected createCorsHttpResponse(): HttpResponse;
|
|
65
|
+
/**
|
|
66
|
+
* Create an error HTTP response with proper CORS headers
|
|
67
|
+
*/
|
|
68
|
+
protected createErrorHttpResponse(statusCode: number, errorCode: ErrorCode, message: string, additionalHeaders?: Record<string, string>, data?: unknown): HttpResponse;
|
|
69
|
+
/**
|
|
70
|
+
* Helper function to create JSON-RPC error responses with no ID
|
|
71
|
+
*/
|
|
72
|
+
protected createJSONRPCErrorResponse(code: ErrorCode, message: string, data?: unknown): {
|
|
73
|
+
jsonrpc: "2.0";
|
|
74
|
+
error: {
|
|
75
|
+
data?: {} | null | undefined;
|
|
76
|
+
code: ErrorCode;
|
|
77
|
+
message: string;
|
|
78
|
+
};
|
|
79
|
+
id: null;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { isJSONRPCRequest, isJSONRPCResponse, isJSONRPCError, isJSONRPCNotification, ErrorCode, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { createLogger, format, transports } from 'winston';
|
|
3
|
+
const logger = createLogger({
|
|
4
|
+
level: process.env.LOG_LEVEL?.toLowerCase() || 'info',
|
|
5
|
+
format: format.simple(),
|
|
6
|
+
transports: [new transports.Console()],
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Abstract base class for MCP Streamable HTTP protocol handlers in Lambda functions
|
|
10
|
+
*
|
|
11
|
+
* This class handles all the generic JSON-RPC protocol aspects:
|
|
12
|
+
* - HTTP method validation (POST, OPTIONS, GET)
|
|
13
|
+
* - Content-Type and Accept header validation
|
|
14
|
+
* - JSON parsing and validation
|
|
15
|
+
* - Batch request handling
|
|
16
|
+
* - CORS headers
|
|
17
|
+
* - Error response formatting
|
|
18
|
+
* This class does not implement session management.
|
|
19
|
+
*
|
|
20
|
+
* The specific business logic is delegated to a RequestHandler implementation.
|
|
21
|
+
* Event-specific parsing and response formatting is handled by concrete subclasses.
|
|
22
|
+
*/
|
|
23
|
+
export class StreamableHttpHandler {
|
|
24
|
+
requestHandler;
|
|
25
|
+
constructor(requestHandler) {
|
|
26
|
+
this.requestHandler = requestHandler;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Main handler method that processes Lambda events.
|
|
30
|
+
* Concrete implementations should call this after parsing the event.
|
|
31
|
+
*/
|
|
32
|
+
async handle(event, context) {
|
|
33
|
+
try {
|
|
34
|
+
logger.debug("Incoming event:", JSON.stringify(event, null, 2));
|
|
35
|
+
// Parse the event into a common HTTP request format
|
|
36
|
+
const httpRequest = this.parseEvent(event);
|
|
37
|
+
// Process the HTTP request using shared logic
|
|
38
|
+
const httpResponse = await this.processHttpRequest(httpRequest, context);
|
|
39
|
+
// Format the response for the specific event type
|
|
40
|
+
return this.formatResponse(httpResponse);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger.error("Error processing MCP Streamable HTTP request:", error);
|
|
44
|
+
return this.formatResponse(this.createErrorHttpResponse(500, ErrorCode.InternalError, "Internal error", {}, error instanceof Error ? error.message : "Unknown error"));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Process the HTTP request using shared MCP Streamable HTTP logic
|
|
49
|
+
*/
|
|
50
|
+
async processHttpRequest(httpRequest, context) {
|
|
51
|
+
// Handle different HTTP methods according to MCP Streamable HTTP spec
|
|
52
|
+
logger.debug("Detected HTTP method:", httpRequest.method);
|
|
53
|
+
if (httpRequest.method === "OPTIONS") {
|
|
54
|
+
// Handle CORS preflight
|
|
55
|
+
return this.createCorsHttpResponse();
|
|
56
|
+
}
|
|
57
|
+
if (httpRequest.method === "GET") {
|
|
58
|
+
// No support for SSE streaming in Lambda functions
|
|
59
|
+
// Return 405 Method Not Allowed as per spec
|
|
60
|
+
return this.createErrorHttpResponse(405, ErrorCode.ConnectionClosed, "Method Not Allowed: SSE streaming not supported", { Allow: "POST, OPTIONS" });
|
|
61
|
+
}
|
|
62
|
+
if (httpRequest.method !== "POST") {
|
|
63
|
+
return this.createErrorHttpResponse(405, ErrorCode.ConnectionClosed, "Method Not Allowed", { Allow: "POST, OPTIONS" });
|
|
64
|
+
}
|
|
65
|
+
// Validate Accept header for POST requests
|
|
66
|
+
const acceptHeader = this.getHeaderValue(httpRequest.headers, "accept");
|
|
67
|
+
if (!acceptHeader?.includes("application/json")) {
|
|
68
|
+
return this.createErrorHttpResponse(406, ErrorCode.ConnectionClosed, "Not Acceptable: Client must accept application/json");
|
|
69
|
+
}
|
|
70
|
+
// Validate Content-Type header
|
|
71
|
+
const contentType = this.getHeaderValue(httpRequest.headers, "content-type");
|
|
72
|
+
if (!contentType?.includes("application/json")) {
|
|
73
|
+
return this.createErrorHttpResponse(415, ErrorCode.ConnectionClosed, "Unsupported Media Type: Content-Type must be application/json");
|
|
74
|
+
}
|
|
75
|
+
// Parse the request body according to MCP Streamable HTTP spec
|
|
76
|
+
if (!httpRequest.body) {
|
|
77
|
+
return this.createErrorHttpResponse(400, ErrorCode.ParseError, "Parse error: Empty request body");
|
|
78
|
+
}
|
|
79
|
+
let parsedBody;
|
|
80
|
+
try {
|
|
81
|
+
parsedBody = JSON.parse(httpRequest.body);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return this.createErrorHttpResponse(400, ErrorCode.ParseError, "Parse error: Invalid JSON");
|
|
85
|
+
}
|
|
86
|
+
// Handle both single messages and batches according to MCP spec
|
|
87
|
+
let messages;
|
|
88
|
+
if (Array.isArray(parsedBody)) {
|
|
89
|
+
messages = parsedBody;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
messages = [parsedBody];
|
|
93
|
+
}
|
|
94
|
+
// Validate that all messages are valid JSON-RPC using schema validation
|
|
95
|
+
const validatedMessages = [];
|
|
96
|
+
for (const message of messages) {
|
|
97
|
+
if (isJSONRPCRequest(message) ||
|
|
98
|
+
isJSONRPCResponse(message) ||
|
|
99
|
+
isJSONRPCError(message) ||
|
|
100
|
+
isJSONRPCNotification(message)) {
|
|
101
|
+
validatedMessages.push(message);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
return this.createErrorHttpResponse(400, ErrorCode.InvalidRequest, "Invalid Request: All messages must be valid JSON-RPC 2.0");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Check if any message is a request (vs notification/response)
|
|
108
|
+
const hasRequests = validatedMessages.some(isJSONRPCRequest);
|
|
109
|
+
if (!hasRequests) {
|
|
110
|
+
// If it only contains notifications or responses, return 202 Accepted
|
|
111
|
+
return {
|
|
112
|
+
statusCode: 202,
|
|
113
|
+
headers: {
|
|
114
|
+
"Access-Control-Allow-Origin": "*",
|
|
115
|
+
},
|
|
116
|
+
body: "",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Process requests - for Lambda, we'll process them sequentially and return JSON
|
|
120
|
+
const responses = [];
|
|
121
|
+
for (const message of validatedMessages) {
|
|
122
|
+
if (isJSONRPCRequest(message)) {
|
|
123
|
+
try {
|
|
124
|
+
// Delegate to the specific request handler
|
|
125
|
+
const response = await this.requestHandler.handleRequest(message, context);
|
|
126
|
+
// The handler should return JSONRPCResponse or JSONRPCError for requests
|
|
127
|
+
if (isJSONRPCResponse(response) || isJSONRPCError(response)) {
|
|
128
|
+
responses.push(response);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Unexpected response format - return internal server error
|
|
132
|
+
logger.error("Unexpected response format from request handler:", response);
|
|
133
|
+
const errorResponse = {
|
|
134
|
+
jsonrpc: "2.0",
|
|
135
|
+
error: {
|
|
136
|
+
code: ErrorCode.InternalError,
|
|
137
|
+
message: "Internal error: Unexpected response format from request handler",
|
|
138
|
+
data: "Expected JSONRPCResponse or JSONRPCError",
|
|
139
|
+
},
|
|
140
|
+
id: message.id,
|
|
141
|
+
};
|
|
142
|
+
responses.push(errorResponse);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
// Return JSON-RPC error response
|
|
147
|
+
const errorResponse = {
|
|
148
|
+
jsonrpc: "2.0",
|
|
149
|
+
error: {
|
|
150
|
+
code: ErrorCode.InternalError,
|
|
151
|
+
message: "Internal error",
|
|
152
|
+
data: error instanceof Error ? error.message : "Unknown error",
|
|
153
|
+
},
|
|
154
|
+
id: message.id,
|
|
155
|
+
};
|
|
156
|
+
responses.push(errorResponse);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Prepare response headers
|
|
161
|
+
const responseHeaders = {
|
|
162
|
+
"Content-Type": "application/json",
|
|
163
|
+
"Access-Control-Allow-Origin": "*",
|
|
164
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
165
|
+
"Access-Control-Allow-Headers": "Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version",
|
|
166
|
+
};
|
|
167
|
+
// Return the response(s)
|
|
168
|
+
const responseBody = responses.length === 1 ? responses[0] : responses;
|
|
169
|
+
return {
|
|
170
|
+
statusCode: 200,
|
|
171
|
+
headers: responseHeaders,
|
|
172
|
+
body: JSON.stringify(responseBody),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get header value in a case-insensitive way
|
|
177
|
+
*/
|
|
178
|
+
getHeaderValue(headers, headerName) {
|
|
179
|
+
// Try exact match first
|
|
180
|
+
if (headers[headerName] !== undefined) {
|
|
181
|
+
return headers[headerName];
|
|
182
|
+
}
|
|
183
|
+
// Try case-insensitive match
|
|
184
|
+
const lowerHeaderName = headerName.toLowerCase();
|
|
185
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
186
|
+
if (key.toLowerCase() === lowerHeaderName) {
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Create a CORS preflight HTTP response
|
|
194
|
+
*/
|
|
195
|
+
createCorsHttpResponse() {
|
|
196
|
+
return {
|
|
197
|
+
statusCode: 200,
|
|
198
|
+
headers: {
|
|
199
|
+
"Access-Control-Allow-Origin": "*",
|
|
200
|
+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
|
|
201
|
+
"Access-Control-Allow-Headers": "Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version",
|
|
202
|
+
},
|
|
203
|
+
body: "",
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Create an error HTTP response with proper CORS headers
|
|
208
|
+
*/
|
|
209
|
+
createErrorHttpResponse(statusCode, errorCode, message, additionalHeaders = {}, data) {
|
|
210
|
+
return {
|
|
211
|
+
statusCode,
|
|
212
|
+
headers: {
|
|
213
|
+
"Content-Type": "application/json",
|
|
214
|
+
"Access-Control-Allow-Origin": "*",
|
|
215
|
+
...additionalHeaders,
|
|
216
|
+
},
|
|
217
|
+
body: JSON.stringify(this.createJSONRPCErrorResponse(errorCode, message, data)),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Helper function to create JSON-RPC error responses with no ID
|
|
222
|
+
*/
|
|
223
|
+
createJSONRPCErrorResponse(code, message, data) {
|
|
224
|
+
return {
|
|
225
|
+
jsonrpc: "2.0",
|
|
226
|
+
error: {
|
|
227
|
+
code,
|
|
228
|
+
message,
|
|
229
|
+
...(data !== undefined && { data }),
|
|
230
|
+
},
|
|
231
|
+
id: null,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./client/index.js";
|
|
2
|
+
export * from "./server-adapter/index.js";
|
|
3
|
+
export * from "./handlers/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./client/index.js";
|
|
2
|
+
export * from "./server-adapter/index.js";
|
|
3
|
+
export * from "./handlers/index.js";
|
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
export declare function stdioServerAdapter(serverParams: StdioServerParameters, event: JSONRPCMessage, context: Context): Promise<{}>;
|
|
1
|
+
export { stdioServerAdapter } from "./stdio_server_adapter.js";
|
|
2
|
+
export { StdioServerAdapterRequestHandler } from "./stdio_server_adapter_request_handler.js";
|
|
@@ -1,110 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { JSONRPCNotificationSchema, JSONRPCRequestSchema, McpError, ResultSchema, ErrorCode, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
import { createLogger, format, transports } from 'winston';
|
|
6
|
-
const logger = createLogger({
|
|
7
|
-
level: process.env.LOG_LEVEL?.toLowerCase() || 'info',
|
|
8
|
-
format: format.simple(),
|
|
9
|
-
transports: [new transports.Console()],
|
|
10
|
-
});
|
|
11
|
-
export async function stdioServerAdapter(serverParams, event, context) {
|
|
12
|
-
logger.debug(`Request: ${JSON.stringify(event)}`);
|
|
13
|
-
const response = await handleMessage(serverParams, event, context);
|
|
14
|
-
logger.debug(`Response: ${JSON.stringify(response)}`);
|
|
15
|
-
return response;
|
|
16
|
-
}
|
|
17
|
-
async function handleMessage(serverParams, event, context) {
|
|
18
|
-
// Determine the type of the message
|
|
19
|
-
try {
|
|
20
|
-
const request = JSONRPCRequestSchema.parse(event);
|
|
21
|
-
return await handleRequest(serverParams, request, context);
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
if (error instanceof z.ZodError) {
|
|
25
|
-
try {
|
|
26
|
-
const notification = JSONRPCNotificationSchema.parse(event);
|
|
27
|
-
return await handleNotification(serverParams, notification, context);
|
|
28
|
-
}
|
|
29
|
-
catch (notificationError) {
|
|
30
|
-
if (notificationError instanceof z.ZodError) {
|
|
31
|
-
return {
|
|
32
|
-
jsonrpc: event.jsonrpc,
|
|
33
|
-
id: 0,
|
|
34
|
-
error: {
|
|
35
|
-
code: ErrorCode.InvalidRequest,
|
|
36
|
-
message: 'Request is neither a valid JSON-RPC request nor a valid JSON-RPC notification',
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
throw notificationError;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
throw error;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
async function handleNotification(_serverParams, _event, _context) {
|
|
51
|
-
// Ignore notifications
|
|
52
|
-
logger.debug('Ignoring notification');
|
|
53
|
-
return {};
|
|
54
|
-
}
|
|
55
|
-
async function handleRequest(serverParams, event, _context) {
|
|
56
|
-
logger.debug('Handling request');
|
|
57
|
-
const { jsonrpc, id, ...request } = event;
|
|
58
|
-
const client = new Client({
|
|
59
|
-
name: 'mcp-client',
|
|
60
|
-
version: '1.0.0',
|
|
61
|
-
}, {
|
|
62
|
-
capabilities: {
|
|
63
|
-
prompts: {},
|
|
64
|
-
resources: {},
|
|
65
|
-
tools: {},
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
try {
|
|
69
|
-
const transport = new StdioClientTransport(serverParams);
|
|
70
|
-
await client.connect(transport);
|
|
71
|
-
const result = await client.request(request, ResultSchema);
|
|
72
|
-
return {
|
|
73
|
-
jsonrpc: jsonrpc,
|
|
74
|
-
id: id,
|
|
75
|
-
result: result,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
if (error instanceof McpError) {
|
|
80
|
-
logger.error(`MCP error: ${error}`);
|
|
81
|
-
return {
|
|
82
|
-
jsonrpc: jsonrpc,
|
|
83
|
-
id: id,
|
|
84
|
-
error: {
|
|
85
|
-
code: error.code,
|
|
86
|
-
message: error.message,
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
logger.error(`General exception: ${error}`);
|
|
92
|
-
return {
|
|
93
|
-
jsonrpc: jsonrpc,
|
|
94
|
-
id: id,
|
|
95
|
-
error: {
|
|
96
|
-
code: 500,
|
|
97
|
-
message: 'Internal failure, please check Lambda function logs',
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
finally {
|
|
103
|
-
try {
|
|
104
|
-
await client.close();
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
logger.error(`Did not cleanly close client ${error}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
1
|
+
export { stdioServerAdapter } from "./stdio_server_adapter.js";
|
|
2
|
+
export { StdioServerAdapterRequestHandler } from "./stdio_server_adapter_request_handler.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Context } from "aws-lambda";
|
|
2
|
+
import { StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Lambda function adapter for MCP stdio-based server
|
|
6
|
+
*
|
|
7
|
+
* This function provides a bridge between a JSON-RPC message in a Lambda function payload
|
|
8
|
+
* and an MCP stdio server. It runs the MCP server as a child process of the function invocation,
|
|
9
|
+
* proxies the message to the MCP server, and returns the server's response.
|
|
10
|
+
* The MCP server is started and shut down on each function invocation.
|
|
11
|
+
*
|
|
12
|
+
* @param serverParams - Configuration for the stdio server (command, args, etc.)
|
|
13
|
+
* @param event - The JSON-RPC message to process
|
|
14
|
+
* @param context - AWS Lambda context
|
|
15
|
+
* @returns Promise resolving to a JSON-RPC response, error, or empty object for notifications
|
|
16
|
+
*/
|
|
17
|
+
export declare function stdioServerAdapter(serverParams: StdioServerParameters, event: JSONRPCMessage, context: Context): Promise<{}>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport, } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import { McpError, ResultSchema, ErrorCode, isJSONRPCRequest, isJSONRPCNotification, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { createLogger, format, transports } from "winston";
|
|
5
|
+
const logger = createLogger({
|
|
6
|
+
level: process.env.LOG_LEVEL?.toLowerCase() || "info",
|
|
7
|
+
format: format.simple(),
|
|
8
|
+
transports: [new transports.Console()],
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Lambda function adapter for MCP stdio-based server
|
|
12
|
+
*
|
|
13
|
+
* This function provides a bridge between a JSON-RPC message in a Lambda function payload
|
|
14
|
+
* and an MCP stdio server. It runs the MCP server as a child process of the function invocation,
|
|
15
|
+
* proxies the message to the MCP server, and returns the server's response.
|
|
16
|
+
* The MCP server is started and shut down on each function invocation.
|
|
17
|
+
*
|
|
18
|
+
* @param serverParams - Configuration for the stdio server (command, args, etc.)
|
|
19
|
+
* @param event - The JSON-RPC message to process
|
|
20
|
+
* @param context - AWS Lambda context
|
|
21
|
+
* @returns Promise resolving to a JSON-RPC response, error, or empty object for notifications
|
|
22
|
+
*/
|
|
23
|
+
export async function stdioServerAdapter(serverParams, event, context) {
|
|
24
|
+
logger.debug(`Request: ${JSON.stringify(event)}`);
|
|
25
|
+
const response = await handleMessage(serverParams, event, context);
|
|
26
|
+
logger.debug(`Response: ${JSON.stringify(response)}`);
|
|
27
|
+
return response;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Handles incoming JSON-RPC messages by determining their type and routing appropriately
|
|
31
|
+
*/
|
|
32
|
+
async function handleMessage(serverParams, event, context) {
|
|
33
|
+
// Determine the type of the message
|
|
34
|
+
if (isJSONRPCRequest(event)) {
|
|
35
|
+
return await handleRequest(serverParams, event, context);
|
|
36
|
+
}
|
|
37
|
+
else if (isJSONRPCNotification(event)) {
|
|
38
|
+
return await handleNotification(serverParams, event, context);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Invalid message format
|
|
42
|
+
return {
|
|
43
|
+
jsonrpc: event.jsonrpc,
|
|
44
|
+
id: 0,
|
|
45
|
+
error: {
|
|
46
|
+
code: ErrorCode.InvalidRequest,
|
|
47
|
+
message: "Request is neither a valid JSON-RPC request nor a valid JSON-RPC notification",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Handles JSON-RPC notifications (fire-and-forget messages)
|
|
54
|
+
*/
|
|
55
|
+
async function handleNotification(_serverParams, _event, _context) {
|
|
56
|
+
// Ignore notifications
|
|
57
|
+
logger.debug("Ignoring notification");
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Handles JSON-RPC requests by starting the MCP server and forwarding the request.
|
|
62
|
+
*/
|
|
63
|
+
async function handleRequest(serverParams, event, _context) {
|
|
64
|
+
logger.debug("Handling request");
|
|
65
|
+
const { jsonrpc, id, ...request } = event;
|
|
66
|
+
const client = new Client({
|
|
67
|
+
name: "mcp-client",
|
|
68
|
+
version: "1.0.0",
|
|
69
|
+
}, {
|
|
70
|
+
capabilities: {
|
|
71
|
+
prompts: {},
|
|
72
|
+
resources: {},
|
|
73
|
+
tools: {},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
try {
|
|
77
|
+
const transport = new StdioClientTransport(serverParams);
|
|
78
|
+
await client.connect(transport);
|
|
79
|
+
const result = await client.request(request, ResultSchema);
|
|
80
|
+
return {
|
|
81
|
+
jsonrpc: jsonrpc,
|
|
82
|
+
id: id,
|
|
83
|
+
result: result,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof McpError) {
|
|
88
|
+
logger.error(`MCP error: ${error}`);
|
|
89
|
+
return {
|
|
90
|
+
jsonrpc: jsonrpc,
|
|
91
|
+
id: id,
|
|
92
|
+
error: {
|
|
93
|
+
code: error.code,
|
|
94
|
+
message: error.message,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
logger.error(`General exception: ${error}`);
|
|
100
|
+
return {
|
|
101
|
+
jsonrpc: jsonrpc,
|
|
102
|
+
id: id,
|
|
103
|
+
error: {
|
|
104
|
+
code: 500,
|
|
105
|
+
message: "Internal failure, please check Lambda function logs",
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
try {
|
|
112
|
+
await client.close();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.error(`Did not cleanly close client ${error}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|