effortless-aws 0.33.0 → 0.34.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/{chunk-HGSMOO4A.js → chunk-6HFS224S.js} +173 -34
- package/dist/index.d.ts +230 -48
- package/dist/index.js +38 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/wrap-api.js +9 -2
- package/dist/runtime/wrap-bucket.js +14 -3
- package/dist/runtime/wrap-cron.js +1 -1
- package/dist/runtime/wrap-fifo-queue.js +1 -1
- package/dist/runtime/wrap-mcp.js +235 -0
- package/dist/runtime/wrap-table-stream.js +1 -1
- package/dist/runtime/wrap-worker.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHandlerRuntime
|
|
3
|
+
} from "../chunk-6HFS224S.js";
|
|
4
|
+
|
|
5
|
+
// src/runtime/wrap-mcp.ts
|
|
6
|
+
var ErrorCode = {
|
|
7
|
+
InvalidRequest: -32600,
|
|
8
|
+
MethodNotFound: -32601,
|
|
9
|
+
InvalidParams: -32602,
|
|
10
|
+
InternalError: -32603
|
|
11
|
+
};
|
|
12
|
+
var parseBody = (body, isBase64) => {
|
|
13
|
+
if (!body) return void 0;
|
|
14
|
+
const decoded = isBase64 ? Buffer.from(body, "base64").toString("utf-8") : body;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(decoded);
|
|
17
|
+
} catch {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var isTemplate = (uri) => /\{[^}]+\}/.test(uri);
|
|
22
|
+
var matchTemplate = (template, uri) => {
|
|
23
|
+
const paramNames = [];
|
|
24
|
+
const regexStr = template.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
25
|
+
paramNames.push(name);
|
|
26
|
+
return "([^/]+)";
|
|
27
|
+
});
|
|
28
|
+
const match = uri.match(new RegExp(`^${regexStr}$`));
|
|
29
|
+
if (!match) return null;
|
|
30
|
+
const params = {};
|
|
31
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
32
|
+
params[paramNames[i]] = decodeURIComponent(match[i + 1]);
|
|
33
|
+
}
|
|
34
|
+
return params;
|
|
35
|
+
};
|
|
36
|
+
var jsonResponse = (body, status = 200) => ({
|
|
37
|
+
statusCode: status,
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
body: JSON.stringify(body)
|
|
40
|
+
});
|
|
41
|
+
var RESOURCE_NOT_FOUND = -32002;
|
|
42
|
+
var wrapMcp = (handler) => {
|
|
43
|
+
const rt = createHandlerRuntime(handler, "mcp", handler.__spec.lambda?.logLevel ?? "info");
|
|
44
|
+
const serverName = handler.__spec.name;
|
|
45
|
+
const serverVersion = handler.__spec.version ?? "1.0.0";
|
|
46
|
+
const instructions = handler.__spec.instructions;
|
|
47
|
+
return async (event) => {
|
|
48
|
+
const startTime = Date.now();
|
|
49
|
+
rt.patchConsole();
|
|
50
|
+
let ctxProps = {};
|
|
51
|
+
try {
|
|
52
|
+
const method = event.requestContext?.http?.method ?? "GET";
|
|
53
|
+
if (method === "GET") {
|
|
54
|
+
return {
|
|
55
|
+
statusCode: 200,
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: null, result: { name: serverName, version: serverVersion, protocol: "mcp" } })
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (method !== "POST") {
|
|
61
|
+
return { statusCode: 405, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Method not allowed" }) };
|
|
62
|
+
}
|
|
63
|
+
const body = parseBody(event.body, event.isBase64Encoded ?? false);
|
|
64
|
+
if (!body || body.jsonrpc !== "2.0" || !body.method) {
|
|
65
|
+
return jsonResponse({ jsonrpc: "2.0", error: { code: ErrorCode.InvalidRequest, message: "Invalid JSON-RPC request" } });
|
|
66
|
+
}
|
|
67
|
+
const headers = event.headers ?? {};
|
|
68
|
+
const authHeader = headers["authorization"] ?? headers["Authorization"] ?? void 0;
|
|
69
|
+
const common = await rt.commonArgs(void 0, authHeader, headers);
|
|
70
|
+
const ctx = common.ctx;
|
|
71
|
+
ctxProps = ctx && typeof ctx === "object" ? { ...ctx } : {};
|
|
72
|
+
if (common.auth) {
|
|
73
|
+
const auth = common.auth;
|
|
74
|
+
if (!auth.session) {
|
|
75
|
+
return { statusCode: 401, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Unauthorized" }) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const tools = handler.tools ? handler.tools(ctxProps) : {};
|
|
79
|
+
const resourceMap = handler.resources ? handler.resources(ctxProps) : {};
|
|
80
|
+
const prompts = handler.prompts ? handler.prompts(ctxProps) : {};
|
|
81
|
+
const response = await handleMethod(body, ctxProps, tools, resourceMap, prompts, serverName, serverVersion, instructions);
|
|
82
|
+
const logPayload = "result" in response ? response.result : response.error;
|
|
83
|
+
rt.logExecution(startTime, { method: body.method, id: body.id }, logPayload);
|
|
84
|
+
return jsonResponse(response);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
rt.logError(startTime, { method: "unknown" }, error);
|
|
87
|
+
if (handler.onError) {
|
|
88
|
+
try {
|
|
89
|
+
await handler.onError({ error, toolName: "unknown", ...ctxProps });
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return jsonResponse({ jsonrpc: "2.0", error: { code: ErrorCode.InternalError, message: "Internal server error" } });
|
|
94
|
+
} finally {
|
|
95
|
+
if (handler.onCleanup) {
|
|
96
|
+
try {
|
|
97
|
+
await handler.onCleanup(ctxProps);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error(`[effortless:${rt.handlerName}] onCleanup error`, e);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
rt.restoreConsole();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
async function handleMethod(req, ctx, tools, resourceMap, prompts, serverName, serverVersion, instructions) {
|
|
107
|
+
switch (req.method) {
|
|
108
|
+
case "initialize":
|
|
109
|
+
return {
|
|
110
|
+
jsonrpc: "2.0",
|
|
111
|
+
id: req.id,
|
|
112
|
+
result: {
|
|
113
|
+
protocolVersion: "2025-03-26",
|
|
114
|
+
capabilities: {
|
|
115
|
+
...Object.keys(tools).length > 0 ? { tools: {} } : {},
|
|
116
|
+
...Object.keys(resourceMap).length > 0 ? { resources: {} } : {},
|
|
117
|
+
...Object.keys(prompts).length > 0 ? { prompts: {} } : {}
|
|
118
|
+
},
|
|
119
|
+
serverInfo: {
|
|
120
|
+
name: serverName,
|
|
121
|
+
version: serverVersion
|
|
122
|
+
},
|
|
123
|
+
...instructions ? { instructions } : {}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
case "notifications/initialized":
|
|
127
|
+
return { jsonrpc: "2.0", id: req.id, result: {} };
|
|
128
|
+
case "ping":
|
|
129
|
+
return { jsonrpc: "2.0", id: req.id, result: {} };
|
|
130
|
+
case "notifications/cancelled":
|
|
131
|
+
return { jsonrpc: "2.0", id: req.id, result: {} };
|
|
132
|
+
// ── Tools ──
|
|
133
|
+
case "tools/list":
|
|
134
|
+
return {
|
|
135
|
+
jsonrpc: "2.0",
|
|
136
|
+
id: req.id,
|
|
137
|
+
result: {
|
|
138
|
+
tools: Object.entries(tools).map(([name, def]) => ({
|
|
139
|
+
name,
|
|
140
|
+
description: def.description,
|
|
141
|
+
inputSchema: def.input
|
|
142
|
+
}))
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
case "tools/call": {
|
|
146
|
+
const toolName = req.params?.name;
|
|
147
|
+
if (!toolName || !tools[toolName]) {
|
|
148
|
+
return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InvalidParams, message: `Unknown tool: ${toolName}` } };
|
|
149
|
+
}
|
|
150
|
+
const tool = tools[toolName];
|
|
151
|
+
try {
|
|
152
|
+
const result = await tool.handler(req.params?.arguments ?? {}, ctx);
|
|
153
|
+
return { jsonrpc: "2.0", id: req.id, result };
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return {
|
|
156
|
+
jsonrpc: "2.0",
|
|
157
|
+
id: req.id,
|
|
158
|
+
result: {
|
|
159
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
160
|
+
isError: true
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ── Resources ──
|
|
166
|
+
case "resources/list": {
|
|
167
|
+
const resources = Object.entries(resourceMap).filter(([uri]) => !isTemplate(uri)).map(([uri, def]) => ({
|
|
168
|
+
uri,
|
|
169
|
+
name: def.name,
|
|
170
|
+
...def.description ? { description: def.description } : {},
|
|
171
|
+
...def.mimeType ? { mimeType: def.mimeType } : {}
|
|
172
|
+
}));
|
|
173
|
+
return { jsonrpc: "2.0", id: req.id, result: { resources } };
|
|
174
|
+
}
|
|
175
|
+
case "resources/templates/list": {
|
|
176
|
+
const resourceTemplates = Object.entries(resourceMap).filter(([uri]) => isTemplate(uri)).map(([uriTemplate, def]) => ({
|
|
177
|
+
uriTemplate,
|
|
178
|
+
name: def.name,
|
|
179
|
+
...def.description ? { description: def.description } : {},
|
|
180
|
+
...def.mimeType ? { mimeType: def.mimeType } : {}
|
|
181
|
+
}));
|
|
182
|
+
return { jsonrpc: "2.0", id: req.id, result: { resourceTemplates } };
|
|
183
|
+
}
|
|
184
|
+
case "resources/read": {
|
|
185
|
+
const uri = req.params?.uri;
|
|
186
|
+
if (!uri) {
|
|
187
|
+
return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InvalidParams, message: "Missing uri parameter" } };
|
|
188
|
+
}
|
|
189
|
+
const staticDef = resourceMap[uri];
|
|
190
|
+
if (staticDef && !isTemplate(uri)) {
|
|
191
|
+
const result = await staticDef.handler(ctx);
|
|
192
|
+
const contents = Array.isArray(result) ? result : [result];
|
|
193
|
+
return { jsonrpc: "2.0", id: req.id, result: { contents } };
|
|
194
|
+
}
|
|
195
|
+
for (const [template, def] of Object.entries(resourceMap)) {
|
|
196
|
+
if (!isTemplate(template)) continue;
|
|
197
|
+
const params = matchTemplate(template, uri);
|
|
198
|
+
if (params) {
|
|
199
|
+
const result = await def.handler(params, ctx);
|
|
200
|
+
const contents = Array.isArray(result) ? result : [result];
|
|
201
|
+
return { jsonrpc: "2.0", id: req.id, result: { contents } };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { jsonrpc: "2.0", id: req.id, error: { code: RESOURCE_NOT_FOUND, message: "Resource not found", data: { uri } } };
|
|
205
|
+
}
|
|
206
|
+
// ── Prompts ──
|
|
207
|
+
case "prompts/list": {
|
|
208
|
+
const promptList = Object.entries(prompts).map(([name, def]) => ({
|
|
209
|
+
name,
|
|
210
|
+
...def.description ? { description: def.description } : {},
|
|
211
|
+
...def.arguments ? { arguments: def.arguments } : {}
|
|
212
|
+
}));
|
|
213
|
+
return { jsonrpc: "2.0", id: req.id, result: { prompts: promptList } };
|
|
214
|
+
}
|
|
215
|
+
case "prompts/get": {
|
|
216
|
+
const promptName = req.params?.name;
|
|
217
|
+
if (!promptName || !prompts[promptName]) {
|
|
218
|
+
return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InvalidParams, message: `Unknown prompt: ${promptName}` } };
|
|
219
|
+
}
|
|
220
|
+
const prompt = prompts[promptName];
|
|
221
|
+
const args = req.params?.arguments ?? {};
|
|
222
|
+
try {
|
|
223
|
+
const result = await prompt.handler(args, ctx);
|
|
224
|
+
return { jsonrpc: "2.0", id: req.id, result };
|
|
225
|
+
} catch (error) {
|
|
226
|
+
return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InternalError, message: error instanceof Error ? error.message : String(error) } };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.MethodNotFound, message: `Unknown method: ${req.method}` } };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export {
|
|
234
|
+
wrapMcp
|
|
235
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effortless-aws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Code-first AWS Lambda framework. Export handlers, deploy with one command.",
|
|
@@ -43,11 +43,12 @@
|
|
|
43
43
|
"@aws-sdk/client-ssm": "^3.985.0",
|
|
44
44
|
"@aws-sdk/util-dynamodb": "^3.975.0",
|
|
45
45
|
"@types/aws-lambda": "^8.10.144",
|
|
46
|
-
"@types/node": "
|
|
46
|
+
"@types/node": "22.19.9",
|
|
47
47
|
"tsup": "^8.5.1",
|
|
48
48
|
"type-fest": "^4.22.1",
|
|
49
49
|
"typescript": "^5.9.3",
|
|
50
50
|
"vite-tsconfig-paths": "^6.1.1",
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
52
|
"vitest": "^4.0.18"
|
|
52
53
|
},
|
|
53
54
|
"scripts": {
|