effortless-aws 0.33.1 → 0.35.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.
@@ -0,0 +1,292 @@
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 isStandardSchema = (input) => input != null && typeof input === "object" && "~standard" in input && typeof input["~standard"]?.jsonSchema?.input === "function";
43
+ var validateInput = async (schema, rawArgs, toolName) => {
44
+ const validate = schema["~standard"].validate;
45
+ if (typeof validate !== "function") return rawArgs;
46
+ const result = await validate(rawArgs);
47
+ if (result.issues) {
48
+ const messages = result.issues.map((i) => {
49
+ const path = i.path?.map((p) => typeof p === "object" ? p.key : p).join(".");
50
+ return path ? `${path}: ${i.message}` : i.message;
51
+ });
52
+ throw new Error(`Validation failed for tool "${toolName}": ${messages.join("; ")}`);
53
+ }
54
+ return result.value;
55
+ };
56
+ var wrapResourceResult = (raw, uri) => {
57
+ if (raw == null) return [{ uri, text: "null" }];
58
+ if (Array.isArray(raw)) {
59
+ if (raw.length > 0 && typeof raw[0] === "object" && raw[0] !== null && "uri" in raw[0]) return raw;
60
+ return [{ uri, text: JSON.stringify(raw) }];
61
+ }
62
+ if (typeof raw === "object" && "uri" in raw && ("text" in raw || "blob" in raw)) {
63
+ return [raw];
64
+ }
65
+ const text = typeof raw === "string" ? raw : JSON.stringify(raw);
66
+ return [{ uri, text }];
67
+ };
68
+ var wrapToolResult = (raw) => {
69
+ if (raw != null && typeof raw === "object" && "content" in raw && Array.isArray(raw.content)) {
70
+ return raw;
71
+ }
72
+ const text = typeof raw === "string" ? raw : JSON.stringify(raw ?? null);
73
+ return { content: [{ type: "text", text }] };
74
+ };
75
+ var wrapMcp = (handler) => {
76
+ const rt = createHandlerRuntime(handler, "mcp", handler.__spec.lambda?.logLevel ?? "info");
77
+ const serverName = handler.__spec.name;
78
+ const serverVersion = handler.__spec.version ?? "1.0.0";
79
+ const instructions = handler.__spec.instructions;
80
+ return async (event) => {
81
+ const startTime = Date.now();
82
+ rt.patchConsole();
83
+ let ctxProps = {};
84
+ try {
85
+ const method = event.requestContext?.http?.method ?? "GET";
86
+ if (method === "GET") {
87
+ return {
88
+ statusCode: 200,
89
+ headers: { "Content-Type": "application/json" },
90
+ body: JSON.stringify({ jsonrpc: "2.0", id: null, result: { name: serverName, version: serverVersion, protocol: "mcp" } })
91
+ };
92
+ }
93
+ if (method !== "POST") {
94
+ return { statusCode: 405, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Method not allowed" }) };
95
+ }
96
+ const body = parseBody(event.body, event.isBase64Encoded ?? false);
97
+ if (!body || body.jsonrpc !== "2.0" || !body.method) {
98
+ return jsonResponse({ jsonrpc: "2.0", error: { code: ErrorCode.InvalidRequest, message: "Invalid JSON-RPC request" } });
99
+ }
100
+ const headers = event.headers ?? {};
101
+ const authHeader = headers["authorization"] ?? headers["Authorization"] ?? void 0;
102
+ const common = await rt.commonArgs(void 0, authHeader, headers);
103
+ const ctx = common.ctx;
104
+ ctxProps = ctx && typeof ctx === "object" ? { ...ctx } : {};
105
+ if (common.auth) {
106
+ const auth = common.auth;
107
+ if (!auth.session) {
108
+ return { statusCode: 401, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Unauthorized" }) };
109
+ }
110
+ }
111
+ const tools = handler.tools ? handler.tools(ctxProps) : {};
112
+ const resourceMap = handler.resources ? handler.resources(ctxProps) : {};
113
+ const prompts = handler.prompts ? handler.prompts(ctxProps) : {};
114
+ const response = await handleMethod(body, ctxProps, tools, resourceMap, prompts, serverName, serverVersion, instructions);
115
+ const logPayload = "result" in response ? response.result : response.error;
116
+ rt.logExecution(startTime, { method: body.method, id: body.id }, logPayload);
117
+ return jsonResponse(response);
118
+ } catch (error) {
119
+ rt.logError(startTime, { method: "unknown" }, error);
120
+ if (handler.onError) {
121
+ try {
122
+ await handler.onError({ error, toolName: "unknown", ...ctxProps });
123
+ } catch {
124
+ }
125
+ }
126
+ return jsonResponse({ jsonrpc: "2.0", error: { code: ErrorCode.InternalError, message: "Internal server error" } });
127
+ } finally {
128
+ if (handler.onCleanup) {
129
+ try {
130
+ await handler.onCleanup(ctxProps);
131
+ } catch (e) {
132
+ console.error(`[effortless:${rt.handlerName}] onCleanup error`, e);
133
+ }
134
+ }
135
+ rt.restoreConsole();
136
+ }
137
+ };
138
+ };
139
+ async function handleMethod(req, ctx, tools, resourceMap, prompts, serverName, serverVersion, instructions) {
140
+ switch (req.method) {
141
+ case "initialize":
142
+ return {
143
+ jsonrpc: "2.0",
144
+ id: req.id,
145
+ result: {
146
+ protocolVersion: "2025-03-26",
147
+ capabilities: {
148
+ ...Object.keys(tools).length > 0 ? { tools: {} } : {},
149
+ ...Object.keys(resourceMap).length > 0 ? { resources: {} } : {},
150
+ ...Object.keys(prompts).length > 0 ? { prompts: {} } : {}
151
+ },
152
+ serverInfo: {
153
+ name: serverName,
154
+ version: serverVersion
155
+ },
156
+ ...instructions ? { instructions } : {}
157
+ }
158
+ };
159
+ case "notifications/initialized":
160
+ return { jsonrpc: "2.0", id: req.id, result: {} };
161
+ case "ping":
162
+ return { jsonrpc: "2.0", id: req.id, result: {} };
163
+ case "notifications/cancelled":
164
+ return { jsonrpc: "2.0", id: req.id, result: {} };
165
+ // ── Tools ──
166
+ case "tools/list":
167
+ return {
168
+ jsonrpc: "2.0",
169
+ id: req.id,
170
+ result: {
171
+ tools: Object.entries(tools).map(([name, def]) => ({
172
+ name,
173
+ description: def.description,
174
+ inputSchema: isStandardSchema(def.input) ? def.input["~standard"].jsonSchema.input({ target: "draft-07" }) : def.input
175
+ }))
176
+ }
177
+ };
178
+ case "tools/call": {
179
+ const toolName = req.params?.name;
180
+ if (!toolName || !tools[toolName]) {
181
+ return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InvalidParams, message: `Unknown tool: ${toolName}` } };
182
+ }
183
+ const tool = tools[toolName];
184
+ try {
185
+ const rawArgs = req.params?.arguments ?? {};
186
+ const input = isStandardSchema(tool.input) ? await validateInput(tool.input, rawArgs, toolName) : rawArgs;
187
+ const raw = await tool.handler(input, ctx);
188
+ return { jsonrpc: "2.0", id: req.id, result: wrapToolResult(raw) };
189
+ } catch (error) {
190
+ return {
191
+ jsonrpc: "2.0",
192
+ id: req.id,
193
+ result: {
194
+ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
195
+ isError: true
196
+ }
197
+ };
198
+ }
199
+ }
200
+ // ── Resources ──
201
+ case "resources/list": {
202
+ const resources = Object.entries(resourceMap).filter(([uri]) => !isTemplate(uri)).map(([uri, def]) => ({
203
+ uri,
204
+ name: def.name,
205
+ ...def.description ? { description: def.description } : {},
206
+ ...def.mimeType ? { mimeType: def.mimeType } : {}
207
+ }));
208
+ return { jsonrpc: "2.0", id: req.id, result: { resources } };
209
+ }
210
+ case "resources/templates/list": {
211
+ const resourceTemplates = Object.entries(resourceMap).filter(([uri]) => isTemplate(uri)).map(([uriTemplate, def]) => ({
212
+ uriTemplate,
213
+ name: def.name,
214
+ ...def.description ? { description: def.description } : {},
215
+ ...def.mimeType ? { mimeType: def.mimeType } : {}
216
+ }));
217
+ return { jsonrpc: "2.0", id: req.id, result: { resourceTemplates } };
218
+ }
219
+ case "resources/read": {
220
+ const uri = req.params?.uri;
221
+ if (!uri) {
222
+ return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InvalidParams, message: "Missing uri parameter" } };
223
+ }
224
+ const staticDef = resourceMap[uri];
225
+ if (staticDef && !isTemplate(uri)) {
226
+ const raw = await staticDef.handler(ctx);
227
+ return { jsonrpc: "2.0", id: req.id, result: { contents: wrapResourceResult(raw, uri) } };
228
+ }
229
+ for (const [template, def] of Object.entries(resourceMap)) {
230
+ if (!isTemplate(template)) continue;
231
+ const rawParams = matchTemplate(template, uri);
232
+ if (rawParams) {
233
+ const defAny = def;
234
+ const params = defAny.params && typeof defAny.params === "object" && "~standard" in defAny.params ? await validateInput(defAny.params, rawParams, `resource ${template}`) : rawParams;
235
+ const raw = await def.handler(params, ctx);
236
+ return { jsonrpc: "2.0", id: req.id, result: { contents: wrapResourceResult(raw, uri) } };
237
+ }
238
+ }
239
+ return { jsonrpc: "2.0", id: req.id, error: { code: RESOURCE_NOT_FOUND, message: "Resource not found", data: { uri } } };
240
+ }
241
+ // ── Prompts ──
242
+ case "prompts/list": {
243
+ const promptList = Object.entries(prompts).map(([name, def]) => {
244
+ const defAny = def;
245
+ let args;
246
+ if (defAny.args && typeof defAny.args === "object" && "~standard" in defAny.args) {
247
+ const jsonSchema = defAny.args["~standard"].jsonSchema?.input?.({ target: "draft-07" });
248
+ if (jsonSchema?.properties) {
249
+ const required = new Set(jsonSchema.required ?? []);
250
+ args = Object.entries(jsonSchema.properties).map(([key, prop]) => ({
251
+ name: key,
252
+ ...prop.description ? { description: prop.description } : {},
253
+ ...required.has(key) ? { required: true } : {}
254
+ }));
255
+ }
256
+ } else if (Array.isArray(defAny.args)) {
257
+ args = defAny.args;
258
+ } else if (defAny.arguments) {
259
+ args = defAny.arguments;
260
+ }
261
+ return {
262
+ name,
263
+ ...def.description ? { description: def.description } : {},
264
+ ...args ? { arguments: args } : {}
265
+ };
266
+ });
267
+ return { jsonrpc: "2.0", id: req.id, result: { prompts: promptList } };
268
+ }
269
+ case "prompts/get": {
270
+ const promptName = req.params?.name;
271
+ if (!promptName || !prompts[promptName]) {
272
+ return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InvalidParams, message: `Unknown prompt: ${promptName}` } };
273
+ }
274
+ const prompt = prompts[promptName];
275
+ const rawArgs = req.params?.arguments ?? {};
276
+ try {
277
+ const defAny = prompt;
278
+ const args = defAny.args && typeof defAny.args === "object" && "~standard" in defAny.args ? await validateInput(defAny.args, rawArgs, `prompt ${promptName}`) : rawArgs;
279
+ const raw = await prompt.handler(args, ctx);
280
+ const result = typeof raw === "string" ? { messages: [{ role: "user", content: { type: "text", text: raw } }] } : raw;
281
+ return { jsonrpc: "2.0", id: req.id, result };
282
+ } catch (error) {
283
+ return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InternalError, message: error instanceof Error ? error.message : String(error) } };
284
+ }
285
+ }
286
+ default:
287
+ return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.MethodNotFound, message: `Unknown method: ${req.method}` } };
288
+ }
289
+ }
290
+ export {
291
+ wrapMcp
292
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effortless-aws",
3
- "version": "0.33.1",
3
+ "version": "0.35.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Code-first AWS Lambda framework. Export handlers, deploy with one command.",
@@ -48,6 +48,8 @@
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",
52
+ "@standard-schema/spec": "^1.1.0",
51
53
  "vitest": "^4.0.18"
52
54
  },
53
55
  "scripts": {