capyai 0.3.6 → 0.4.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/AGENTS.md CHANGED
@@ -81,7 +81,7 @@ Config file locations:
81
81
  - Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
82
82
  - Cursor: `.cursor/mcp.json`
83
83
 
84
- 17 MCP tools with full CLI parity:
84
+ 21 MCP tools with full API parity:
85
85
 
86
86
  | Tool | What it does | Annotations |
87
87
  |------|-------------|-------------|
@@ -98,10 +98,14 @@ Config file locations:
98
98
  | `capy_threads` | List threads (paginated) | readOnly, idempotent |
99
99
  | `capy_thread_messages` | Read thread conversation history | readOnly, idempotent |
100
100
  | `capy_diff` | View diff | readOnly |
101
- | `capy_msg` | Message task/thread | openWorld |
101
+ | `capy_msg` | Message task/thread (supports attachments, model switch) | openWorld |
102
102
  | `capy_stop` | Stop task/thread | destructive |
103
- | `capy_pr` | Create PR | openWorld |
103
+ | `capy_pr` | Create PR (title, description, draft) | openWorld |
104
104
  | `capy_models` | List models | readOnly, idempotent |
105
+ | `capy_pool_status` | Warm pool config + VM status | readOnly, idempotent |
106
+ | `capy_pool_update` | Update warm pool config | openWorld |
107
+ | `capy_pool_instances` | List warm pool VMs | readOnly, idempotent |
108
+ | `capy_pool_clear` | Clear/refresh warm pool | destructive |
105
109
 
106
110
  Tools with predictable outputs (`capy_captain`, `capy_build`, `capy_review`, `capy_approve`, `capy_retry`) declare `outputSchema` for typed structured content per the 2025-03-26 MCP spec.
107
111
 
@@ -227,6 +231,12 @@ Every command supports `--json` for structured output. Errors always return `{ "
227
231
  | `capy threads msg <id> "<text>"` | Message a thread |
228
232
  | `capy threads stop <id>` | Stop a thread |
229
233
  | `capy threads messages <id>` | Read thread conversation history |
234
+ | `capy pool` | Warm pool status |
235
+ | `capy pool set --size=N --age=M` | Update warm pool config |
236
+ | `capy pool test` | Test VM boot |
237
+ | `capy pool instances [status]` | List pool VMs |
238
+ | `capy pool instance <id>` | VM detail + logs |
239
+ | `capy pool clear [--replenish]` | Clear/refresh pool |
230
240
  | `capy config [key] [value]` | Get/set config |
231
241
  | `capy models` | List available models |
232
242
  | `capy tools` | Show all commands + env vars |
package/README.md CHANGED
@@ -53,7 +53,8 @@ Every command supports `--json` for machine-readable output.
53
53
  | `capy diff <id>` | View diff |
54
54
  | `capy pr <id>` | Create PR for task |
55
55
  | `capy watch <id>` | Cron poll + notify on completion |
56
- | `capy threads [list\|get\|msg\|stop]` | Manage Captain threads |
56
+ | `capy threads [list\|get\|msg\|stop\|messages]` | Manage Captain threads |
57
+ | `capy pool [status\|set\|test\|instances\|clear]` | Manage warm pool VMs |
57
58
  | `capy models` | List available models |
58
59
  | `capy config [key] [value]` | Get/set config |
59
60
 
@@ -87,7 +88,7 @@ For agents that prefer MCP over CLI:
87
88
  }
88
89
  ```
89
90
 
90
- 17 tools with full CLI parity: `capy_captain`, `capy_build`, `capy_start`, `capy_wait`, `capy_review`, `capy_approve`, `capy_retry`, `capy_re_review`, `capy_status`, `capy_list`, `capy_threads`, `capy_thread_messages`, `capy_diff`, `capy_msg`, `capy_stop`, `capy_pr`, `capy_models`.
91
+ 21 tools with full API parity, including warm pool management.
91
92
 
92
93
  ## Config
93
94
 
package/bin/capy.ts CHANGED
@@ -36,6 +36,7 @@ const main = defineCommand({
36
36
  watches: () => import("../src/commands/monitoring.js").then(m => m.watches),
37
37
  wait: () => import("../src/commands/monitoring.js").then(m => m.wait),
38
38
  _poll: () => import("../src/commands/monitoring.js").then(m => m._poll),
39
+ pool: () => import("../src/commands/pool.js").then(m => m.default),
39
40
  init: () => import("../src/commands/setup.js").then(m => m.init),
40
41
  config: () => import("../src/commands/setup.js").then(m => m.config),
41
42
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capyai",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Unofficial Capy.ai CLI for agent orchestration with quality gates",
6
6
  "bin": {
@@ -3,7 +3,7 @@ name: capy
3
3
  description: Orchestrate Capy.ai coding agents with quality gates. Delegate coding work, wait for completion, review quality, approve or retry.
4
4
  metadata:
5
5
  author: yazcaleb
6
- version: "0.3.6"
6
+ version: "0.4.0"
7
7
  ---
8
8
 
9
9
  # capy
package/src/api.ts CHANGED
@@ -34,15 +34,16 @@ async function rawRequest(apiKey: string, server: string, method: string, path:
34
34
  }
35
35
 
36
36
  const text = await res.text();
37
+ let data: any;
37
38
  try {
38
- const data = JSON.parse(text);
39
- if (data.error) {
40
- fail("api_error", `API error — ${data.error.message || data.error.code}`);
41
- }
42
- return data;
39
+ data = JSON.parse(text);
43
40
  } catch {
44
41
  fail("bad_response", `bad API response: ${text.slice(0, 200)}`);
45
42
  }
43
+ if (data.error) {
44
+ fail(data.error.code || "api_error", data.error.message || "unknown API error");
45
+ }
46
+ return data;
46
47
  }
47
48
 
48
49
  async function request(method: string, path: string, body?: unknown): Promise<any> {
@@ -78,21 +79,24 @@ export async function getProject(id?: string): Promise<Project & { createdAt?: s
78
79
  }
79
80
 
80
81
  // --- Threads ---
81
- export async function createThread(prompt: string, model?: string, repos?: unknown[]): Promise<Thread> {
82
+ export async function createThread(prompt: string, model?: string, opts: { repos?: unknown[]; attachmentUrls?: string[] } = {}): Promise<Thread> {
82
83
  const cfg = config.load();
83
84
  return request("POST", "/threads", {
84
85
  projectId: cfg.projectId,
85
86
  prompt,
86
87
  model: model || cfg.defaultModel,
87
- repos: repos || cfg.repos,
88
+ repos: opts.repos || cfg.repos,
89
+ ...(opts.attachmentUrls?.length ? { attachmentUrls: opts.attachmentUrls } : {}),
88
90
  });
89
91
  }
90
92
 
91
- export async function listThreads(opts: { limit?: number; status?: string; cursor?: string } = {}): Promise<ListResponse<Thread>> {
93
+ export async function listThreads(opts: { limit?: number; status?: string; cursor?: string; prNumber?: number; branch?: string } = {}): Promise<ListResponse<Thread>> {
92
94
  const cfg = config.load();
93
95
  const p = new URLSearchParams({ projectId: cfg.projectId, limit: String(opts.limit || 10) });
94
96
  if (opts.status) p.set("status", opts.status);
95
97
  if (opts.cursor) p.set("cursor", opts.cursor);
98
+ if (opts.prNumber) p.set("prNumber", String(opts.prNumber));
99
+ if (opts.branch) p.set("branch", opts.branch);
96
100
  return request("GET", `/threads?${p}`);
97
101
  }
98
102
 
@@ -100,8 +104,12 @@ export async function getThread(id: string): Promise<Thread> {
100
104
  return request("GET", `/threads/${id}`);
101
105
  }
102
106
 
103
- export async function messageThread(id: string, msg: string): Promise<unknown> {
104
- return request("POST", `/threads/${id}/message`, { message: msg });
107
+ export async function messageThread(id: string, msg: string, opts: { model?: string; attachmentUrls?: string[] } = {}): Promise<unknown> {
108
+ return request("POST", `/threads/${id}/message`, {
109
+ message: msg,
110
+ ...(opts.model ? { model: opts.model } : {}),
111
+ ...(opts.attachmentUrls?.length ? { attachmentUrls: opts.attachmentUrls } : {}),
112
+ });
105
113
  }
106
114
 
107
115
  export async function stopThread(id: string): Promise<unknown> {
@@ -114,7 +122,7 @@ export async function getThreadMessages(id: string, opts: { limit?: number } = {
114
122
  }
115
123
 
116
124
  // --- Tasks ---
117
- export async function createTask(prompt: string, model?: string, opts: { title?: string; start?: boolean; labels?: string[] } = {}): Promise<Task> {
125
+ export async function createTask(prompt: string, model?: string, opts: { title?: string; start?: boolean; labels?: string[]; attachmentUrls?: string[] } = {}): Promise<Task> {
118
126
  const cfg = config.load();
119
127
  return request("POST", "/tasks", {
120
128
  projectId: cfg.projectId,
@@ -124,14 +132,17 @@ export async function createTask(prompt: string, model?: string, opts: { title?:
124
132
  model: model || cfg.defaultModel,
125
133
  start: opts.start !== false,
126
134
  ...(opts.labels ? { labels: opts.labels } : {}),
135
+ ...(opts.attachmentUrls?.length ? { attachmentUrls: opts.attachmentUrls } : {}),
127
136
  });
128
137
  }
129
138
 
130
- export async function listTasks(opts: { limit?: number; status?: string; cursor?: string } = {}): Promise<ListResponse<Task>> {
139
+ export async function listTasks(opts: { limit?: number; status?: string; cursor?: string; prNumber?: number; branch?: string } = {}): Promise<ListResponse<Task>> {
131
140
  const cfg = config.load();
132
141
  const p = new URLSearchParams({ projectId: cfg.projectId, limit: String(opts.limit || 30) });
133
142
  if (opts.status) p.set("status", opts.status);
134
143
  if (opts.cursor) p.set("cursor", opts.cursor);
144
+ if (opts.prNumber) p.set("prNumber", String(opts.prNumber));
145
+ if (opts.branch) p.set("branch", opts.branch);
135
146
  return request("GET", `/tasks?${p}`);
136
147
  }
137
148
 
@@ -147,8 +158,11 @@ export async function stopTask(id: string, reason?: string): Promise<Task> {
147
158
  return request("POST", `/tasks/${id}/stop`, reason ? { reason } : {});
148
159
  }
149
160
 
150
- export async function messageTask(id: string, msg: string): Promise<unknown> {
151
- return request("POST", `/tasks/${id}/message`, { message: msg });
161
+ export async function messageTask(id: string, msg: string, opts: { attachmentUrls?: string[] } = {}): Promise<unknown> {
162
+ return request("POST", `/tasks/${id}/message`, {
163
+ message: msg,
164
+ ...(opts.attachmentUrls?.length ? { attachmentUrls: opts.attachmentUrls } : {}),
165
+ });
152
166
  }
153
167
 
154
168
  export async function createPR(id: string, opts: Record<string, unknown> = {}): Promise<PullRequestRef & { url?: string; number?: number; title?: string; headRef?: string; baseRef?: string }> {
@@ -162,3 +176,55 @@ export async function getDiff(id: string, mode = "run"): Promise<DiffData> {
162
176
  export async function listModels(): Promise<{ models?: Model[] }> {
163
177
  return request("GET", "/models");
164
178
  }
179
+
180
+ // --- Warm Pool ---
181
+ export interface WarmPoolConfig {
182
+ enabled: boolean;
183
+ targetSize: number;
184
+ maxAgeMinutes: number;
185
+ branch?: string;
186
+ setupCommands?: string[];
187
+ instances?: WarmPoolInstance[];
188
+ }
189
+
190
+ export interface WarmPoolInstance {
191
+ id: string;
192
+ status: string;
193
+ createdAt?: string;
194
+ claimedAt?: string;
195
+ logs?: string;
196
+ }
197
+
198
+ export async function getWarmPool(projectId?: string): Promise<WarmPoolConfig> {
199
+ const pid = projectId || config.load().projectId;
200
+ return request("GET", `/projects/${pid}/warm-pool`);
201
+ }
202
+
203
+ export async function updateWarmPool(update: { enabled?: boolean; targetSize?: number; maxAgeMinutes?: number; branch?: string; setupCommands?: string[] }, projectId?: string): Promise<WarmPoolConfig> {
204
+ const pid = projectId || config.load().projectId;
205
+ return request("PATCH", `/projects/${pid}/warm-pool`, update);
206
+ }
207
+
208
+ export async function testWarmPool(projectId?: string): Promise<{ instanceId: string; status: string }> {
209
+ const pid = projectId || config.load().projectId;
210
+ return request("POST", `/projects/${pid}/warm-pool/test`);
211
+ }
212
+
213
+ export async function listWarmPoolInstances(opts: { status?: string } = {}, projectId?: string): Promise<WarmPoolInstance[]> {
214
+ const pid = projectId || config.load().projectId;
215
+ const p = new URLSearchParams();
216
+ if (opts.status) p.set("status", opts.status);
217
+ const qs = p.toString();
218
+ const data = await request("GET", `/projects/${pid}/warm-pool/instances${qs ? `?${qs}` : ""}`);
219
+ return Array.isArray(data) ? data : data.instances || data.items || [];
220
+ }
221
+
222
+ export async function getWarmPoolInstance(instanceId: string, projectId?: string): Promise<WarmPoolInstance> {
223
+ const pid = projectId || config.load().projectId;
224
+ return request("GET", `/projects/${pid}/warm-pool/instances/${instanceId}`);
225
+ }
226
+
227
+ export async function clearWarmPool(opts: { replenish?: boolean } = {}, projectId?: string): Promise<unknown> {
228
+ const pid = projectId || config.load().projectId;
229
+ return request("POST", `/projects/${pid}/warm-pool/clear`, opts.replenish != null ? { replenish: opts.replenish } : {});
230
+ }
@@ -0,0 +1,128 @@
1
+ import { defineCommand } from "citty";
2
+ import { jsonArg } from "./_shared.js";
3
+
4
+ const status = defineCommand({
5
+ meta: { name: "status", description: "Warm pool config and status" },
6
+ args: { ...jsonArg },
7
+ async run({ args }) {
8
+ const api = await import("../api.js");
9
+ const fmt = await import("../output.js");
10
+ const { log } = await import("@clack/prompts");
11
+
12
+ const pool = await api.getWarmPool();
13
+ if (args.json) { fmt.out(pool); return; }
14
+
15
+ log.info(`Warm pool: ${pool.enabled ? "enabled" : "disabled"}`);
16
+ console.log(` Target size: ${pool.targetSize}`);
17
+ console.log(` Max age: ${pool.maxAgeMinutes}min`);
18
+ if (pool.branch) console.log(` Branch: ${pool.branch}`);
19
+ if (pool.setupCommands?.length) {
20
+ console.log(` Setup: ${pool.setupCommands.length} commands`);
21
+ pool.setupCommands.forEach(c => console.log(` $ ${c}`));
22
+ }
23
+ },
24
+ });
25
+
26
+ const set = defineCommand({
27
+ meta: { name: "set", description: "Update warm pool config" },
28
+ args: {
29
+ enabled: { type: "boolean", description: "Enable/disable" },
30
+ size: { type: "string", description: "Target pool size" },
31
+ age: { type: "string", description: "Max VM age in minutes" },
32
+ branch: { type: "string", description: "Branch for pool VMs" },
33
+ ...jsonArg,
34
+ },
35
+ async run({ args }) {
36
+ const api = await import("../api.js");
37
+ const fmt = await import("../output.js");
38
+ const { log } = await import("@clack/prompts");
39
+
40
+ const update: Record<string, unknown> = {};
41
+ if (args.enabled !== undefined) update.enabled = args.enabled;
42
+ if (args.size) update.targetSize = parseInt(args.size);
43
+ if (args.age) update.maxAgeMinutes = parseInt(args.age);
44
+ if (args.branch) update.branch = args.branch;
45
+
46
+ const pool = await api.updateWarmPool(update as any);
47
+ if (args.json) { fmt.out(pool); return; }
48
+ log.success(`Updated: size=${pool.targetSize}, age=${pool.maxAgeMinutes}min, enabled=${pool.enabled}`);
49
+ },
50
+ });
51
+
52
+ const test = defineCommand({
53
+ meta: { name: "test", description: "Test VM boot with setup commands" },
54
+ args: { ...jsonArg },
55
+ async run({ args }) {
56
+ const api = await import("../api.js");
57
+ const fmt = await import("../output.js");
58
+ const { log } = await import("@clack/prompts");
59
+
60
+ const result = await api.testWarmPool();
61
+ if (args.json) { fmt.out(result); return; }
62
+ log.success(`Test VM started: ${result.instanceId} [${result.status}]`);
63
+ log.info(`Check status: capy pool instance ${result.instanceId}`);
64
+ },
65
+ });
66
+
67
+ const instances = defineCommand({
68
+ meta: { name: "instances", description: "List warm pool VMs", alias: "ls" },
69
+ args: {
70
+ status: { type: "positional", required: false, description: "Filter: ready, provisioning, failed, claimed" },
71
+ ...jsonArg,
72
+ },
73
+ async run({ args }) {
74
+ const api = await import("../api.js");
75
+ const fmt = await import("../output.js");
76
+
77
+ const list = await api.listWarmPoolInstances({ status: args.status });
78
+ if (args.json) { fmt.out(list); return; }
79
+ if (!list.length) { console.log("No instances."); return; }
80
+ fmt.table(["ID", "STATUS", "CREATED"], list.map(i => [
81
+ i.id.slice(0, 16), i.status, i.createdAt || "?",
82
+ ]));
83
+ },
84
+ });
85
+
86
+ const instance = defineCommand({
87
+ meta: { name: "instance", description: "VM instance detail + logs" },
88
+ args: {
89
+ id: { type: "positional", description: "Instance ID", required: true },
90
+ ...jsonArg,
91
+ },
92
+ async run({ args }) {
93
+ const api = await import("../api.js");
94
+ const fmt = await import("../output.js");
95
+ const { log } = await import("@clack/prompts");
96
+
97
+ const data = await api.getWarmPoolInstance(args.id);
98
+ if (args.json) { fmt.out(data); return; }
99
+ log.info(`Instance: ${data.id}\nStatus: ${data.status}\nCreated: ${data.createdAt || "?"}`);
100
+ if (data.claimedAt) console.log(`Claimed: ${data.claimedAt}`);
101
+ if (data.logs) {
102
+ console.log(`\nLogs:\n${data.logs}`);
103
+ }
104
+ },
105
+ });
106
+
107
+ const clear = defineCommand({
108
+ meta: { name: "clear", description: "Clear all warm pool VMs" },
109
+ args: {
110
+ replenish: { type: "boolean", description: "Replenish after clearing", default: false },
111
+ ...jsonArg,
112
+ },
113
+ async run({ args }) {
114
+ const api = await import("../api.js");
115
+ const fmt = await import("../output.js");
116
+ const { log } = await import("@clack/prompts");
117
+
118
+ const result = await api.clearWarmPool({ replenish: args.replenish });
119
+ if (args.json) { fmt.out(result); return; }
120
+ log.success(`Pool cleared.${args.replenish ? " Replenishing..." : ""}`);
121
+ },
122
+ });
123
+
124
+ export default defineCommand({
125
+ meta: { name: "pool", description: "Manage warm pool VMs" },
126
+ default: "status",
127
+ subCommands: { status, set, test, instances, instance, clear },
128
+ });
@@ -178,6 +178,7 @@ export const tools = defineCommand({
178
178
  watch: { args: "<id>", desc: "Poll + notify on completion" },
179
179
  unwatch: { args: "<id>", desc: "Stop watching" },
180
180
  watches: { args: "", desc: "List watches" },
181
+ pool: { args: "[status|set|test|...]", desc: "Manage warm pool VMs" },
181
182
  models: { args: "", desc: "List models" },
182
183
  tools: { args: "", desc: "This list" },
183
184
  config: { args: "[key] [value]", desc: "Get/set config" },
package/src/mcp.ts CHANGED
@@ -278,12 +278,16 @@ server.registerTool("capy_msg", {
278
278
  inputSchema: {
279
279
  id: z.string().describe("Task or thread ID"),
280
280
  text: z.string().describe("Message text"),
281
+ model: z.string().optional().describe("Switch model mid-conversation (threads only)"),
282
+ attachmentUrls: z.array(z.string()).optional().describe("URLs to attach"),
281
283
  },
282
284
  annotations: { openWorldHint: true },
283
- }, async ({ id, text: msg }) => {
285
+ }, async ({ id, text: msg, model, attachmentUrls }) => {
284
286
  try {
285
287
  const isThread = isThreadId(id);
286
- const result = isThread ? await api.messageThread(id, msg) : await api.messageTask(id, msg);
288
+ const result = isThread
289
+ ? await api.messageThread(id, msg, { model, attachmentUrls })
290
+ : await api.messageTask(id, msg, { attachmentUrls });
287
291
  return text({ id, sent: true, type: isThread ? "thread" : "task", ...(result && typeof result === "object" ? result as Record<string, unknown> : {}) });
288
292
  } catch (e) { return err(e); }
289
293
  });
@@ -308,11 +312,17 @@ server.registerTool("capy_pr", {
308
312
  inputSchema: {
309
313
  id: z.string().describe("Task ID"),
310
314
  title: z.string().optional().describe("PR title override"),
315
+ description: z.string().optional().describe("PR body/description"),
316
+ draft: z.boolean().optional().describe("Create as draft PR"),
311
317
  },
312
318
  annotations: { openWorldHint: true },
313
- }, async ({ id, title }) => {
319
+ }, async ({ id, title, description, draft }) => {
314
320
  try {
315
- const data = await api.createPR(id, title ? { title } : {});
321
+ const opts: Record<string, unknown> = {};
322
+ if (title) opts.title = title;
323
+ if (description) opts.description = description;
324
+ if (draft != null) opts.draft = draft;
325
+ const data = await api.createPR(id, opts);
316
326
  return text(data);
317
327
  } catch (e) { return err(e); }
318
328
  });
@@ -383,5 +393,61 @@ server.registerTool("capy_models", {
383
393
  } catch (e) { return err(e); }
384
394
  });
385
395
 
396
+ // --- Warm Pool ---
397
+
398
+ server.registerTool("capy_pool_status", {
399
+ description: "Get warm pool config and VM status",
400
+ inputSchema: {},
401
+ annotations: { readOnlyHint: true, idempotentHint: true },
402
+ }, async () => {
403
+ try {
404
+ const pool = await api.getWarmPool();
405
+ return text(pool);
406
+ } catch (e) { return err(e); }
407
+ });
408
+
409
+ server.registerTool("capy_pool_update", {
410
+ description: "Update warm pool configuration (VM pre-warming)",
411
+ inputSchema: {
412
+ enabled: z.boolean().optional().describe("Enable/disable warm pool"),
413
+ targetSize: z.number().optional().describe("Number of VMs to keep warm"),
414
+ maxAgeMinutes: z.number().optional().describe("Max VM age before recycling"),
415
+ branch: z.string().optional().describe("Branch for pool VMs"),
416
+ setupCommands: z.array(z.string()).optional().describe("Commands to run on VM boot"),
417
+ },
418
+ annotations: { openWorldHint: true },
419
+ }, async (params) => {
420
+ try {
421
+ const data = await api.updateWarmPool(params);
422
+ return text(data);
423
+ } catch (e) { return err(e); }
424
+ });
425
+
426
+ server.registerTool("capy_pool_instances", {
427
+ description: "List warm pool VM instances",
428
+ inputSchema: {
429
+ status: z.string().optional().describe("Filter: ready, provisioning, failed, claimed"),
430
+ },
431
+ annotations: { readOnlyHint: true, idempotentHint: true },
432
+ }, async ({ status }) => {
433
+ try {
434
+ const data = await api.listWarmPoolInstances({ status });
435
+ return text(data);
436
+ } catch (e) { return err(e); }
437
+ });
438
+
439
+ server.registerTool("capy_pool_clear", {
440
+ description: "Clear all warm pool VMs",
441
+ inputSchema: {
442
+ replenish: z.boolean().optional().describe("Replenish pool after clearing"),
443
+ },
444
+ annotations: { destructiveHint: true },
445
+ }, async ({ replenish }) => {
446
+ try {
447
+ const data = await api.clearWarmPool({ replenish });
448
+ return text(data);
449
+ } catch (e) { return err(e); }
450
+ });
451
+
386
452
  const transport = new StdioServerTransport();
387
453
  await server.connect(transport);