@cursor/july 0.1.25 → 0.1.27

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 (288) 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 +31 -9
  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/cursor-account.d.ts +2 -1
  17. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  18. package/dist/channels/slack/cursor-account.js +1 -1
  19. package/dist/channels/slack/dispatch.d.ts +4 -1
  20. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  21. package/dist/channels/slack/dispatch.js +3 -3
  22. package/dist/channels/slack/interactive.d.ts +2 -1
  23. package/dist/channels/slack/interactive.d.ts.map +1 -1
  24. package/dist/channels/slack/interactive.js +1 -0
  25. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  26. package/dist/channels/slack/slack-channel.js +4 -3
  27. package/dist/channels/slack/types.d.ts +6 -1
  28. package/dist/channels/slack/types.d.ts.map +1 -1
  29. package/dist/docs/404.html +1 -1
  30. package/dist/docs/ab.html +2 -2
  31. package/dist/docs/assets/{app.DYcC9FY-.js → app.BICLoJYF.js} +1 -1
  32. package/dist/docs/assets/chunks/@localSearchIndexroot.CQJytDwU.js +1 -0
  33. package/dist/docs/assets/chunks/{VPLocalSearchBox.o4N_knTV.js → VPLocalSearchBox.wN4Ko9dT.js} +1 -1
  34. package/dist/docs/assets/chunks/{theme.DQ-njyo0.js → theme.dnnTE-Co.js} +2 -2
  35. package/dist/docs/building-with-agents.html +2 -2
  36. package/dist/docs/concepts.html +2 -2
  37. package/dist/docs/deployment.html +2 -2
  38. package/dist/docs/evals.html +2 -2
  39. package/dist/docs/example-agents/approval-buddy.html +2 -2
  40. package/dist/docs/example-agents/benny.html +2 -2
  41. package/dist/docs/example-agents/bugbot.html +2 -2
  42. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  43. package/dist/docs/example-agents/codeowners-review.html +2 -2
  44. package/dist/docs/example-agents/concierge.html +2 -2
  45. package/dist/docs/example-agents/fsd.html +2 -2
  46. package/dist/docs/example-agents/index.html +2 -2
  47. package/dist/docs/example-agents/knowledge-base.html +2 -2
  48. package/dist/docs/example-agents/oncall.html +2 -2
  49. package/dist/docs/example-agents/security-reviewer.html +2 -2
  50. package/dist/docs/example-agents/slack-agent.html +2 -2
  51. package/dist/docs/example-agents/weather-agent.html +2 -2
  52. package/dist/docs/guides/agent-to-agent.html +2 -2
  53. package/dist/docs/guides/cloud-runtime.html +2 -2
  54. package/dist/docs/guides/github.html +2 -2
  55. package/dist/docs/guides/human-in-the-loop.html +2 -2
  56. package/dist/docs/guides/mcp-oauth.html +2 -2
  57. package/dist/docs/guides/slack.html +2 -2
  58. package/dist/docs/guides/webhooks.html +2 -2
  59. package/dist/docs/hillclimbing.html +2 -2
  60. package/dist/docs/index.html +2 -2
  61. package/dist/docs/quickstart.html +2 -2
  62. package/dist/docs/reference/agent-config.html +2 -2
  63. package/dist/docs/reference/channels.html +2 -2
  64. package/dist/docs/reference/cli.html +2 -2
  65. package/dist/docs/reference/connections.html +2 -2
  66. package/dist/docs/reference/hooks.html +2 -2
  67. package/dist/docs/reference/http-api.html +2 -2
  68. package/dist/docs/reference/instructions.html +2 -2
  69. package/dist/docs/reference/playground.html +2 -2
  70. package/dist/docs/reference/project-layout.html +2 -2
  71. package/dist/docs/reference/prompt.html +2 -2
  72. package/dist/docs/reference/schedules.html +2 -2
  73. package/dist/docs/reference/sessions.html +2 -2
  74. package/dist/docs/reference/skills.html +2 -2
  75. package/dist/docs/reference/subagents.html +2 -2
  76. package/dist/docs/reference/tools.html +2 -2
  77. package/dist/docs/scaffolding-agents.html +2 -2
  78. package/dist/docs/storage.html +2 -2
  79. package/dist/docs/troubleshooting.html +2 -2
  80. package/dist/evals/assertions.d.ts +10 -1
  81. package/dist/evals/assertions.d.ts.map +1 -1
  82. package/dist/evals/assertions.js +12 -0
  83. package/dist/evals.d.ts +1 -1
  84. package/dist/evals.d.ts.map +1 -1
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +1 -0
  88. package/dist/internal/ab-fold.d.ts.map +1 -1
  89. package/dist/internal/ab-fold.js +4 -0
  90. package/dist/internal/ab-snapshot.d.ts.map +1 -1
  91. package/dist/internal/ab-snapshot.js +1 -0
  92. package/dist/internal/artifacts-store.d.ts +45 -0
  93. package/dist/internal/artifacts-store.d.ts.map +1 -0
  94. package/dist/internal/artifacts-store.js +250 -0
  95. package/dist/internal/builtin-tools/artifacts.d.ts +10 -0
  96. package/dist/internal/builtin-tools/artifacts.d.ts.map +1 -0
  97. package/dist/internal/builtin-tools/artifacts.js +73 -0
  98. package/dist/internal/builtin-tools/index.d.ts +8 -1
  99. package/dist/internal/builtin-tools/index.d.ts.map +1 -1
  100. package/dist/internal/builtin-tools/index.js +15 -0
  101. package/dist/internal/cli-ax.d.ts +11 -0
  102. package/dist/internal/cli-ax.d.ts.map +1 -1
  103. package/dist/internal/cli-ax.js +80 -0
  104. package/dist/internal/cli-slack.d.ts.map +1 -1
  105. package/dist/internal/cli-slack.js +14 -6
  106. package/dist/internal/discovery.d.ts.map +1 -1
  107. package/dist/internal/discovery.js +83 -2
  108. package/dist/internal/distribution.d.ts.map +1 -1
  109. package/dist/internal/distribution.js +1 -0
  110. package/dist/internal/model-pricing.d.ts +49 -0
  111. package/dist/internal/model-pricing.d.ts.map +1 -0
  112. package/dist/internal/model-pricing.js +377 -0
  113. package/dist/internal/reminder-runner.d.ts.map +1 -1
  114. package/dist/internal/reminder-runner.js +1 -0
  115. package/dist/internal/schedule-runner.d.ts.map +1 -1
  116. package/dist/internal/schedule-runner.js +1 -0
  117. package/dist/internal/sdk-runner.d.ts.map +1 -1
  118. package/dist/internal/sdk-runner.js +9 -1
  119. package/dist/internal/server.d.ts.map +1 -1
  120. package/dist/internal/server.js +39 -0
  121. package/dist/internal/session-cost.d.ts +49 -0
  122. package/dist/internal/session-cost.d.ts.map +1 -0
  123. package/dist/internal/session-cost.js +175 -0
  124. package/dist/internal/session-engine.d.ts +35 -1
  125. package/dist/internal/session-engine.d.ts.map +1 -1
  126. package/dist/internal/session-engine.js +128 -7
  127. package/dist/internal/storage-coordinator.d.ts +15 -0
  128. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  129. package/dist/internal/storage-coordinator.js +52 -0
  130. package/dist/internal/trajectory.d.ts +9 -1
  131. package/dist/internal/trajectory.d.ts.map +1 -1
  132. package/dist/internal/trajectory.js +32 -5
  133. package/dist/playground/assets/index-BDCeW4uc.js +86 -0
  134. package/dist/playground/assets/index-Bul1WfrB.css +1 -0
  135. package/dist/playground/index.html +2 -2
  136. package/dist/storage.d.ts +4 -0
  137. package/dist/storage.d.ts.map +1 -1
  138. package/dist/storage.js +4 -0
  139. package/dist/types.d.ts +164 -1
  140. package/dist/types.d.ts.map +1 -1
  141. package/package.json +26 -26
  142. package/src/ab.ts +7 -0
  143. package/src/bin/agent-serve.ts +30 -2
  144. package/src/channels/github/github-channel.ts +19 -2
  145. package/src/channels/github/types.ts +6 -0
  146. package/src/channels/slack/api.ts +3 -1
  147. package/src/channels/slack/cursor-account.ts +3 -1
  148. package/src/channels/slack/dispatch.ts +12 -1
  149. package/src/channels/slack/interactive.ts +3 -0
  150. package/src/channels/slack/slack-channel.ts +6 -0
  151. package/src/channels/slack/types.ts +6 -0
  152. package/src/internal/ab-fold.ts +4 -0
  153. package/src/internal/ab-snapshot.ts +1 -0
  154. package/src/internal/cli-ax.ts +95 -0
  155. package/src/internal/model-pricing.ts +426 -0
  156. package/src/internal/reminder-runner.ts +1 -0
  157. package/src/internal/schedule-runner.ts +1 -0
  158. package/src/internal/sdk-runner.ts +10 -2
  159. package/src/internal/server.ts +2 -0
  160. package/src/internal/session-cost.ts +231 -0
  161. package/src/internal/session-engine.ts +32 -1
  162. package/src/internal/trajectory.ts +45 -3
  163. package/src/types.ts +42 -2
  164. package/dist/channels/github/instrument.d.ts +0 -20
  165. package/dist/channels/github/instrument.d.ts.map +0 -1
  166. package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +0 -1
  167. package/dist/internal/json-dir-store.d.ts +0 -32
  168. package/dist/internal/json-dir-store.d.ts.map +0 -1
  169. package/dist/internal/json-dir-store.js +0 -100
  170. package/dist/playground/assets/index-CjOQ4hN9.css +0 -1
  171. package/dist/playground/assets/index-DqXdAFGa.js +0 -85
  172. package/src/bin/agent-serve.version.test.ts +0 -64
  173. package/src/channels/github/api.test.ts +0 -64
  174. package/src/channels/github/auth.test.ts +0 -105
  175. package/src/channels/github/cursor-account.test.ts +0 -204
  176. package/src/channels/github/forward.test.ts +0 -457
  177. package/src/channels/github/github.test.ts +0 -943
  178. package/src/channels/github/replay.test.ts +0 -179
  179. package/src/channels/slack/api.post-message.test.ts +0 -148
  180. package/src/channels/slack/approvals.test.ts +0 -328
  181. package/src/channels/slack/block-actions.test.ts +0 -452
  182. package/src/channels/slack/bot-mentions.test.ts +0 -267
  183. package/src/channels/slack/channel-watch.test.ts +0 -363
  184. package/src/channels/slack/cursor-account.test.ts +0 -253
  185. package/src/channels/slack/defaults.final-post.test.ts +0 -182
  186. package/src/channels/slack/dispatch.test.ts +0 -795
  187. package/src/channels/slack/eval-directive.test.ts +0 -273
  188. package/src/channels/slack/message-body.test.ts +0 -54
  189. package/src/channels/slack/nudge-store.test.ts +0 -143
  190. package/src/channels/slack/slack.test.ts +0 -391
  191. package/src/channels/slack/stop.test.ts +0 -23
  192. package/src/channels/slack/thread-context.test.ts +0 -202
  193. package/src/evals/assertions.test.ts +0 -617
  194. package/src/evals/expect.test.ts +0 -144
  195. package/src/evals/judge.test.ts +0 -181
  196. package/src/evals/loaders.test.ts +0 -132
  197. package/src/evals/matchers.test.ts +0 -95
  198. package/src/evals/reporters.test.ts +0 -303
  199. package/src/evals/run-facts.test.ts +0 -259
  200. package/src/internal/ab-snapshot.test.ts +0 -325
  201. package/src/internal/approval-gate.test.ts +0 -49
  202. package/src/internal/approvals.integration.test.ts +0 -383
  203. package/src/internal/artifacts-store.test.ts +0 -283
  204. package/src/internal/authored-loaders.test.ts +0 -31
  205. package/src/internal/builtin-tools/artifacts.test.ts +0 -247
  206. package/src/internal/builtin-tools/reminders.test.ts +0 -201
  207. package/src/internal/channel-route-schema.test.ts +0 -294
  208. package/src/internal/chat-attach.test.ts +0 -262
  209. package/src/internal/cli-deploy.test.ts +0 -1991
  210. package/src/internal/cli-docs.test.ts +0 -161
  211. package/src/internal/cli-mcp.test.ts +0 -789
  212. package/src/internal/cli-skills.test.ts +0 -133
  213. package/src/internal/cli-slack.test.ts +0 -1654
  214. package/src/internal/cloud-merge.test.ts +0 -74
  215. package/src/internal/cron.test.ts +0 -22
  216. package/src/internal/cursor/account-mcp.test.ts +0 -807
  217. package/src/internal/cursor/backend-client.test.ts +0 -591
  218. package/src/internal/cursor/credentials.test.ts +0 -351
  219. package/src/internal/cursor/github-credentials.test.ts +0 -136
  220. package/src/internal/cursor-account-mcp-auth.test.ts +0 -310
  221. package/src/internal/cursor-account.integration.test.ts +0 -441
  222. package/src/internal/cursor-event-relay.test.ts +0 -746
  223. package/src/internal/cursor-github-credentials.integration.test.ts +0 -271
  224. package/src/internal/cursor-slack-relay.test.ts +0 -525
  225. package/src/internal/deploy-source.test.ts +0 -111
  226. package/src/internal/discovery.artifact-tool.test.ts +0 -136
  227. package/src/internal/discovery.artifacts.test.ts +0 -119
  228. package/src/internal/discovery.builtin-tools.test.ts +0 -94
  229. package/src/internal/discovery.concurrency.test.ts +0 -60
  230. package/src/internal/discovery.cursor-account.test.ts +0 -133
  231. package/src/internal/discovery.cwd.test.ts +0 -83
  232. package/src/internal/discovery.hosting.test.ts +0 -80
  233. package/src/internal/discovery.identity.test.ts +0 -44
  234. package/src/internal/docs-site.test.ts +0 -66
  235. package/src/internal/duration.test.ts +0 -29
  236. package/src/internal/eval-judge-model.test.ts +0 -187
  237. package/src/internal/eval-run-store.cancel.test.ts +0 -142
  238. package/src/internal/eval-run-store.storage.test.ts +0 -211
  239. package/src/internal/eval-runner.http.test.ts +0 -403
  240. package/src/internal/eval-runner.run.test.ts +0 -928
  241. package/src/internal/evals-client.test.ts +0 -307
  242. package/src/internal/event-mapper.test.ts +0 -243
  243. package/src/internal/github-fanout.test.ts +0 -213
  244. package/src/internal/handleAgentServeTrigger.test.ts +0 -185
  245. package/src/internal/host-kv.test.ts +0 -82
  246. package/src/internal/host-platforms.test.ts +0 -126
  247. package/src/internal/http-channel.test.ts +0 -402
  248. package/src/internal/init-project.test.ts +0 -270
  249. package/src/internal/install-cursor-skills.test.ts +0 -262
  250. package/src/internal/local-env.test.ts +0 -120
  251. package/src/internal/log-ring.test.ts +0 -31
  252. package/src/internal/logs-client.test.ts +0 -350
  253. package/src/internal/mcp-endpoint.test.ts +0 -436
  254. package/src/internal/mcp-host.test.ts +0 -298
  255. package/src/internal/mcp-oauth.test.ts +0 -148
  256. package/src/internal/net.test.ts +0 -17
  257. package/src/internal/peer-connections.test.ts +0 -128
  258. package/src/internal/peer-mcp.integration.test.ts +0 -289
  259. package/src/internal/playground/toolchain.test.ts +0 -53
  260. package/src/internal/playground-cli.test.ts +0 -187
  261. package/src/internal/playground-proxy.test.ts +0 -376
  262. package/src/internal/prompt-context.integration.test.ts +0 -232
  263. package/src/internal/prompt-context.test.ts +0 -127
  264. package/src/internal/reminder-runner.test.ts +0 -390
  265. package/src/internal/reminder-store.test.ts +0 -53
  266. package/src/internal/request-headers.test.ts +0 -27
  267. package/src/internal/resolve-prod-target.test.ts +0 -787
  268. package/src/internal/resolved-connections.test.ts +0 -295
  269. package/src/internal/router.test.ts +0 -57
  270. package/src/internal/sdk-runner.test.ts +0 -290
  271. package/src/internal/server.artifacts.test.ts +0 -303
  272. package/src/internal/session-engine.artifacts.test.ts +0 -243
  273. package/src/internal/session-engine.coalesce.test.ts +0 -169
  274. package/src/internal/session-engine.concurrency.test.ts +0 -250
  275. package/src/internal/session-engine.host-oauth-mcp.test.ts +0 -110
  276. package/src/internal/session-engine.interrupt.test.ts +0 -577
  277. package/src/internal/session-engine.storage.test.ts +0 -547
  278. package/src/internal/session-urls.test.ts +0 -28
  279. package/src/internal/sessions-client.test.ts +0 -518
  280. package/src/internal/storage-coordinator.test.ts +0 -517
  281. package/src/internal/tool-call.test.ts +0 -458
  282. package/src/internal/tool-result.test.ts +0 -52
  283. package/src/internal/trajectory.approvals.test.ts +0 -83
  284. package/src/internal/trajectory.subagents.test.ts +0 -198
  285. package/src/internal/turn-governor.test.ts +0 -137
  286. package/src/internal/update-check.test.ts +0 -485
  287. package/src/internal/workspace.test.ts +0 -207
  288. package/src/storage-backends/cursor-hosted.test.ts +0 -121
@@ -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
- });