@getjack/jack 0.1.33 → 0.1.35

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.
Files changed (94) hide show
  1. package/README.md +6 -6
  2. package/package.json +1 -1
  3. package/src/commands/down.ts +39 -7
  4. package/src/commands/link.ts +2 -4
  5. package/src/commands/logs.ts +2 -4
  6. package/src/commands/mcp.ts +12 -10
  7. package/src/commands/secrets.ts +3 -1
  8. package/src/commands/services.ts +4 -2
  9. package/src/commands/sync.ts +5 -6
  10. package/src/lib/auth/client.ts +5 -2
  11. package/src/lib/binding-validator.ts +39 -3
  12. package/src/lib/build-helper.ts +18 -19
  13. package/src/lib/control-plane.ts +1 -0
  14. package/src/lib/crypto.ts +84 -0
  15. package/src/lib/deploy-upload.ts +7 -3
  16. package/src/lib/do-config.ts +110 -0
  17. package/src/lib/do-export-validator.ts +26 -0
  18. package/src/lib/hooks.ts +1 -2
  19. package/src/lib/jsonc-edit.ts +292 -0
  20. package/src/lib/managed-deploy.ts +36 -1
  21. package/src/lib/project-link.ts +37 -0
  22. package/src/lib/project-operations.ts +37 -46
  23. package/src/lib/prompts.ts +2 -2
  24. package/src/lib/resources.ts +4 -5
  25. package/src/lib/schema.ts +8 -12
  26. package/src/lib/services/db-create.ts +2 -2
  27. package/src/lib/services/db-execute.ts +9 -6
  28. package/src/lib/services/db-list.ts +6 -4
  29. package/src/lib/services/endpoint-test.ts +275 -0
  30. package/src/lib/services/project-delete.ts +190 -0
  31. package/src/lib/services/project-environment.ts +457 -0
  32. package/src/lib/services/storage-config.ts +7 -309
  33. package/src/lib/services/storage-create.ts +2 -1
  34. package/src/lib/services/storage-delete.ts +3 -2
  35. package/src/lib/services/storage-info.ts +2 -1
  36. package/src/lib/services/storage-list.ts +6 -3
  37. package/src/lib/services/vectorize-config.ts +7 -264
  38. package/src/lib/services/vectorize-create.ts +2 -1
  39. package/src/lib/services/vectorize-delete.ts +6 -4
  40. package/src/lib/services/vectorize-list.ts +6 -3
  41. package/src/lib/storage/index.ts +21 -23
  42. package/src/lib/telemetry.ts +1 -0
  43. package/src/lib/wrangler-config.ts +43 -312
  44. package/src/lib/zip-packager.ts +28 -0
  45. package/src/mcp/test-utils.ts +31 -0
  46. package/src/mcp/tools/index.ts +271 -0
  47. package/src/templates/index.ts +5 -0
  48. package/src/templates/types.ts +4 -0
  49. package/templates/AI-BINDINGS.md +34 -76
  50. package/templates/CLAUDE.md +22 -1
  51. package/templates/ai-chat/src/index.ts +7 -14
  52. package/templates/ai-chat/src/jack-ai.ts +0 -6
  53. package/templates/chat/.jack.json +45 -0
  54. package/templates/chat/bun.lock +1588 -0
  55. package/templates/chat/components.json +23 -0
  56. package/templates/chat/index.html +12 -0
  57. package/templates/chat/package.json +41 -0
  58. package/templates/chat/src/chat-agent.ts +61 -0
  59. package/templates/chat/src/client/app.tsx +189 -0
  60. package/templates/chat/src/client/chat.tsx +222 -0
  61. package/templates/chat/src/client/components/prompt-kit/chat-container.tsx +47 -0
  62. package/templates/chat/src/client/components/prompt-kit/loader.tsx +33 -0
  63. package/templates/chat/src/client/components/prompt-kit/markdown.tsx +84 -0
  64. package/templates/chat/src/client/components/prompt-kit/message.tsx +54 -0
  65. package/templates/chat/src/client/components/prompt-kit/prompt-suggestion.tsx +20 -0
  66. package/templates/chat/src/client/components/prompt-kit/reasoning.tsx +134 -0
  67. package/templates/chat/src/client/components/prompt-kit/scroll-button.tsx +28 -0
  68. package/templates/chat/src/client/components/ui/button.tsx +38 -0
  69. package/templates/chat/src/client/lib/utils.ts +6 -0
  70. package/templates/chat/src/client/main.tsx +11 -0
  71. package/templates/chat/src/client/styles.css +125 -0
  72. package/templates/chat/src/index.ts +25 -0
  73. package/templates/chat/src/jack-ai.ts +94 -0
  74. package/templates/chat/tsconfig.json +18 -0
  75. package/templates/chat/vite.config.ts +14 -0
  76. package/templates/chat/wrangler.jsonc +18 -0
  77. package/templates/cron/.jack.json +18 -28
  78. package/templates/cron/schema.sql +10 -20
  79. package/templates/cron/src/admin.ts +321 -0
  80. package/templates/cron/src/index.ts +151 -81
  81. package/templates/cron/src/monitor.ts +124 -0
  82. package/templates/nextjs-clerk/app/layout.tsx +2 -0
  83. package/templates/semantic-search/src/index.ts +5 -43
  84. package/templates/semantic-search/src/jack-ai.ts +0 -6
  85. package/templates/telegram-bot/.jack.json +56 -0
  86. package/templates/telegram-bot/bun.lock +41 -0
  87. package/templates/telegram-bot/package.json +16 -0
  88. package/templates/telegram-bot/src/index.ts +236 -0
  89. package/templates/telegram-bot/src/jack-ai.ts +100 -0
  90. package/templates/telegram-bot/tsconfig.json +11 -0
  91. package/templates/telegram-bot/wrangler.jsonc +8 -0
  92. package/templates/cron/src/jobs.ts +0 -139
  93. package/templates/cron/src/webhooks.ts +0 -95
  94. package/templates/semantic-search/src/jack-vectorize.ts +0 -169
@@ -4,8 +4,8 @@
4
4
  * Uses wrangler CLI to delete Vectorize indexes.
5
5
  */
6
6
 
7
- import { join } from "node:path";
8
7
  import { $ } from "bun";
8
+ import { findWranglerConfig } from "../wrangler-config.ts";
9
9
  import { removeVectorizeBinding } from "./vectorize-config.ts";
10
10
 
11
11
  export interface DeleteVectorizeResult {
@@ -42,9 +42,11 @@ export async function deleteVectorizeIndex(
42
42
  // Delete via wrangler
43
43
  await deleteIndexViaWrangler(indexName);
44
44
 
45
- // Remove binding from wrangler.jsonc
46
- const wranglerPath = join(projectDir, "wrangler.jsonc");
47
- const bindingRemoved = await removeVectorizeBinding(wranglerPath, indexName);
45
+ // Remove binding from wrangler config
46
+ const wranglerPath = findWranglerConfig(projectDir);
47
+ const bindingRemoved = wranglerPath
48
+ ? await removeVectorizeBinding(wranglerPath, indexName)
49
+ : false;
48
50
 
49
51
  return {
50
52
  indexName,
@@ -4,7 +4,7 @@
4
4
  * Lists Vectorize indexes configured in wrangler.jsonc with their metadata.
5
5
  */
6
6
 
7
- import { join } from "node:path";
7
+ import { findWranglerConfig } from "../wrangler-config.ts";
8
8
  import { getExistingVectorizeBindings } from "./vectorize-config.ts";
9
9
  import { getVectorizeInfo } from "./vectorize-info.ts";
10
10
 
@@ -23,9 +23,12 @@ export interface VectorizeListEntry {
23
23
  * (dimensions, metric, vector count) via wrangler vectorize info for each index.
24
24
  */
25
25
  export async function listVectorizeIndexes(projectDir: string): Promise<VectorizeListEntry[]> {
26
- const wranglerPath = join(projectDir, "wrangler.jsonc");
26
+ const wranglerPath = findWranglerConfig(projectDir);
27
+ if (!wranglerPath) {
28
+ return [];
29
+ }
27
30
 
28
- // Get existing Vectorize bindings from wrangler.jsonc
31
+ // Get existing Vectorize bindings from wrangler config
29
32
  const bindings = await getExistingVectorizeBindings(wranglerPath);
30
33
 
31
34
  if (bindings.length === 0) {
@@ -4,6 +4,8 @@
4
4
 
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
6
  import { dirname, join } from "node:path";
7
+ import { parseJsonc } from "../jsonc.ts";
8
+ import { findWranglerConfig } from "../wrangler-config.ts";
7
9
 
8
10
  // Re-export submodules
9
11
  export * from "./checksum.ts";
@@ -63,34 +65,30 @@ export interface CloudProject {
63
65
  * @throws Error if wrangler file not found or name not found
64
66
  */
65
67
  export async function getProjectNameFromDir(projectDir: string): Promise<string> {
66
- // Try wrangler.toml first
67
- const tomlPath = join(projectDir, "wrangler.toml");
68
- try {
69
- const content = await Bun.file(tomlPath).text();
70
- const match = content.match(/^name\s*=\s*["']([^"']+)["']/m);
71
- if (match?.[1]) {
72
- return match[1];
73
- }
74
- } catch {
75
- // File doesn't exist, try JSON
68
+ const configPath = findWranglerConfig(projectDir);
69
+ if (!configPath) {
70
+ throw new Error(
71
+ "Could not find project name. Please ensure wrangler.toml or wrangler.jsonc exists with a 'name' field",
72
+ );
76
73
  }
77
74
 
78
- // Try wrangler.jsonc
79
- const jsoncPath = join(projectDir, "wrangler.jsonc");
80
75
  try {
81
- const content = await Bun.file(jsoncPath).text();
82
- // Remove comments and parse JSON
83
- // Note: Only remove line comments at the start of a line (with optional whitespace)
84
- // to avoid breaking URLs like https://example.com
85
- const jsonContent = content
86
- .replace(/\/\*[\s\S]*?\*\//g, "") // block comments
87
- .replace(/^\s*\/\/.*$/gm, ""); // line comments at start of line only
88
- const config = JSON.parse(jsonContent);
89
- if (config.name) {
90
- return config.name;
76
+ const content = await Bun.file(configPath).text();
77
+
78
+ if (configPath.endsWith(".toml")) {
79
+ const match = content.match(/^name\s*=\s*["']([^"']+)["']/m);
80
+ if (match?.[1]) {
81
+ return match[1];
82
+ }
83
+ } else {
84
+ // .jsonc or .json
85
+ const config = parseJsonc<{ name?: string }>(content);
86
+ if (config.name) {
87
+ return config.name;
88
+ }
91
89
  }
92
90
  } catch {
93
- // File doesn't exist or parse failed
91
+ // Parse failed
94
92
  }
95
93
 
96
94
  throw new Error(
@@ -17,6 +17,7 @@ export const Events = {
17
17
  COMMAND_COMPLETED: "command_completed",
18
18
  COMMAND_FAILED: "command_failed",
19
19
  PROJECT_CREATED: "project_created",
20
+ PROJECT_DELETED: "project_deleted",
20
21
  DEPLOY_STARTED: "deploy_started",
21
22
  CONFIG_CHANGED: "config_changed",
22
23
  INTENT_MATCHED: "intent_matched",
@@ -1,10 +1,51 @@
1
1
  /**
2
- * Utilities for modifying wrangler.jsonc while preserving comments
2
+ * Utilities for wrangler config detection and modification.
3
+ *
4
+ * All wrangler config path resolution should go through this module.
5
+ * Priority: .jsonc > .toml > .json (jsonc is jack's default)
3
6
  */
4
7
 
5
8
  import { existsSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import {
11
+ addSectionBeforeClosingBrace,
12
+ findLastObjectEndInArray,
13
+ findMatchingBracket,
14
+ isOnlyCommentsAndWhitespace,
15
+ shouldAddCommaBefore,
16
+ } from "./jsonc-edit.ts";
6
17
  import { parseJsonc } from "./jsonc.ts";
7
18
 
19
+ // ============================================================================
20
+ // Wrangler Config Path Resolution
21
+ // ============================================================================
22
+
23
+ /** Canonical priority order: jsonc (jack's default) > toml > json */
24
+ const WRANGLER_CONFIG_NAMES = ["wrangler.jsonc", "wrangler.toml", "wrangler.json"] as const;
25
+
26
+ /**
27
+ * Find the wrangler config file in a project directory.
28
+ * Returns the full absolute path, or null if none found.
29
+ */
30
+ export function findWranglerConfig(projectDir: string): string | null {
31
+ for (const name of WRANGLER_CONFIG_NAMES) {
32
+ const fullPath = join(projectDir, name);
33
+ if (existsSync(fullPath)) return fullPath;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Check whether a project directory has any wrangler config file.
40
+ */
41
+ export function hasWranglerConfig(projectDir: string): boolean {
42
+ return findWranglerConfig(projectDir) !== null;
43
+ }
44
+
45
+ // ============================================================================
46
+ // D1 Binding Config
47
+ // ============================================================================
48
+
8
49
  export interface D1BindingConfig {
9
50
  binding: string; // e.g., "DB" or "ANALYTICS_DB"
10
51
  database_name: string; // e.g., "my-app-db"
@@ -145,317 +186,7 @@ function appendToD1DatabasesArray(content: string, bindingJson: string): string
145
186
  * Inserts before the final closing brace.
146
187
  */
147
188
  function addD1DatabasesSection(content: string, bindingJson: string): string {
148
- // Find the last closing brace in the file
149
- const lastBraceIndex = content.lastIndexOf("}");
150
- if (lastBraceIndex === -1) {
151
- throw new Error("Invalid JSON: no closing brace found");
152
- }
153
-
154
- // Check what comes before the last brace to determine if we need a comma
155
- const beforeBrace = content.slice(0, lastBraceIndex);
156
- const needsComma = shouldAddCommaBefore(beforeBrace);
157
-
158
- // Build the d1_databases section
159
- const d1Section = `"d1_databases": [
160
- ${bindingJson}
161
- ]`;
162
-
163
- // Find proper insertion point - look for last non-whitespace content
164
- const trimmedBefore = beforeBrace.trimEnd();
165
- const whitespaceAfterContent = beforeBrace.slice(trimmedBefore.length);
166
-
167
- let insertion: string;
168
- if (needsComma) {
169
- insertion = `,\n\t${d1Section}`;
170
- } else {
171
- insertion = `\n\t${d1Section}`;
172
- }
173
-
174
- // Reconstruct: content before + insertion + newline + closing brace
175
- return trimmedBefore + insertion + "\n" + content.slice(lastBraceIndex);
176
- }
177
-
178
- /**
179
- * Find the matching closing bracket/brace for an opening one
180
- */
181
- function findMatchingBracket(
182
- content: string,
183
- startIndex: number,
184
- openChar: string,
185
- closeChar: string,
186
- ): number {
187
- let depth = 0;
188
- let inString = false;
189
- let stringChar = "";
190
- let escaped = false;
191
- let inLineComment = false;
192
- let inBlockComment = false;
193
-
194
- for (let i = startIndex; i < content.length; i++) {
195
- const char = content[i] ?? "";
196
- const next = content[i + 1] ?? "";
197
-
198
- // Handle line comments
199
- if (inLineComment) {
200
- if (char === "\n") {
201
- inLineComment = false;
202
- }
203
- continue;
204
- }
205
-
206
- // Handle block comments
207
- if (inBlockComment) {
208
- if (char === "*" && next === "/") {
209
- inBlockComment = false;
210
- i++;
211
- }
212
- continue;
213
- }
214
-
215
- // Handle strings
216
- if (inString) {
217
- if (escaped) {
218
- escaped = false;
219
- continue;
220
- }
221
- if (char === "\\") {
222
- escaped = true;
223
- continue;
224
- }
225
- if (char === stringChar) {
226
- inString = false;
227
- stringChar = "";
228
- }
229
- continue;
230
- }
231
-
232
- // Check for comment start
233
- if (char === "/" && next === "/") {
234
- inLineComment = true;
235
- i++;
236
- continue;
237
- }
238
- if (char === "/" && next === "*") {
239
- inBlockComment = true;
240
- i++;
241
- continue;
242
- }
243
-
244
- // Check for string start
245
- if (char === '"' || char === "'") {
246
- inString = true;
247
- stringChar = char;
248
- continue;
249
- }
250
-
251
- // Track bracket depth
252
- if (char === openChar) {
253
- depth++;
254
- } else if (char === closeChar) {
255
- depth--;
256
- if (depth === 0) {
257
- return i;
258
- }
259
- }
260
- }
261
-
262
- return -1;
263
- }
264
-
265
- /**
266
- * Check if content is only whitespace and comments
267
- */
268
- function isOnlyCommentsAndWhitespace(content: string): boolean {
269
- let inLineComment = false;
270
- let inBlockComment = false;
271
-
272
- for (let i = 0; i < content.length; i++) {
273
- const char = content[i] ?? "";
274
- const next = content[i + 1] ?? "";
275
-
276
- if (inLineComment) {
277
- if (char === "\n") {
278
- inLineComment = false;
279
- }
280
- continue;
281
- }
282
-
283
- if (inBlockComment) {
284
- if (char === "*" && next === "/") {
285
- inBlockComment = false;
286
- i++;
287
- }
288
- continue;
289
- }
290
-
291
- if (char === "/" && next === "/") {
292
- inLineComment = true;
293
- i++;
294
- continue;
295
- }
296
-
297
- if (char === "/" && next === "*") {
298
- inBlockComment = true;
299
- i++;
300
- continue;
301
- }
302
-
303
- if (!/\s/.test(char)) {
304
- return false;
305
- }
306
- }
307
-
308
- return true;
309
- }
310
-
311
- /**
312
- * Find the last closing brace of an object within an array range
313
- */
314
- function findLastObjectEndInArray(content: string, startIndex: number, endIndex: number): number {
315
- let lastBraceIndex = -1;
316
- let inString = false;
317
- let stringChar = "";
318
- let escaped = false;
319
- let inLineComment = false;
320
- let inBlockComment = false;
321
-
322
- for (let i = startIndex; i < endIndex; i++) {
323
- const char = content[i] ?? "";
324
- const next = content[i + 1] ?? "";
325
-
326
- if (inLineComment) {
327
- if (char === "\n") {
328
- inLineComment = false;
329
- }
330
- continue;
331
- }
332
-
333
- if (inBlockComment) {
334
- if (char === "*" && next === "/") {
335
- inBlockComment = false;
336
- i++;
337
- }
338
- continue;
339
- }
340
-
341
- if (inString) {
342
- if (escaped) {
343
- escaped = false;
344
- continue;
345
- }
346
- if (char === "\\") {
347
- escaped = true;
348
- continue;
349
- }
350
- if (char === stringChar) {
351
- inString = false;
352
- stringChar = "";
353
- }
354
- continue;
355
- }
356
-
357
- if (char === "/" && next === "/") {
358
- inLineComment = true;
359
- i++;
360
- continue;
361
- }
362
-
363
- if (char === "/" && next === "*") {
364
- inBlockComment = true;
365
- i++;
366
- continue;
367
- }
368
-
369
- if (char === '"' || char === "'") {
370
- inString = true;
371
- stringChar = char;
372
- continue;
373
- }
374
-
375
- if (char === "}") {
376
- lastBraceIndex = i;
377
- }
378
- }
379
-
380
- return lastBraceIndex;
381
- }
382
-
383
- /**
384
- * Determine if we need to add a comma before new content.
385
- * Looks at the last non-whitespace, non-comment character.
386
- */
387
- function shouldAddCommaBefore(content: string): boolean {
388
- // Strip trailing comments and whitespace to find last meaningful char
389
- let i = content.length - 1;
390
- const inLineComment = false;
391
-
392
- // First pass: find where any trailing line comment starts
393
- for (let j = content.length - 1; j >= 0; j--) {
394
- if (content[j] === "\n") {
395
- // Check if there's a // comment on this line
396
- const lineStart = content.lastIndexOf("\n", j - 1) + 1;
397
- const line = content.slice(lineStart, j);
398
- const commentIndex = findLineCommentStart(line);
399
- if (commentIndex !== -1) {
400
- i = lineStart + commentIndex - 1;
401
- }
402
- break;
403
- }
404
- }
405
-
406
- // Skip whitespace
407
- while (i >= 0 && /\s/.test(content[i] ?? "")) {
408
- i--;
409
- }
410
-
411
- if (i < 0) return false;
412
-
413
- const lastChar = content[i];
414
- // Need comma if last char is }, ], ", number, or identifier char
415
- // Don't need comma if last char is { or [ or ,
416
- return lastChar !== "{" && lastChar !== "[" && lastChar !== ",";
417
- }
418
-
419
- /**
420
- * Find the start of a line comment (//) in a string, respecting strings
421
- */
422
- function findLineCommentStart(line: string): number {
423
- let inString = false;
424
- let stringChar = "";
425
- let escaped = false;
426
-
427
- for (let i = 0; i < line.length - 1; i++) {
428
- const char = line[i] ?? "";
429
- const next = line[i + 1] ?? "";
430
-
431
- if (inString) {
432
- if (escaped) {
433
- escaped = false;
434
- continue;
435
- }
436
- if (char === "\\") {
437
- escaped = true;
438
- continue;
439
- }
440
- if (char === stringChar) {
441
- inString = false;
442
- stringChar = "";
443
- }
444
- continue;
445
- }
446
-
447
- if (char === '"' || char === "'") {
448
- inString = true;
449
- stringChar = char;
450
- continue;
451
- }
452
-
453
- if (char === "/" && next === "/") {
454
- return i;
455
- }
456
- }
457
-
458
- return -1;
189
+ return addSectionBeforeClosingBrace(content, `"d1_databases": [\n\t\t${bindingJson}\n\t]`);
459
190
  }
460
191
 
461
192
  /**
@@ -51,7 +51,17 @@ export interface ManifestData {
51
51
  dimensions?: number;
52
52
  metric?: string;
53
53
  }>;
54
+ durable_objects?: Array<{
55
+ binding: string;
56
+ class_name: string;
57
+ }>;
54
58
  };
59
+ migrations?: Array<{
60
+ tag: string;
61
+ new_sqlite_classes?: string[];
62
+ deleted_classes?: string[];
63
+ renamed_classes?: Array<{ from: string; to: string }>;
64
+ }>;
55
65
  }
56
66
 
57
67
  export type ZipProgressCallback = (current: number, total: number) => void;
@@ -238,6 +248,14 @@ function extractBindingsFromConfig(config?: WranglerConfig): ManifestData["bindi
238
248
  }));
239
249
  }
240
250
 
251
+ // Extract Durable Object bindings
252
+ if (config?.durable_objects?.bindings && config.durable_objects.bindings.length > 0) {
253
+ bindings.durable_objects = config.durable_objects.bindings.map((dob) => ({
254
+ binding: dob.name,
255
+ class_name: dob.class_name,
256
+ }));
257
+ }
258
+
241
259
  // Return undefined if no bindings were extracted
242
260
  return Object.keys(bindings).length > 0 ? bindings : undefined;
243
261
  }
@@ -305,6 +323,16 @@ export async function packageForDeploy(
305
323
  bindings: extractBindingsFromConfig(config),
306
324
  };
307
325
 
326
+ // Extract migrations (top-level, for DO support)
327
+ if (config?.migrations?.length) {
328
+ manifest.migrations = config.migrations.map((m) => ({
329
+ tag: m.tag,
330
+ new_sqlite_classes: m.new_sqlite_classes,
331
+ deleted_classes: m.deleted_classes,
332
+ renamed_classes: m.renamed_classes,
333
+ }));
334
+ }
335
+
308
336
  await Bun.write(manifestPath, JSON.stringify(manifest, null, 2));
309
337
 
310
338
  // 4. Check for optional files (schema.sql and .secrets.json)
@@ -156,3 +156,34 @@ export async function callMcpGetProjectStatus(
156
156
 
157
157
  return parseMcpToolResult(response);
158
158
  }
159
+
160
+ export async function callMcpGetProjectEnvironment(
161
+ client: Client,
162
+ args: { project_path?: string },
163
+ ): Promise<unknown> {
164
+ const response = await client.callTool({
165
+ name: "get_project_environment",
166
+ arguments: args,
167
+ });
168
+
169
+ return parseMcpToolResult(response);
170
+ }
171
+
172
+ export async function callMcpTestEndpoint(
173
+ client: Client,
174
+ args: {
175
+ project_path?: string;
176
+ path: string;
177
+ method?: string;
178
+ headers?: Record<string, string>;
179
+ body?: string;
180
+ include_logs?: boolean;
181
+ },
182
+ ): Promise<unknown> {
183
+ const response = await client.callTool({
184
+ name: "test_endpoint",
185
+ arguments: args,
186
+ });
187
+
188
+ return parseMcpToolResult(response);
189
+ }