@matthesketh/utopia-ai 0.7.0 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import { d as ChatRequest, e as ChatResponse, C as ChatChunk, E as EmbeddingRequest, f as EmbeddingResponse, c as ChatMessage, l as ToolDefinition, j as ToolCall, a as AIHooks, R as RetryConfig, A as AIAdapter } from './types-FSnS43LM.js';
1
+ import { d as ChatRequest, e as ChatResponse, C as ChatChunk, E as EmbeddingRequest, f as EmbeddingResponse, c as ChatMessage, l as ToolDefinition, j as ToolCall, a as AIHooks, R as RetryConfig, A as AIAdapter } from './types-FSnS43LM';
2
2
 
3
3
  interface AI {
4
4
  /** Send a chat completion request. */
package/dist/index.cjs CHANGED
@@ -256,6 +256,7 @@ async function* parseSSEStream(response) {
256
256
  try {
257
257
  yield JSON.parse(data);
258
258
  } catch {
259
+ console.warn("[utopia-ai] Skipping malformed SSE data:", data);
259
260
  }
260
261
  }
261
262
  }
package/dist/index.js CHANGED
@@ -227,6 +227,7 @@ async function* parseSSEStream(response) {
227
227
  try {
228
228
  yield JSON.parse(data);
229
229
  } catch {
230
+ console.warn("[utopia-ai] Skipping malformed SSE data:", data);
230
231
  }
231
232
  }
232
233
  }
@@ -43,19 +43,32 @@ function createMCPServer(config) {
43
43
  (config.prompts ?? []).map((p) => [p.definition.name, p])
44
44
  );
45
45
  async function handleRequest(request) {
46
+ if (!request || request.jsonrpc !== "2.0" || typeof request.method !== "string") {
47
+ return {
48
+ jsonrpc: "2.0",
49
+ id: request?.id ?? null,
50
+ error: {
51
+ code: -32600,
52
+ message: 'Invalid Request: must include jsonrpc "2.0" and a string method'
53
+ }
54
+ };
55
+ }
46
56
  try {
47
57
  const result = await dispatch(request);
48
58
  return { jsonrpc: "2.0", id: request.id, result };
49
59
  } catch (err) {
50
60
  const rpcErr = err;
61
+ if (typeof rpcErr.code === "number") {
62
+ return {
63
+ jsonrpc: "2.0",
64
+ id: request.id,
65
+ error: { code: rpcErr.code, message: rpcErr.message ?? "Error", data: rpcErr.data }
66
+ };
67
+ }
51
68
  return {
52
69
  jsonrpc: "2.0",
53
70
  id: request.id,
54
- error: {
55
- code: rpcErr.code ?? -32603,
56
- message: rpcErr.message ?? "Internal error",
57
- data: rpcErr.data
58
- }
71
+ error: { code: -32603, message: "Internal error" }
59
72
  };
60
73
  }
61
74
  }
@@ -83,12 +96,22 @@ function createMCPServer(config) {
83
96
  }))
84
97
  };
85
98
  case "tools/call": {
86
- const params = request.params;
87
- const tool = toolMap.get(params.name);
99
+ const params = asParamsObject(request.params, "tools/call");
100
+ const name = params.name;
101
+ if (typeof name !== "string") {
102
+ throw makeError(-32602, 'tools/call requires a string "name"');
103
+ }
104
+ const tool = toolMap.get(name);
88
105
  if (!tool) {
89
- throw makeError(-32602, `Unknown tool: ${params.name}`);
106
+ throw makeError(-32602, `Unknown tool: ${name}`);
107
+ }
108
+ const args = params.arguments ?? {};
109
+ if (typeof args !== "object" || args === null || Array.isArray(args)) {
110
+ throw makeError(-32602, "tool arguments must be an object");
90
111
  }
91
- return tool.handler(params.arguments ?? {});
112
+ const argRecord = args;
113
+ validateAgainstSchema(tool.definition.inputSchema, argRecord, name);
114
+ return tool.handler(argRecord);
92
115
  }
93
116
  case "resources/list":
94
117
  return {
@@ -100,12 +123,16 @@ function createMCPServer(config) {
100
123
  }))
101
124
  };
102
125
  case "resources/read": {
103
- const params = request.params;
104
- const resource = findResource(params.uri);
126
+ const params = asParamsObject(request.params, "resources/read");
127
+ const uri = params.uri;
128
+ if (typeof uri !== "string") {
129
+ throw makeError(-32602, 'resources/read requires a string "uri"');
130
+ }
131
+ const resource = findResource(uri);
105
132
  if (!resource) {
106
- throw makeError(-32602, `Unknown resource: ${params.uri}`);
133
+ throw makeError(-32602, `Unknown resource: ${uri}`);
107
134
  }
108
- const content = await resource.handler(params.uri);
135
+ const content = await resource.handler(uri);
109
136
  return { contents: [content] };
110
137
  }
111
138
  case "prompts/list":
@@ -117,12 +144,17 @@ function createMCPServer(config) {
117
144
  }))
118
145
  };
119
146
  case "prompts/get": {
120
- const params = request.params;
121
- const prompt = promptMap.get(params.name);
147
+ const params = asParamsObject(request.params, "prompts/get");
148
+ const name = params.name;
149
+ if (typeof name !== "string") {
150
+ throw makeError(-32602, 'prompts/get requires a string "name"');
151
+ }
152
+ const prompt = promptMap.get(name);
122
153
  if (!prompt) {
123
- throw makeError(-32602, `Unknown prompt: ${params.name}`);
154
+ throw makeError(-32602, `Unknown prompt: ${name}`);
124
155
  }
125
- return prompt.handler(params.arguments ?? {});
156
+ const args = params.arguments ?? {};
157
+ return prompt.handler(args);
126
158
  }
127
159
  case "ping":
128
160
  return {};
@@ -149,10 +181,46 @@ function matchesTemplate(pattern, uri) {
149
181
  function makeError(code, message) {
150
182
  return { code, message };
151
183
  }
184
+ function asParamsObject(params, ctx) {
185
+ if (params === null || typeof params !== "object" || Array.isArray(params)) {
186
+ throw makeError(-32602, `Invalid params for ${ctx}: expected an object`);
187
+ }
188
+ return params;
189
+ }
190
+ function validateAgainstSchema(schema, args, toolName) {
191
+ if (!schema || typeof schema !== "object") return;
192
+ const s = schema;
193
+ if (Array.isArray(s.required)) {
194
+ for (const key of s.required) {
195
+ if (typeof key === "string" && !(key in args)) {
196
+ throw makeError(-32602, `Missing required argument "${key}" for tool "${toolName}"`);
197
+ }
198
+ }
199
+ }
200
+ if (s.properties && typeof s.properties === "object") {
201
+ for (const [key, prop] of Object.entries(s.properties)) {
202
+ const value = args[key];
203
+ if (value === void 0 || value === null) continue;
204
+ const expected = prop?.type;
205
+ if (!expected) continue;
206
+ const ok = expected === "number" || expected === "integer" ? typeof value === "number" : expected === "array" ? Array.isArray(value) : expected === "object" ? typeof value === "object" && !Array.isArray(value) : typeof value === expected;
207
+ if (!ok) {
208
+ throw makeError(
209
+ -32602,
210
+ `Argument "${key}" for tool "${toolName}" must be of type ${expected}`
211
+ );
212
+ }
213
+ }
214
+ }
215
+ }
152
216
 
153
217
  // src/mcp/client.ts
154
218
  function createMCPClient(config) {
155
219
  let requestId = 0;
220
+ const parsedUrl = new URL(config.url);
221
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
222
+ throw new Error(`MCP client URL must be http(s): ${config.url}`);
223
+ }
156
224
  async function rpc(method, params) {
157
225
  const request = {
158
226
  jsonrpc: "2.0",
@@ -167,6 +235,10 @@ function createMCPClient(config) {
167
235
  ...config.headers
168
236
  },
169
237
  body: JSON.stringify(request),
238
+ // never follow redirects: a 3xx to another host would otherwise re-send
239
+ // config.headers (often a bearer token) to that host, leaking the
240
+ // credential and enabling an ssrf pivot.
241
+ redirect: "error",
170
242
  signal: AbortSignal.timeout(3e4)
171
243
  });
172
244
  if (!response.ok) {
@@ -239,16 +311,45 @@ function createMCPClient(config) {
239
311
 
240
312
  // src/mcp/handler.ts
241
313
  function createMCPHandler(server, options) {
242
- const corsOrigin = options?.corsOrigin ?? "*";
314
+ const corsOrigin = options?.corsOrigin;
315
+ const allowedOrigins = options?.allowedOrigins;
316
+ const authorize = options?.authorize;
243
317
  return async (req, res) => {
244
- res.setHeader("Access-Control-Allow-Origin", corsOrigin);
245
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
246
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
318
+ if (corsOrigin) {
319
+ res.setHeader("Access-Control-Allow-Origin", corsOrigin);
320
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
321
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
322
+ }
247
323
  if (req.method === "OPTIONS") {
248
324
  res.writeHead(204);
249
325
  res.end();
250
326
  return;
251
327
  }
328
+ const origin = req.headers.origin;
329
+ if (allowedOrigins && origin !== void 0 && !allowedOrigins.includes(origin)) {
330
+ res.writeHead(403, { "Content-Type": "text/plain" });
331
+ res.end("Forbidden");
332
+ return;
333
+ }
334
+ if (authorize) {
335
+ let allowed = false;
336
+ try {
337
+ allowed = await authorize(req);
338
+ } catch {
339
+ allowed = false;
340
+ }
341
+ if (!allowed) {
342
+ res.writeHead(401, { "Content-Type": "application/json" });
343
+ res.end(
344
+ JSON.stringify({
345
+ jsonrpc: "2.0",
346
+ id: null,
347
+ error: { code: -32600, message: "Unauthorized" }
348
+ })
349
+ );
350
+ return;
351
+ }
352
+ }
252
353
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
253
354
  if (url.pathname.endsWith("/sse") && req.method === "GET") {
254
355
  handleSSE(server, req, res);
@@ -270,6 +371,18 @@ async function handlePost(server, req, res) {
270
371
  res.writeHead(200, { "Content-Type": "application/json" });
271
372
  res.end(JSON.stringify(response));
272
373
  } catch (err) {
374
+ const errObj = err;
375
+ if (errObj.statusCode === 413) {
376
+ res.writeHead(413, { "Content-Type": "application/json" });
377
+ res.end(
378
+ JSON.stringify({
379
+ jsonrpc: "2.0",
380
+ id: null,
381
+ error: { code: -32600, message: "Payload too large" }
382
+ })
383
+ );
384
+ return;
385
+ }
273
386
  const data = err instanceof SyntaxError ? err.message : "Invalid request";
274
387
  res.writeHead(400, { "Content-Type": "application/json" });
275
388
  res.end(
@@ -303,10 +416,20 @@ data: ${endpointUrl}
303
416
  clearInterval(keepAlive);
304
417
  });
305
418
  }
306
- function readBody(req) {
419
+ var DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
420
+ function readBody(req, maxSize = DEFAULT_MAX_BODY_SIZE) {
307
421
  return new Promise((resolve, reject) => {
308
422
  const chunks = [];
309
- req.on("data", (chunk) => chunks.push(chunk));
423
+ let size = 0;
424
+ req.on("data", (chunk) => {
425
+ size += chunk.length;
426
+ if (size > maxSize) {
427
+ req.removeAllListeners("data");
428
+ reject(Object.assign(new Error("Payload too large"), { statusCode: 413 }));
429
+ return;
430
+ }
431
+ chunks.push(chunk);
432
+ });
310
433
  req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
311
434
  req.on("error", reject);
312
435
  });
@@ -174,6 +174,19 @@ declare function createMCPClient(config: MCPClientConfig): MCPClient;
174
174
 
175
175
  interface MCPHandlerOptions {
176
176
  corsOrigin?: string;
177
+ /**
178
+ * allow-list of permitted `Origin` header values. when set, any request
179
+ * carrying an `Origin` not in the list is rejected with 403. this is the
180
+ * primary defence against dns-rebinding attacks on a locally-bound server.
181
+ */
182
+ allowedOrigins?: string[];
183
+ /**
184
+ * authorisation gate run before any request is dispatched to the server.
185
+ * return false (or throw) to reject with 401. an mcp server exposes tool
186
+ * execution, so it must not be reachable on a network without an authz
187
+ * check — there is intentionally no default-allow for remote callers.
188
+ */
189
+ authorize?: (req: IncomingMessage) => boolean | Promise<boolean>;
177
190
  }
178
191
  /**
179
192
  * Create a Node.js HTTP handler for an MCP server.
@@ -174,6 +174,19 @@ declare function createMCPClient(config: MCPClientConfig): MCPClient;
174
174
 
175
175
  interface MCPHandlerOptions {
176
176
  corsOrigin?: string;
177
+ /**
178
+ * allow-list of permitted `Origin` header values. when set, any request
179
+ * carrying an `Origin` not in the list is rejected with 403. this is the
180
+ * primary defence against dns-rebinding attacks on a locally-bound server.
181
+ */
182
+ allowedOrigins?: string[];
183
+ /**
184
+ * authorisation gate run before any request is dispatched to the server.
185
+ * return false (or throw) to reject with 401. an mcp server exposes tool
186
+ * execution, so it must not be reachable on a network without an authz
187
+ * check — there is intentionally no default-allow for remote callers.
188
+ */
189
+ authorize?: (req: IncomingMessage) => boolean | Promise<boolean>;
177
190
  }
178
191
  /**
179
192
  * Create a Node.js HTTP handler for an MCP server.
package/dist/mcp/index.js CHANGED
@@ -15,19 +15,32 @@ function createMCPServer(config) {
15
15
  (config.prompts ?? []).map((p) => [p.definition.name, p])
16
16
  );
17
17
  async function handleRequest(request) {
18
+ if (!request || request.jsonrpc !== "2.0" || typeof request.method !== "string") {
19
+ return {
20
+ jsonrpc: "2.0",
21
+ id: request?.id ?? null,
22
+ error: {
23
+ code: -32600,
24
+ message: 'Invalid Request: must include jsonrpc "2.0" and a string method'
25
+ }
26
+ };
27
+ }
18
28
  try {
19
29
  const result = await dispatch(request);
20
30
  return { jsonrpc: "2.0", id: request.id, result };
21
31
  } catch (err) {
22
32
  const rpcErr = err;
33
+ if (typeof rpcErr.code === "number") {
34
+ return {
35
+ jsonrpc: "2.0",
36
+ id: request.id,
37
+ error: { code: rpcErr.code, message: rpcErr.message ?? "Error", data: rpcErr.data }
38
+ };
39
+ }
23
40
  return {
24
41
  jsonrpc: "2.0",
25
42
  id: request.id,
26
- error: {
27
- code: rpcErr.code ?? -32603,
28
- message: rpcErr.message ?? "Internal error",
29
- data: rpcErr.data
30
- }
43
+ error: { code: -32603, message: "Internal error" }
31
44
  };
32
45
  }
33
46
  }
@@ -55,12 +68,22 @@ function createMCPServer(config) {
55
68
  }))
56
69
  };
57
70
  case "tools/call": {
58
- const params = request.params;
59
- const tool = toolMap.get(params.name);
71
+ const params = asParamsObject(request.params, "tools/call");
72
+ const name = params.name;
73
+ if (typeof name !== "string") {
74
+ throw makeError(-32602, 'tools/call requires a string "name"');
75
+ }
76
+ const tool = toolMap.get(name);
60
77
  if (!tool) {
61
- throw makeError(-32602, `Unknown tool: ${params.name}`);
78
+ throw makeError(-32602, `Unknown tool: ${name}`);
79
+ }
80
+ const args = params.arguments ?? {};
81
+ if (typeof args !== "object" || args === null || Array.isArray(args)) {
82
+ throw makeError(-32602, "tool arguments must be an object");
62
83
  }
63
- return tool.handler(params.arguments ?? {});
84
+ const argRecord = args;
85
+ validateAgainstSchema(tool.definition.inputSchema, argRecord, name);
86
+ return tool.handler(argRecord);
64
87
  }
65
88
  case "resources/list":
66
89
  return {
@@ -72,12 +95,16 @@ function createMCPServer(config) {
72
95
  }))
73
96
  };
74
97
  case "resources/read": {
75
- const params = request.params;
76
- const resource = findResource(params.uri);
98
+ const params = asParamsObject(request.params, "resources/read");
99
+ const uri = params.uri;
100
+ if (typeof uri !== "string") {
101
+ throw makeError(-32602, 'resources/read requires a string "uri"');
102
+ }
103
+ const resource = findResource(uri);
77
104
  if (!resource) {
78
- throw makeError(-32602, `Unknown resource: ${params.uri}`);
105
+ throw makeError(-32602, `Unknown resource: ${uri}`);
79
106
  }
80
- const content = await resource.handler(params.uri);
107
+ const content = await resource.handler(uri);
81
108
  return { contents: [content] };
82
109
  }
83
110
  case "prompts/list":
@@ -89,12 +116,17 @@ function createMCPServer(config) {
89
116
  }))
90
117
  };
91
118
  case "prompts/get": {
92
- const params = request.params;
93
- const prompt = promptMap.get(params.name);
119
+ const params = asParamsObject(request.params, "prompts/get");
120
+ const name = params.name;
121
+ if (typeof name !== "string") {
122
+ throw makeError(-32602, 'prompts/get requires a string "name"');
123
+ }
124
+ const prompt = promptMap.get(name);
94
125
  if (!prompt) {
95
- throw makeError(-32602, `Unknown prompt: ${params.name}`);
126
+ throw makeError(-32602, `Unknown prompt: ${name}`);
96
127
  }
97
- return prompt.handler(params.arguments ?? {});
128
+ const args = params.arguments ?? {};
129
+ return prompt.handler(args);
98
130
  }
99
131
  case "ping":
100
132
  return {};
@@ -121,10 +153,46 @@ function matchesTemplate(pattern, uri) {
121
153
  function makeError(code, message) {
122
154
  return { code, message };
123
155
  }
156
+ function asParamsObject(params, ctx) {
157
+ if (params === null || typeof params !== "object" || Array.isArray(params)) {
158
+ throw makeError(-32602, `Invalid params for ${ctx}: expected an object`);
159
+ }
160
+ return params;
161
+ }
162
+ function validateAgainstSchema(schema, args, toolName) {
163
+ if (!schema || typeof schema !== "object") return;
164
+ const s = schema;
165
+ if (Array.isArray(s.required)) {
166
+ for (const key of s.required) {
167
+ if (typeof key === "string" && !(key in args)) {
168
+ throw makeError(-32602, `Missing required argument "${key}" for tool "${toolName}"`);
169
+ }
170
+ }
171
+ }
172
+ if (s.properties && typeof s.properties === "object") {
173
+ for (const [key, prop] of Object.entries(s.properties)) {
174
+ const value = args[key];
175
+ if (value === void 0 || value === null) continue;
176
+ const expected = prop?.type;
177
+ if (!expected) continue;
178
+ const ok = expected === "number" || expected === "integer" ? typeof value === "number" : expected === "array" ? Array.isArray(value) : expected === "object" ? typeof value === "object" && !Array.isArray(value) : typeof value === expected;
179
+ if (!ok) {
180
+ throw makeError(
181
+ -32602,
182
+ `Argument "${key}" for tool "${toolName}" must be of type ${expected}`
183
+ );
184
+ }
185
+ }
186
+ }
187
+ }
124
188
 
125
189
  // src/mcp/client.ts
126
190
  function createMCPClient(config) {
127
191
  let requestId = 0;
192
+ const parsedUrl = new URL(config.url);
193
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
194
+ throw new Error(`MCP client URL must be http(s): ${config.url}`);
195
+ }
128
196
  async function rpc(method, params) {
129
197
  const request = {
130
198
  jsonrpc: "2.0",
@@ -139,6 +207,10 @@ function createMCPClient(config) {
139
207
  ...config.headers
140
208
  },
141
209
  body: JSON.stringify(request),
210
+ // never follow redirects: a 3xx to another host would otherwise re-send
211
+ // config.headers (often a bearer token) to that host, leaking the
212
+ // credential and enabling an ssrf pivot.
213
+ redirect: "error",
142
214
  signal: AbortSignal.timeout(3e4)
143
215
  });
144
216
  if (!response.ok) {
@@ -211,16 +283,45 @@ function createMCPClient(config) {
211
283
 
212
284
  // src/mcp/handler.ts
213
285
  function createMCPHandler(server, options) {
214
- const corsOrigin = options?.corsOrigin ?? "*";
286
+ const corsOrigin = options?.corsOrigin;
287
+ const allowedOrigins = options?.allowedOrigins;
288
+ const authorize = options?.authorize;
215
289
  return async (req, res) => {
216
- res.setHeader("Access-Control-Allow-Origin", corsOrigin);
217
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
218
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
290
+ if (corsOrigin) {
291
+ res.setHeader("Access-Control-Allow-Origin", corsOrigin);
292
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
293
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
294
+ }
219
295
  if (req.method === "OPTIONS") {
220
296
  res.writeHead(204);
221
297
  res.end();
222
298
  return;
223
299
  }
300
+ const origin = req.headers.origin;
301
+ if (allowedOrigins && origin !== void 0 && !allowedOrigins.includes(origin)) {
302
+ res.writeHead(403, { "Content-Type": "text/plain" });
303
+ res.end("Forbidden");
304
+ return;
305
+ }
306
+ if (authorize) {
307
+ let allowed = false;
308
+ try {
309
+ allowed = await authorize(req);
310
+ } catch {
311
+ allowed = false;
312
+ }
313
+ if (!allowed) {
314
+ res.writeHead(401, { "Content-Type": "application/json" });
315
+ res.end(
316
+ JSON.stringify({
317
+ jsonrpc: "2.0",
318
+ id: null,
319
+ error: { code: -32600, message: "Unauthorized" }
320
+ })
321
+ );
322
+ return;
323
+ }
324
+ }
224
325
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
225
326
  if (url.pathname.endsWith("/sse") && req.method === "GET") {
226
327
  handleSSE(server, req, res);
@@ -242,6 +343,18 @@ async function handlePost(server, req, res) {
242
343
  res.writeHead(200, { "Content-Type": "application/json" });
243
344
  res.end(JSON.stringify(response));
244
345
  } catch (err) {
346
+ const errObj = err;
347
+ if (errObj.statusCode === 413) {
348
+ res.writeHead(413, { "Content-Type": "application/json" });
349
+ res.end(
350
+ JSON.stringify({
351
+ jsonrpc: "2.0",
352
+ id: null,
353
+ error: { code: -32600, message: "Payload too large" }
354
+ })
355
+ );
356
+ return;
357
+ }
245
358
  const data = err instanceof SyntaxError ? err.message : "Invalid request";
246
359
  res.writeHead(400, { "Content-Type": "application/json" });
247
360
  res.end(
@@ -275,10 +388,20 @@ data: ${endpointUrl}
275
388
  clearInterval(keepAlive);
276
389
  });
277
390
  }
278
- function readBody(req) {
391
+ var DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
392
+ function readBody(req, maxSize = DEFAULT_MAX_BODY_SIZE) {
279
393
  return new Promise((resolve, reject) => {
280
394
  const chunks = [];
281
- req.on("data", (chunk) => chunks.push(chunk));
395
+ let size = 0;
396
+ req.on("data", (chunk) => {
397
+ size += chunk.length;
398
+ if (size > maxSize) {
399
+ req.removeAllListeners("data");
400
+ reject(Object.assign(new Error("Payload too large"), { statusCode: 413 }));
401
+ return;
402
+ }
403
+ chunks.push(chunk);
404
+ });
282
405
  req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
283
406
  req.on("error", reject);
284
407
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/utopia-ai",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "AI adapters and MCP support for UtopiaJS",
5
5
  "type": "module",
6
6
  "license": "MIT",