@cursor/july 0.1.26 → 0.1.28

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 (324) hide show
  1. package/dist/ab.d.ts +6 -0
  2. package/dist/ab.d.ts.map +1 -1
  3. package/dist/ab.js +1 -0
  4. package/dist/artifacts.d.ts +34 -0
  5. package/dist/artifacts.d.ts.map +1 -0
  6. package/dist/artifacts.js +57 -0
  7. package/dist/bin/agent-serve.js +43 -12
  8. package/dist/channels/github/github-channel.d.ts +1 -1
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +14 -1
  11. package/dist/channels/github/types.d.ts +6 -1
  12. package/dist/channels/github/types.d.ts.map +1 -1
  13. package/dist/channels/slack/api.d.ts +2 -1
  14. package/dist/channels/slack/api.d.ts.map +1 -1
  15. package/dist/channels/slack/api.js +1 -0
  16. package/dist/channels/slack/auth.d.ts +13 -13
  17. package/dist/channels/slack/auth.d.ts.map +1 -1
  18. package/dist/channels/slack/auth.js +19 -10
  19. package/dist/channels/slack/cursor-account.d.ts +2 -1
  20. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  21. package/dist/channels/slack/cursor-account.js +3 -1
  22. package/dist/channels/slack/defaults.d.ts +7 -4
  23. package/dist/channels/slack/defaults.d.ts.map +1 -1
  24. package/dist/channels/slack/defaults.js +14 -4
  25. package/dist/channels/slack/dispatch.d.ts +10 -1
  26. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  27. package/dist/channels/slack/dispatch.js +59 -21
  28. package/dist/channels/slack/external-policy.d.ts +27 -0
  29. package/dist/channels/slack/external-policy.d.ts.map +1 -0
  30. package/dist/channels/slack/external-policy.js +53 -0
  31. package/dist/channels/slack/index.d.ts +1 -0
  32. package/dist/channels/slack/index.d.ts.map +1 -1
  33. package/dist/channels/slack/index.js +1 -0
  34. package/dist/channels/slack/interactive.d.ts +8 -1
  35. package/dist/channels/slack/interactive.d.ts.map +1 -1
  36. package/dist/channels/slack/interactive.js +43 -15
  37. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  38. package/dist/channels/slack/slack-channel.js +22 -8
  39. package/dist/channels/slack/types.d.ts +16 -1
  40. package/dist/channels/slack/types.d.ts.map +1 -1
  41. package/dist/docs/404.html +2 -2
  42. package/dist/docs/ab.html +3 -3
  43. package/dist/docs/assets/{app.DYcC9FY-.js → app.CqjrYaT2.js} +1 -1
  44. package/dist/docs/assets/chunks/@localSearchIndexroot.VWQoPFJ1.js +1 -0
  45. package/dist/docs/assets/chunks/{VPLocalSearchBox.o4N_knTV.js → VPLocalSearchBox.ag-MwUel.js} +1 -1
  46. package/dist/docs/assets/chunks/{theme.DQ-njyo0.js → theme.BS-DJWat.js} +2 -2
  47. package/dist/docs/assets/{guides_slack.md.D_xFKlnF.js → guides_slack.md.CjmJSvZS.js} +8 -4
  48. package/dist/docs/assets/guides_slack.md.CjmJSvZS.lean.js +1 -0
  49. package/dist/docs/assets/guides_webhooks.md.wSOYas3X.js +183 -0
  50. package/dist/docs/assets/{guides_webhooks.md.B1EswtUu.lean.js → guides_webhooks.md.wSOYas3X.lean.js} +1 -1
  51. package/dist/docs/assets/{index.md.Dfv5ic9t.js → index.md.CH_s5uZe.js} +1 -1
  52. package/dist/docs/assets/{reference_channels.md.DdmiKgqf.js → reference_channels.md.CDhTRfUz.js} +2 -2
  53. package/dist/docs/assets/{reference_channels.md.DdmiKgqf.lean.js → reference_channels.md.CDhTRfUz.lean.js} +1 -1
  54. package/dist/docs/building-with-agents.html +3 -3
  55. package/dist/docs/concepts.html +3 -3
  56. package/dist/docs/deployment.html +3 -3
  57. package/dist/docs/evals.html +3 -3
  58. package/dist/docs/example-agents/approval-buddy.html +3 -3
  59. package/dist/docs/example-agents/benny.html +3 -3
  60. package/dist/docs/example-agents/bugbot.html +3 -3
  61. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  62. package/dist/docs/example-agents/codeowners-review.html +3 -3
  63. package/dist/docs/example-agents/concierge.html +3 -3
  64. package/dist/docs/example-agents/fsd.html +3 -3
  65. package/dist/docs/example-agents/index.html +3 -3
  66. package/dist/docs/example-agents/knowledge-base.html +3 -3
  67. package/dist/docs/example-agents/oncall.html +3 -3
  68. package/dist/docs/example-agents/security-reviewer.html +3 -3
  69. package/dist/docs/example-agents/slack-agent.html +3 -3
  70. package/dist/docs/example-agents/weather-agent.html +3 -3
  71. package/dist/docs/guides/agent-to-agent.html +3 -3
  72. package/dist/docs/guides/cloud-runtime.html +3 -3
  73. package/dist/docs/guides/github.html +3 -3
  74. package/dist/docs/guides/human-in-the-loop.html +3 -3
  75. package/dist/docs/guides/mcp-oauth.html +3 -3
  76. package/dist/docs/guides/slack.html +11 -7
  77. package/dist/docs/guides/webhooks.html +138 -5
  78. package/dist/docs/hashmap.json +1 -1
  79. package/dist/docs/hillclimbing.html +3 -3
  80. package/dist/docs/index.html +5 -5
  81. package/dist/docs/quickstart.html +3 -3
  82. package/dist/docs/reference/agent-config.html +3 -3
  83. package/dist/docs/reference/channels.html +5 -5
  84. package/dist/docs/reference/cli.html +3 -3
  85. package/dist/docs/reference/connections.html +3 -3
  86. package/dist/docs/reference/hooks.html +3 -3
  87. package/dist/docs/reference/http-api.html +3 -3
  88. package/dist/docs/reference/instructions.html +3 -3
  89. package/dist/docs/reference/playground.html +3 -3
  90. package/dist/docs/reference/project-layout.html +3 -3
  91. package/dist/docs/reference/prompt.html +3 -3
  92. package/dist/docs/reference/schedules.html +3 -3
  93. package/dist/docs/reference/sessions.html +3 -3
  94. package/dist/docs/reference/skills.html +3 -3
  95. package/dist/docs/reference/subagents.html +3 -3
  96. package/dist/docs/reference/tools.html +3 -3
  97. package/dist/docs/scaffolding-agents.html +3 -3
  98. package/dist/docs/storage.html +3 -3
  99. package/dist/docs/troubleshooting.html +3 -3
  100. package/dist/evals/assertions.d.ts +10 -1
  101. package/dist/evals/assertions.d.ts.map +1 -1
  102. package/dist/evals/assertions.js +12 -0
  103. package/dist/evals.d.ts +1 -1
  104. package/dist/evals.d.ts.map +1 -1
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +1 -0
  108. package/dist/internal/ab-fold.d.ts.map +1 -1
  109. package/dist/internal/ab-fold.js +4 -0
  110. package/dist/internal/ab-snapshot.d.ts.map +1 -1
  111. package/dist/internal/ab-snapshot.js +1 -0
  112. package/dist/internal/artifacts-store.d.ts +45 -0
  113. package/dist/internal/artifacts-store.d.ts.map +1 -0
  114. package/dist/internal/artifacts-store.js +250 -0
  115. package/dist/internal/builtin-tools/artifacts.d.ts +10 -0
  116. package/dist/internal/builtin-tools/artifacts.d.ts.map +1 -0
  117. package/dist/internal/builtin-tools/artifacts.js +73 -0
  118. package/dist/internal/builtin-tools/index.d.ts +8 -1
  119. package/dist/internal/builtin-tools/index.d.ts.map +1 -1
  120. package/dist/internal/builtin-tools/index.js +15 -0
  121. package/dist/internal/cli-ax.d.ts +19 -0
  122. package/dist/internal/cli-ax.d.ts.map +1 -1
  123. package/dist/internal/cli-ax.js +196 -57
  124. package/dist/internal/cli-slack.d.ts.map +1 -1
  125. package/dist/internal/cli-slack.js +14 -6
  126. package/dist/internal/cursor-slack-relay.d.ts +2 -0
  127. package/dist/internal/cursor-slack-relay.d.ts.map +1 -1
  128. package/dist/internal/cursor-slack-relay.js +1 -0
  129. package/dist/internal/deploy-client.d.ts +2 -0
  130. package/dist/internal/deploy-client.d.ts.map +1 -1
  131. package/dist/internal/deploy-client.js +1 -0
  132. package/dist/internal/discovery.d.ts.map +1 -1
  133. package/dist/internal/discovery.js +91 -3
  134. package/dist/internal/distribution.d.ts.map +1 -1
  135. package/dist/internal/distribution.js +1 -0
  136. package/dist/internal/model-pricing.d.ts +49 -0
  137. package/dist/internal/model-pricing.d.ts.map +1 -0
  138. package/dist/internal/model-pricing.js +377 -0
  139. package/dist/internal/reminder-runner.d.ts.map +1 -1
  140. package/dist/internal/reminder-runner.js +1 -0
  141. package/dist/internal/schedule-runner.d.ts.map +1 -1
  142. package/dist/internal/schedule-runner.js +1 -0
  143. package/dist/internal/sdk-runner.d.ts.map +1 -1
  144. package/dist/internal/sdk-runner.js +9 -1
  145. package/dist/internal/server.d.ts.map +1 -1
  146. package/dist/internal/server.js +49 -5
  147. package/dist/internal/session-cost.d.ts +49 -0
  148. package/dist/internal/session-cost.d.ts.map +1 -0
  149. package/dist/internal/session-cost.js +175 -0
  150. package/dist/internal/session-engine.d.ts +35 -1
  151. package/dist/internal/session-engine.d.ts.map +1 -1
  152. package/dist/internal/session-engine.js +128 -7
  153. package/dist/internal/storage-coordinator.d.ts +15 -0
  154. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  155. package/dist/internal/storage-coordinator.js +52 -0
  156. package/dist/internal/trajectory.d.ts +9 -1
  157. package/dist/internal/trajectory.d.ts.map +1 -1
  158. package/dist/internal/trajectory.js +32 -5
  159. package/dist/playground/assets/index-Bul1WfrB.css +1 -0
  160. package/dist/playground/assets/index-CkN74U_L.js +87 -0
  161. package/dist/playground/index.html +2 -2
  162. package/dist/storage.d.ts +4 -0
  163. package/dist/storage.d.ts.map +1 -1
  164. package/dist/storage.js +4 -0
  165. package/dist/types.d.ts +164 -1
  166. package/dist/types.d.ts.map +1 -1
  167. package/docs/README.md +1 -0
  168. package/docs/guides/slack.md +32 -7
  169. package/docs/guides/webhooks.md +183 -0
  170. package/docs/reference/channels.md +2 -1
  171. package/package.json +26 -26
  172. package/skills/setup-slack/SKILL.md +3 -0
  173. package/src/ab.ts +7 -0
  174. package/src/bin/agent-serve.ts +42 -5
  175. package/src/channels/slack/auth.ts +29 -22
  176. package/src/channels/slack/cursor-account.ts +2 -0
  177. package/src/channels/slack/defaults.ts +24 -4
  178. package/src/channels/slack/dispatch.ts +42 -1
  179. package/src/channels/slack/external-policy.ts +62 -0
  180. package/src/channels/slack/index.ts +1 -0
  181. package/src/channels/slack/interactive.ts +42 -10
  182. package/src/channels/slack/slack-channel.ts +20 -3
  183. package/src/channels/slack/types.ts +12 -0
  184. package/src/internal/ab-fold.ts +4 -0
  185. package/src/internal/ab-snapshot.ts +1 -0
  186. package/src/internal/cli-ax.ts +205 -34
  187. package/src/internal/cursor-slack-relay.ts +3 -0
  188. package/src/internal/deploy-client.ts +3 -0
  189. package/src/internal/discovery.ts +8 -1
  190. package/src/internal/model-pricing.ts +426 -0
  191. package/src/internal/sdk-runner.ts +10 -2
  192. package/src/internal/server.ts +9 -5
  193. package/src/internal/session-cost.ts +231 -0
  194. package/src/internal/session-engine.ts +28 -0
  195. package/src/internal/trajectory.ts +45 -3
  196. package/src/types.ts +24 -1
  197. package/dist/channels/github/instrument.d.ts +0 -20
  198. package/dist/channels/github/instrument.d.ts.map +0 -1
  199. package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +0 -1
  200. package/dist/docs/assets/guides_slack.md.D_xFKlnF.lean.js +0 -1
  201. package/dist/docs/assets/guides_webhooks.md.B1EswtUu.js +0 -50
  202. package/dist/internal/json-dir-store.d.ts +0 -32
  203. package/dist/internal/json-dir-store.d.ts.map +0 -1
  204. package/dist/internal/json-dir-store.js +0 -100
  205. package/dist/playground/assets/index-CjOQ4hN9.css +0 -1
  206. package/dist/playground/assets/index-DqXdAFGa.js +0 -85
  207. package/src/bin/agent-serve.version.test.ts +0 -64
  208. package/src/channels/github/api.test.ts +0 -64
  209. package/src/channels/github/auth.test.ts +0 -105
  210. package/src/channels/github/cursor-account.test.ts +0 -204
  211. package/src/channels/github/forward.test.ts +0 -457
  212. package/src/channels/github/github.test.ts +0 -985
  213. package/src/channels/github/replay.test.ts +0 -179
  214. package/src/channels/slack/api.post-message.test.ts +0 -148
  215. package/src/channels/slack/approvals.test.ts +0 -328
  216. package/src/channels/slack/block-actions.test.ts +0 -469
  217. package/src/channels/slack/bot-mentions.test.ts +0 -267
  218. package/src/channels/slack/channel-watch.test.ts +0 -363
  219. package/src/channels/slack/cursor-account.test.ts +0 -301
  220. package/src/channels/slack/defaults.final-post.test.ts +0 -182
  221. package/src/channels/slack/dispatch.test.ts +0 -875
  222. package/src/channels/slack/eval-directive.test.ts +0 -273
  223. package/src/channels/slack/message-body.test.ts +0 -54
  224. package/src/channels/slack/nudge-store.test.ts +0 -143
  225. package/src/channels/slack/slack.test.ts +0 -391
  226. package/src/channels/slack/stop.test.ts +0 -23
  227. package/src/channels/slack/thread-context.test.ts +0 -202
  228. package/src/evals/assertions.test.ts +0 -617
  229. package/src/evals/expect.test.ts +0 -144
  230. package/src/evals/judge.test.ts +0 -181
  231. package/src/evals/loaders.test.ts +0 -132
  232. package/src/evals/matchers.test.ts +0 -95
  233. package/src/evals/reporters.test.ts +0 -303
  234. package/src/evals/run-facts.test.ts +0 -259
  235. package/src/internal/ab-snapshot.test.ts +0 -325
  236. package/src/internal/approval-gate.test.ts +0 -49
  237. package/src/internal/approvals.integration.test.ts +0 -383
  238. package/src/internal/artifacts-store.test.ts +0 -283
  239. package/src/internal/authored-loaders.test.ts +0 -31
  240. package/src/internal/builtin-tools/artifacts.test.ts +0 -247
  241. package/src/internal/builtin-tools/reminders.test.ts +0 -201
  242. package/src/internal/channel-route-schema.test.ts +0 -294
  243. package/src/internal/chat-attach.test.ts +0 -262
  244. package/src/internal/cli-deploy.test.ts +0 -1991
  245. package/src/internal/cli-docs.test.ts +0 -161
  246. package/src/internal/cli-mcp.test.ts +0 -789
  247. package/src/internal/cli-skills.test.ts +0 -133
  248. package/src/internal/cli-slack.test.ts +0 -1654
  249. package/src/internal/cloud-merge.test.ts +0 -74
  250. package/src/internal/cron.test.ts +0 -22
  251. package/src/internal/cursor/account-mcp.test.ts +0 -807
  252. package/src/internal/cursor/backend-client.test.ts +0 -591
  253. package/src/internal/cursor/credentials.test.ts +0 -351
  254. package/src/internal/cursor/github-credentials.test.ts +0 -136
  255. package/src/internal/cursor-account-mcp-auth.test.ts +0 -310
  256. package/src/internal/cursor-account.integration.test.ts +0 -441
  257. package/src/internal/cursor-event-relay.test.ts +0 -746
  258. package/src/internal/cursor-github-credentials.integration.test.ts +0 -271
  259. package/src/internal/cursor-slack-relay.test.ts +0 -525
  260. package/src/internal/deploy-source.test.ts +0 -111
  261. package/src/internal/discovery.artifact-tool.test.ts +0 -136
  262. package/src/internal/discovery.artifacts.test.ts +0 -119
  263. package/src/internal/discovery.builtin-tools.test.ts +0 -94
  264. package/src/internal/discovery.concurrency.test.ts +0 -60
  265. package/src/internal/discovery.cursor-account.test.ts +0 -133
  266. package/src/internal/discovery.cwd.test.ts +0 -83
  267. package/src/internal/discovery.hosting.test.ts +0 -80
  268. package/src/internal/discovery.identity.test.ts +0 -44
  269. package/src/internal/docs-site.test.ts +0 -66
  270. package/src/internal/duration.test.ts +0 -29
  271. package/src/internal/eval-judge-model.test.ts +0 -187
  272. package/src/internal/eval-run-store.cancel.test.ts +0 -142
  273. package/src/internal/eval-run-store.storage.test.ts +0 -211
  274. package/src/internal/eval-runner.http.test.ts +0 -403
  275. package/src/internal/eval-runner.run.test.ts +0 -928
  276. package/src/internal/evals-client.test.ts +0 -307
  277. package/src/internal/event-mapper.test.ts +0 -243
  278. package/src/internal/github-fanout.test.ts +0 -213
  279. package/src/internal/handleAgentServeTrigger.test.ts +0 -185
  280. package/src/internal/host-kv.test.ts +0 -82
  281. package/src/internal/host-platforms.test.ts +0 -126
  282. package/src/internal/http-channel.test.ts +0 -402
  283. package/src/internal/init-project.test.ts +0 -270
  284. package/src/internal/install-cursor-skills.test.ts +0 -262
  285. package/src/internal/local-env.test.ts +0 -120
  286. package/src/internal/log-ring.test.ts +0 -31
  287. package/src/internal/logs-client.test.ts +0 -350
  288. package/src/internal/mcp-endpoint.test.ts +0 -436
  289. package/src/internal/mcp-host.test.ts +0 -298
  290. package/src/internal/mcp-oauth.test.ts +0 -148
  291. package/src/internal/net.test.ts +0 -17
  292. package/src/internal/peer-connections.test.ts +0 -128
  293. package/src/internal/peer-mcp.integration.test.ts +0 -289
  294. package/src/internal/playground/toolchain.test.ts +0 -53
  295. package/src/internal/playground-cli.test.ts +0 -187
  296. package/src/internal/playground-proxy.test.ts +0 -376
  297. package/src/internal/prompt-context.integration.test.ts +0 -232
  298. package/src/internal/prompt-context.test.ts +0 -127
  299. package/src/internal/reminder-runner.test.ts +0 -432
  300. package/src/internal/reminder-store.test.ts +0 -53
  301. package/src/internal/request-headers.test.ts +0 -27
  302. package/src/internal/resolve-prod-target.test.ts +0 -787
  303. package/src/internal/resolved-connections.test.ts +0 -295
  304. package/src/internal/router.test.ts +0 -57
  305. package/src/internal/sdk-runner.test.ts +0 -290
  306. package/src/internal/server.artifacts.test.ts +0 -334
  307. package/src/internal/session-engine.artifacts.test.ts +0 -293
  308. package/src/internal/session-engine.coalesce.test.ts +0 -169
  309. package/src/internal/session-engine.concurrency.test.ts +0 -250
  310. package/src/internal/session-engine.host-oauth-mcp.test.ts +0 -110
  311. package/src/internal/session-engine.interrupt.test.ts +0 -577
  312. package/src/internal/session-engine.storage.test.ts +0 -547
  313. package/src/internal/session-urls.test.ts +0 -28
  314. package/src/internal/sessions-client.test.ts +0 -518
  315. package/src/internal/storage-coordinator.test.ts +0 -517
  316. package/src/internal/tool-call.test.ts +0 -458
  317. package/src/internal/tool-result.test.ts +0 -52
  318. package/src/internal/trajectory.approvals.test.ts +0 -83
  319. package/src/internal/trajectory.subagents.test.ts +0 -198
  320. package/src/internal/turn-governor.test.ts +0 -137
  321. package/src/internal/update-check.test.ts +0 -485
  322. package/src/internal/workspace.test.ts +0 -207
  323. package/src/storage-backends/cursor-hosted.test.ts +0 -121
  324. /package/dist/docs/assets/{index.md.Dfv5ic9t.lean.js → index.md.CH_s5uZe.lean.js} +0 -0
@@ -1,789 +0,0 @@
1
- /**
2
- * `agentkit mcp` — the stdio ↔ streamable-HTTP proxy and the
3
- * `mcp install` config writer.
4
- *
5
- * Serve-mode tests drive the real wire path: raw newline-delimited JSON-RPC
6
- * into the proxy, which POSTs to a real `startServer` MCP endpoint (mock
7
- * runner) or a stub engine (prod credential refresh).
8
- */
9
-
10
- import { once } from "node:events";
11
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
12
- import { createServer, type Server } from "node:http";
13
- import type { AddressInfo } from "node:net";
14
- import { tmpdir } from "node:os";
15
- import { join } from "node:path";
16
- import { PassThrough } from "node:stream";
17
- import { afterEach, describe, expect, it, vi } from "vitest";
18
- import { httpChannel } from "../channels.js";
19
- import type {
20
- AgentProject,
21
- AgentServeHandle,
22
- RunnerTurnRequest,
23
- } from "../types.js";
24
- import type { resolveDeployAuth } from "./cli-deploy.js";
25
- import {
26
- cmdMcp,
27
- cmdMcpInstall,
28
- cmdMcpServe,
29
- cursorMcpJsonPath,
30
- joinMcpPath,
31
- } from "./cli-mcp.js";
32
- import { AgentServeDeployClient } from "./deploy-client.js";
33
- import type { resolveProdTarget } from "./resolve-prod-target.js";
34
- import type { AgentRunner } from "./sdk-runner.js";
35
- import { startServer } from "./server.js";
36
-
37
- type ResolveProdTargetImpl = typeof resolveProdTarget;
38
-
39
- function makeProject(name: string): AgentProject {
40
- return {
41
- rootDir: `/tmp/mcp-cli-fixture-${name}`,
42
- agentDir: `/tmp/mcp-cli-fixture-${name}/agent`,
43
- name,
44
- agent: {
45
- name,
46
- description: `The ${name} fixture agent.`,
47
- runtime: "local",
48
- tools: [],
49
- skills: [],
50
- connections: [],
51
- subagents: [],
52
- seedFiles: [],
53
- },
54
- httpChannel: httpChannel(),
55
- channels: [],
56
- schedules: [],
57
- hooks: [],
58
- abs: [],
59
- diagnostics: [],
60
- };
61
- }
62
-
63
- interface WireCapture {
64
- sink: { write(chunk: string): unknown };
65
- next: () => Promise<string>;
66
- drained: () => string[];
67
- }
68
-
69
- function createWireCapture(): WireCapture {
70
- const lines: string[] = [];
71
- const waiters: Array<(line: string) => void> = [];
72
- let buffer = "";
73
- return {
74
- sink: {
75
- write(chunk: string): boolean {
76
- buffer += chunk;
77
- let index = buffer.indexOf("\n");
78
- while (index !== -1) {
79
- const line = buffer.slice(0, index);
80
- buffer = buffer.slice(index + 1);
81
- index = buffer.indexOf("\n");
82
- const waiter = waiters.shift();
83
- if (waiter !== undefined) {
84
- waiter(line);
85
- } else {
86
- lines.push(line);
87
- }
88
- }
89
- return true;
90
- },
91
- },
92
- next: () => {
93
- const line = lines.shift();
94
- if (line !== undefined) {
95
- return Promise.resolve(line);
96
- }
97
- return new Promise<string>((resolve, reject) => {
98
- const timer = setTimeout(() => {
99
- reject(new Error("timed out waiting for a wire line"));
100
- }, 15_000);
101
- waiters.push((value) => {
102
- clearTimeout(timer);
103
- resolve(value);
104
- });
105
- });
106
- },
107
- drained: () => [...lines],
108
- };
109
- }
110
-
111
- interface ProxySession {
112
- send: (message: Record<string, unknown>) => void;
113
- next: () => Promise<Record<string, unknown>>;
114
- stdoutLines: () => string[];
115
- stderrText: () => string;
116
- finish: () => Promise<number>;
117
- }
118
-
119
- function startProxySession(
120
- options: Omit<Parameters<typeof cmdMcpServe>[0], "stdin" | "stdout" | "err">
121
- ): ProxySession {
122
- const stdin = new PassThrough();
123
- const capture = createWireCapture();
124
- const stderr: string[] = [];
125
- const exit = cmdMcpServe({
126
- ...options,
127
- stdin,
128
- stdout: capture.sink,
129
- err: (text) => {
130
- stderr.push(text);
131
- },
132
- });
133
- return {
134
- send: (message) => {
135
- stdin.write(`${JSON.stringify(message)}\n`);
136
- },
137
- next: async () =>
138
- JSON.parse(await capture.next()) as Record<string, unknown>,
139
- stdoutLines: () => capture.drained(),
140
- stderrText: () => stderr.join(""),
141
- finish: async () => {
142
- stdin.end();
143
- return exit;
144
- },
145
- };
146
- }
147
-
148
- const INITIALIZE = {
149
- jsonrpc: "2.0",
150
- id: 0,
151
- method: "initialize",
152
- params: {
153
- protocolVersion: "2025-06-18",
154
- capabilities: {},
155
- clientInfo: { name: "cli-mcp-test", version: "0.0.0" },
156
- },
157
- };
158
-
159
- describe("joinMcpPath", () => {
160
- it("appends /v1/mcp to an agent base URL, idempotently", () => {
161
- expect(joinMcpPath("http://127.0.0.1:3000/weather")).toBe(
162
- "http://127.0.0.1:3000/weather/v1/mcp"
163
- );
164
- expect(joinMcpPath("http://127.0.0.1:3000/weather/")).toBe(
165
- "http://127.0.0.1:3000/weather/v1/mcp"
166
- );
167
- expect(joinMcpPath("http://127.0.0.1:3000/weather/v1/mcp")).toBe(
168
- "http://127.0.0.1:3000/weather/v1/mcp"
169
- );
170
- expect(joinMcpPath("http://127.0.0.1:3000/weather?token=abc")).toBe(
171
- "http://127.0.0.1:3000/weather/v1/mcp?token=abc"
172
- );
173
- });
174
- });
175
-
176
- describe("cmdMcpServe stdin framing", () => {
177
- it("processes a final message when stdin ends without a trailing newline", async () => {
178
- const stdin = new PassThrough();
179
- const capture = createWireCapture();
180
- const fetchImpl = vi.fn(async () =>
181
- Response.json({ jsonrpc: "2.0", id: 3, result: { tools: [] } })
182
- );
183
- const exit = cmdMcpServe({
184
- dir: ".",
185
- url: "http://127.0.0.1:3000/helper",
186
- stdin,
187
- stdout: capture.sink,
188
- fetchImpl,
189
- sleep: async () => {},
190
- });
191
- stdin.write(
192
- JSON.stringify({ jsonrpc: "2.0", id: 3, method: "tools/list" })
193
- );
194
- stdin.end();
195
- expect(await exit).toBe(0);
196
- expect(fetchImpl).toHaveBeenCalledTimes(1);
197
- expect(fetchImpl).toHaveBeenCalledWith(
198
- "http://127.0.0.1:3000/helper/v1/mcp",
199
- expect.anything()
200
- );
201
- const response = JSON.parse(await capture.next()) as Record<
202
- string,
203
- unknown
204
- >;
205
- expect(response.id).toBe(3);
206
- });
207
- });
208
-
209
- describe("cmdMcpServe --url against a served agent", () => {
210
- let handle: AgentServeHandle | undefined;
211
- let stateRoot: string | undefined;
212
-
213
- afterEach(async () => {
214
- await handle?.close();
215
- handle = undefined;
216
- if (stateRoot !== undefined) {
217
- await rm(stateRoot, { recursive: true, force: true });
218
- stateRoot = undefined;
219
- }
220
- });
221
-
222
- it("proxies initialize, notifications, tools/list, and ask end-to-end", async () => {
223
- stateRoot = await mkdtemp(join(tmpdir(), "agentkit-mcp-cli-"));
224
- const runner: AgentRunner = {
225
- async runTurn(request: RunnerTurnRequest) {
226
- void request;
227
- return { status: "finished", result: "the answer is 4" };
228
- },
229
- };
230
- handle = await startServer(
231
- [{ slug: "helper", project: makeProject("helper") }],
232
- { port: 0, stateRoot, runner, playground: false, docs: false }
233
- );
234
-
235
- const session = startProxySession({
236
- dir: ".",
237
- url: `${handle.url}/helper`,
238
- });
239
-
240
- session.send(INITIALIZE);
241
- const initResponse = await session.next();
242
- expect(initResponse.id).toBe(0);
243
- expect(initResponse).toHaveProperty("result.serverInfo.name", "helper");
244
-
245
- // Notifications relay upstream (202) and produce no wire output.
246
- session.send({ jsonrpc: "2.0", method: "notifications/initialized" });
247
-
248
- session.send({ jsonrpc: "2.0", id: 1, method: "tools/list" });
249
- const toolsResponse = await session.next();
250
- expect(toolsResponse.id).toBe(1);
251
- const tools = (
252
- toolsResponse.result as { tools: Array<{ name: string }> }
253
- ).tools.map((tool) => tool.name);
254
- expect(tools).toContain("ask");
255
- expect(tools).toContain("check");
256
-
257
- session.send({
258
- jsonrpc: "2.0",
259
- id: 2,
260
- method: "tools/call",
261
- params: { name: "ask", arguments: { message: "What is 2+2?" } },
262
- });
263
- const askResponse = await session.next();
264
- expect(askResponse.id).toBe(2);
265
- const content = (askResponse.result as { content: Array<{ text: string }> })
266
- .content;
267
- const payload = JSON.parse(content[0]?.text ?? "{}") as {
268
- status: string;
269
- reply?: string;
270
- };
271
- expect(payload.status).toBe("completed");
272
- expect(payload.reply).toBe("the answer is 4");
273
-
274
- expect(await session.finish()).toBe(0);
275
- expect(session.stdoutLines()).toEqual([]);
276
- expect(session.stderrText()).toContain("proxying stdio <->");
277
- }, 30_000);
278
- });
279
-
280
- interface StubMcpEngine {
281
- url: string;
282
- close: () => Promise<void>;
283
- seenTokens: string[];
284
- respondFor: (token: string) => void;
285
- }
286
-
287
- /**
288
- * Engine stub that 401s unless the request carries the currently accepted
289
- * port token, echoing a canned JSON-RPC response otherwise.
290
- */
291
- async function startStubMcpEngine(): Promise<StubMcpEngine> {
292
- const seenTokens: string[] = [];
293
- let acceptedToken = "";
294
- const server: Server = createServer((req, res) => {
295
- let raw = "";
296
- req.on("data", (chunk: Buffer) => {
297
- raw += chunk.toString("utf8");
298
- });
299
- req.on("end", () => {
300
- const token = String(req.headers["x-anyrun-port-token"] ?? "");
301
- seenTokens.push(token);
302
- if (token !== acceptedToken) {
303
- res.writeHead(401, { "content-type": "application/json" });
304
- res.end(JSON.stringify({ error: "expired engine access" }));
305
- return;
306
- }
307
- const inbound = JSON.parse(raw) as { id?: unknown };
308
- res.writeHead(200, { "content-type": "application/json" });
309
- res.end(
310
- JSON.stringify({
311
- jsonrpc: "2.0",
312
- id: inbound.id ?? null,
313
- result: { ok: true, token },
314
- })
315
- );
316
- });
317
- });
318
- server.listen(0, "127.0.0.1");
319
- await once(server, "listening");
320
- const { port } = server.address() as AddressInfo;
321
- return {
322
- url: `http://127.0.0.1:${port}`,
323
- seenTokens,
324
- respondFor: (token) => {
325
- acceptedToken = token;
326
- },
327
- close: () =>
328
- new Promise<void>((resolvePromise, reject) =>
329
- server.close((error) => (error ? reject(error) : resolvePromise()))
330
- ),
331
- };
332
- }
333
-
334
- describe("cmdMcpServe --prod credential lifecycle", () => {
335
- let engine: StubMcpEngine | undefined;
336
-
337
- afterEach(async () => {
338
- await engine?.close();
339
- engine = undefined;
340
- });
341
-
342
- it("re-mints engineAccess on 401 and retries the message", async () => {
343
- engine = await startStubMcpEngine();
344
- engine.respondFor("token-2");
345
-
346
- let minted = 0;
347
- const resolveImpl = vi.fn(async () => {
348
- minted += 1;
349
- return {
350
- agentUrl: engine!.url,
351
- headers: { "x-anyrun-port-token": `token-${minted}` },
352
- slug: "weather",
353
- expiresAt: "2099-01-01T00:00:00.000Z",
354
- };
355
- }) satisfies ResolveProdTargetImpl;
356
-
357
- const session = startProxySession({
358
- dir: ".",
359
- prod: true,
360
- slug: "weather",
361
- resolveProdTargetImpl: resolveImpl,
362
- sleep: async () => {},
363
- });
364
-
365
- session.send({ jsonrpc: "2.0", id: 5, method: "tools/list" });
366
- const response = await session.next();
367
- expect(response.id).toBe(5);
368
- expect(response).toHaveProperty("result.token", "token-2");
369
- // token-1 was rejected (401), invalidated, token-2 accepted.
370
- expect(engine.seenTokens).toEqual(["token-1", "token-2"]);
371
- expect(resolveImpl).toHaveBeenCalledTimes(2);
372
-
373
- expect(await session.finish()).toBe(0);
374
- });
375
-
376
- it("proactively re-resolves near expiresAt", async () => {
377
- engine = await startStubMcpEngine();
378
- engine.respondFor("token-1");
379
-
380
- let nowMs = Date.parse("2026-07-21T00:00:00.000Z");
381
- const expiresAt = new Date(nowMs + 10 * 60 * 1000).toISOString();
382
- let minted = 0;
383
- const resolveImpl = vi.fn(async () => {
384
- minted += 1;
385
- if (minted === 2) {
386
- engine!.respondFor("token-2");
387
- }
388
- return {
389
- agentUrl: engine!.url,
390
- headers: { "x-anyrun-port-token": `token-${minted}` },
391
- slug: "weather",
392
- expiresAt,
393
- };
394
- }) satisfies ResolveProdTargetImpl;
395
-
396
- const session = startProxySession({
397
- dir: ".",
398
- prod: true,
399
- slug: "weather",
400
- resolveProdTargetImpl: resolveImpl,
401
- now: () => nowMs,
402
- sleep: async () => {},
403
- });
404
-
405
- session.send({ jsonrpc: "2.0", id: 1, method: "tools/list" });
406
- expect((await session.next()).id).toBe(1);
407
- expect(resolveImpl).toHaveBeenCalledTimes(1);
408
-
409
- // Within the 5-minute leeway of expiry: the next message re-resolves.
410
- nowMs += 6 * 60 * 1000;
411
- session.send({ jsonrpc: "2.0", id: 2, method: "tools/list" });
412
- const second = await session.next();
413
- expect(second.id).toBe(2);
414
- expect(second).toHaveProperty("result.token", "token-2");
415
- expect(resolveImpl).toHaveBeenCalledTimes(2);
416
-
417
- expect(await session.finish()).toBe(0);
418
- });
419
- });
420
-
421
- describe("cmdMcpServe failure shaping", () => {
422
- it("answers requests with a JSON-RPC error when the target is unreachable", async () => {
423
- const session = startProxySession({
424
- dir: ".",
425
- // Port 9 (discard) refuses connections on loopback.
426
- url: "http://127.0.0.1:9/helper",
427
- sleep: async () => {},
428
- });
429
-
430
- session.send({ jsonrpc: "2.0", id: 7, method: "tools/list" });
431
- const response = await session.next();
432
- expect(response.id).toBe(7);
433
- expect(response).toHaveProperty("error.code", -32000);
434
- expect(String((response.error as { message: string }).message)).toContain(
435
- "request failed"
436
- );
437
-
438
- // Notification failures log to stderr but never emit wire output.
439
- session.send({ jsonrpc: "2.0", method: "notifications/initialized" });
440
- expect(await session.finish()).toBe(0);
441
- expect(session.stdoutLines()).toEqual([]);
442
- expect(session.stderrText()).toContain("request failed");
443
- });
444
-
445
- it("rejects --prod with --url and requires a target", async () => {
446
- const errText: string[] = [];
447
- const both = await cmdMcpServe({
448
- dir: ".",
449
- prod: true,
450
- url: "http://127.0.0.1:3000",
451
- err: (text) => {
452
- errText.push(text);
453
- },
454
- });
455
- expect(both).toBe(1);
456
- expect(errText.join("")).toContain("mutually exclusive");
457
-
458
- const neither = await cmdMcpServe({
459
- dir: ".",
460
- err: (text) => {
461
- errText.push(text);
462
- },
463
- });
464
- expect(neither).toBe(1);
465
- expect(errText.join("")).toContain("requires --prod");
466
- });
467
-
468
- it("rejects unknown subcommands", async () => {
469
- const errText: string[] = [];
470
- const code = await cmdMcp("uninstall", {
471
- dir: ".",
472
- err: (text) => {
473
- errText.push(text);
474
- },
475
- });
476
- expect(code).toBe(1);
477
- expect(errText.join("")).toContain('Unknown mcp subcommand "uninstall"');
478
- });
479
- });
480
-
481
- describe("cmdMcpInstall", () => {
482
- let home: string | undefined;
483
- const savedHome = process.env.HOME;
484
-
485
- afterEach(async () => {
486
- if (savedHome === undefined) {
487
- delete process.env.HOME;
488
- } else {
489
- process.env.HOME = savedHome;
490
- }
491
- if (home !== undefined) {
492
- await rm(home, { recursive: true, force: true });
493
- home = undefined;
494
- }
495
- });
496
-
497
- async function withTempHome(): Promise<string> {
498
- home = await mkdtemp(join(tmpdir(), "agentkit-mcp-home-"));
499
- process.env.HOME = home;
500
- return home;
501
- }
502
-
503
- it("writes a --url entry named after the mount slug", async () => {
504
- const dir = await withTempHome();
505
- const out = createWireCapture();
506
- const code = await cmdMcpInstall({
507
- dir: ".",
508
- url: "http://127.0.0.1:3000/weather-agent/",
509
- stdout: out.sink,
510
- err: () => {},
511
- });
512
- expect(code).toBe(0);
513
-
514
- const raw = await readFile(join(dir, ".cursor", "mcp.json"), "utf8");
515
- expect(JSON.parse(raw)).toEqual({
516
- mcpServers: {
517
- "weather-agent": {
518
- command: "agentkit",
519
- args: ["mcp", "--url", "http://127.0.0.1:3000/weather-agent"],
520
- },
521
- },
522
- });
523
- });
524
-
525
- it("requires --name when the URL has no path segment", async () => {
526
- await withTempHome();
527
- const errText: string[] = [];
528
- const code = await cmdMcpInstall({
529
- dir: ".",
530
- url: "http://127.0.0.1:3000",
531
- err: (text) => {
532
- errText.push(text);
533
- },
534
- });
535
- expect(code).toBe(1);
536
- expect(errText.join("")).toContain("pass --name");
537
- });
538
-
539
- it("verifies the hosted deployment and pins slug + team in the args", async () => {
540
- const dir = await withTempHome();
541
- const resolveImpl = vi.fn(async () => ({
542
- agentUrl: "http://127.0.0.1:4000",
543
- headers: {},
544
- slug: "weather",
545
- expiresAt: "2099-01-01T00:00:00.000Z",
546
- })) satisfies ResolveProdTargetImpl;
547
-
548
- const code = await cmdMcpInstall({
549
- dir: ".",
550
- prod: true,
551
- slug: "weather",
552
- team: "42",
553
- resolveProdTargetImpl: resolveImpl,
554
- stdout: createWireCapture().sink,
555
- err: () => {},
556
- });
557
- expect(code).toBe(0);
558
- expect(resolveImpl).toHaveBeenCalledTimes(1);
559
-
560
- const raw = await readFile(join(dir, ".cursor", "mcp.json"), "utf8");
561
- expect(JSON.parse(raw)).toEqual({
562
- mcpServers: {
563
- weather: {
564
- command: "agentkit",
565
- args: ["mcp", "--prod", "--slug", "weather", "--team", "42"],
566
- },
567
- },
568
- });
569
- });
570
-
571
- it("--remote writes a stable gateway URL with the API-key bearer", async () => {
572
- const dir = await withTempHome();
573
- const client = new AgentServeDeployClient({
574
- backendUrl: "https://api.cursor.com",
575
- apiKey: "crsr_test_key",
576
- fetchImpl: (async () =>
577
- new Response(
578
- JSON.stringify({
579
- id: 1,
580
- slug: "jenny",
581
- teamId: 42,
582
- deploymentKind: "repo",
583
- status: "running",
584
- generation: 1,
585
- engineAlias: "/internal/agent-serve/agents/asd_x/engine",
586
- }),
587
- { status: 200, headers: { "content-type": "application/json" } }
588
- )) as unknown as typeof fetch,
589
- });
590
- const resolveDeployAuthImpl: typeof resolveDeployAuth = async () => ({
591
- client,
592
- teamId: 42,
593
- apiKey: "crsr_test_key",
594
- backendUrl: "https://api.cursor.com",
595
- });
596
-
597
- const code = await cmdMcpInstall({
598
- dir: ".",
599
- prod: true,
600
- remote: true,
601
- slug: "jenny",
602
- resolveDeployAuthImpl,
603
- stdout: createWireCapture().sink,
604
- err: () => {},
605
- });
606
- expect(code).toBe(0);
607
-
608
- const raw = await readFile(join(dir, ".cursor", "mcp.json"), "utf8");
609
- expect(JSON.parse(raw)).toEqual({
610
- mcpServers: {
611
- jenny: {
612
- url: "https://api.cursor.com/internal/agent-serve/mcp/42/jenny",
613
- headers: { Authorization: "Bearer crsr_test_key" },
614
- },
615
- },
616
- });
617
- });
618
-
619
- it("--remote still installs when the caller cannot read deployments", async () => {
620
- // Reading deployments is admin-tier; calling the gateway only needs team
621
- // membership, so a 403 preflight must not lock a member out.
622
- const dir = await withTempHome();
623
- const client = new AgentServeDeployClient({
624
- backendUrl: "https://api.cursor.com",
625
- apiKey: "crsr_member_key",
626
- fetchImpl: (async () =>
627
- new Response(JSON.stringify({ error: "Not authorized" }), {
628
- status: 403,
629
- headers: { "content-type": "application/json" },
630
- })) as unknown as typeof fetch,
631
- });
632
- const resolveDeployAuthImpl: typeof resolveDeployAuth = async () => ({
633
- client,
634
- teamId: 42,
635
- apiKey: "crsr_member_key",
636
- backendUrl: "https://api.cursor.com",
637
- });
638
-
639
- const errText: string[] = [];
640
- const code = await cmdMcpInstall({
641
- dir: ".",
642
- prod: true,
643
- remote: true,
644
- slug: "jenny",
645
- resolveDeployAuthImpl,
646
- stdout: createWireCapture().sink,
647
- err: (text) => {
648
- errText.push(text);
649
- },
650
- });
651
- expect(code).toBe(0);
652
- expect(errText.join("")).toContain("Could not verify");
653
-
654
- const raw = await readFile(join(dir, ".cursor", "mcp.json"), "utf8");
655
- expect(JSON.parse(raw).mcpServers.jenny.url).toBe(
656
- "https://api.cursor.com/internal/agent-serve/mcp/42/jenny"
657
- );
658
- });
659
-
660
- it("--remote requires --prod", async () => {
661
- await withTempHome();
662
- const errText: string[] = [];
663
- const code = await cmdMcpInstall({
664
- dir: ".",
665
- remote: true,
666
- slug: "jenny",
667
- err: (text) => {
668
- errText.push(text);
669
- },
670
- });
671
- expect(code).toBe(1);
672
- expect(errText.join("")).toContain("--remote requires --prod");
673
- });
674
-
675
- it("fails without writing when the deployment cannot be resolved", async () => {
676
- const dir = await withTempHome();
677
- const resolveImpl = vi.fn(async () => {
678
- throw new Error('Deployment "weather" (team 42): Deployment not found');
679
- }) satisfies ResolveProdTargetImpl;
680
-
681
- const errText: string[] = [];
682
- const code = await cmdMcpInstall({
683
- dir: ".",
684
- prod: true,
685
- slug: "weather",
686
- resolveProdTargetImpl: resolveImpl,
687
- err: (text) => {
688
- errText.push(text);
689
- },
690
- });
691
- expect(code).toBe(1);
692
- expect(errText.join("")).toContain("Deployment not found");
693
- await expect(
694
- readFile(join(dir, ".cursor", "mcp.json"), "utf8")
695
- ).rejects.toThrow();
696
- });
697
-
698
- it("merges into an existing mcp.json without clobbering other servers", async () => {
699
- const dir = await withTempHome();
700
- const path = cursorMcpJsonPath();
701
- await mkdir(join(dir, ".cursor"), { recursive: true });
702
- await writeFile(
703
- path,
704
- `${JSON.stringify(
705
- {
706
- mcpServers: {
707
- linear: { url: "https://mcp.linear.app/mcp" },
708
- },
709
- otherTopLevelKey: true,
710
- },
711
- null,
712
- 2
713
- )}\n`,
714
- "utf8"
715
- );
716
-
717
- const code = await cmdMcpInstall({
718
- dir: ".",
719
- url: "http://127.0.0.1:3000/helper",
720
- stdout: createWireCapture().sink,
721
- err: () => {},
722
- });
723
- expect(code).toBe(0);
724
-
725
- const raw = JSON.parse(await readFile(path, "utf8")) as {
726
- mcpServers: Record<string, unknown>;
727
- otherTopLevelKey: boolean;
728
- };
729
- expect(raw.otherTopLevelKey).toBe(true);
730
- expect(raw.mcpServers.linear).toEqual({
731
- url: "https://mcp.linear.app/mcp",
732
- });
733
- expect(raw.mcpServers.helper).toEqual({
734
- command: "agentkit",
735
- args: ["mcp", "--url", "http://127.0.0.1:3000/helper"],
736
- });
737
- });
738
-
739
- it("refuses to clobber an unparseable mcp.json", async () => {
740
- const tempHome = await withTempHome();
741
- await mkdir(join(tempHome, ".cursor"), { recursive: true });
742
- const path = cursorMcpJsonPath();
743
- await writeFile(path, "{ not json", "utf8");
744
-
745
- const errText: string[] = [];
746
- const code = await cmdMcpInstall({
747
- dir: ".",
748
- url: "http://127.0.0.1:3000/helper",
749
- err: (text) => {
750
- errText.push(text);
751
- },
752
- });
753
- expect(code).toBe(1);
754
- expect(errText.join("")).toContain("not valid JSON");
755
- expect(await readFile(path, "utf8")).toBe("{ not json");
756
- });
757
-
758
- it("--print emits the entry without touching the filesystem", async () => {
759
- const dir = await withTempHome();
760
- const out = createWireCapture();
761
- const lines: string[] = [];
762
- const sink = {
763
- write(chunk: string): boolean {
764
- lines.push(chunk);
765
- out.sink.write(chunk);
766
- return true;
767
- },
768
- };
769
- const code = await cmdMcpInstall({
770
- dir: ".",
771
- url: "http://127.0.0.1:3000/helper",
772
- print: true,
773
- stdout: sink,
774
- err: () => {},
775
- });
776
- expect(code).toBe(0);
777
- expect(JSON.parse(lines.join(""))).toEqual({
778
- mcpServers: {
779
- helper: {
780
- command: "agentkit",
781
- args: ["mcp", "--url", "http://127.0.0.1:3000/helper"],
782
- },
783
- },
784
- });
785
- await expect(
786
- readFile(join(dir, ".cursor", "mcp.json"), "utf8")
787
- ).rejects.toThrow();
788
- });
789
- });