@cursor/july 0.1.23 → 0.1.24

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 (174) hide show
  1. package/dist/bin/agent-serve.js +0 -0
  2. package/dist/docs/404.html +1 -1
  3. package/dist/docs/ab.html +2 -2
  4. package/dist/docs/assets/{app.DYcC9FY-.js → app.CvSGaAxk.js} +1 -1
  5. package/dist/docs/assets/chunks/@localSearchIndexroot.CL2Y0Zmh.js +1 -0
  6. package/dist/docs/assets/chunks/{VPLocalSearchBox.o4N_knTV.js → VPLocalSearchBox.J1jJbCvs.js} +1 -1
  7. package/dist/docs/assets/chunks/{theme.DQ-njyo0.js → theme.C7vfzr7h.js} +2 -2
  8. package/dist/docs/building-with-agents.html +2 -2
  9. package/dist/docs/concepts.html +2 -2
  10. package/dist/docs/deployment.html +2 -2
  11. package/dist/docs/evals.html +2 -2
  12. package/dist/docs/example-agents/approval-buddy.html +2 -2
  13. package/dist/docs/example-agents/benny.html +2 -2
  14. package/dist/docs/example-agents/bugbot.html +2 -2
  15. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  16. package/dist/docs/example-agents/codeowners-review.html +2 -2
  17. package/dist/docs/example-agents/concierge.html +2 -2
  18. package/dist/docs/example-agents/fsd.html +2 -2
  19. package/dist/docs/example-agents/index.html +2 -2
  20. package/dist/docs/example-agents/knowledge-base.html +2 -2
  21. package/dist/docs/example-agents/oncall.html +2 -2
  22. package/dist/docs/example-agents/security-reviewer.html +2 -2
  23. package/dist/docs/example-agents/slack-agent.html +2 -2
  24. package/dist/docs/example-agents/weather-agent.html +2 -2
  25. package/dist/docs/guides/agent-to-agent.html +2 -2
  26. package/dist/docs/guides/cloud-runtime.html +2 -2
  27. package/dist/docs/guides/github.html +2 -2
  28. package/dist/docs/guides/human-in-the-loop.html +2 -2
  29. package/dist/docs/guides/mcp-oauth.html +2 -2
  30. package/dist/docs/guides/slack.html +2 -2
  31. package/dist/docs/guides/webhooks.html +2 -2
  32. package/dist/docs/hillclimbing.html +2 -2
  33. package/dist/docs/index.html +2 -2
  34. package/dist/docs/quickstart.html +2 -2
  35. package/dist/docs/reference/agent-config.html +2 -2
  36. package/dist/docs/reference/channels.html +2 -2
  37. package/dist/docs/reference/cli.html +2 -2
  38. package/dist/docs/reference/connections.html +2 -2
  39. package/dist/docs/reference/hooks.html +2 -2
  40. package/dist/docs/reference/http-api.html +2 -2
  41. package/dist/docs/reference/instructions.html +2 -2
  42. package/dist/docs/reference/playground.html +2 -2
  43. package/dist/docs/reference/project-layout.html +2 -2
  44. package/dist/docs/reference/prompt.html +2 -2
  45. package/dist/docs/reference/schedules.html +2 -2
  46. package/dist/docs/reference/sessions.html +2 -2
  47. package/dist/docs/reference/skills.html +2 -2
  48. package/dist/docs/reference/subagents.html +2 -2
  49. package/dist/docs/reference/tools.html +2 -2
  50. package/dist/docs/scaffolding-agents.html +2 -2
  51. package/dist/docs/storage.html +2 -2
  52. package/dist/docs/troubleshooting.html +2 -2
  53. package/dist/playground/assets/index-CidizGZv.css +1 -0
  54. package/dist/playground/assets/{index-DqXdAFGa.js → index-DTG9OsPV.js} +41 -41
  55. package/dist/playground/index.html +2 -2
  56. package/package.json +24 -24
  57. package/dist/channels/github/instrument.d.ts +0 -20
  58. package/dist/channels/github/instrument.d.ts.map +0 -1
  59. package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +0 -1
  60. package/dist/internal/json-dir-store.d.ts +0 -32
  61. package/dist/internal/json-dir-store.d.ts.map +0 -1
  62. package/dist/internal/json-dir-store.js +0 -100
  63. package/dist/playground/assets/index-CjOQ4hN9.css +0 -1
  64. package/src/bin/agent-serve.version.test.ts +0 -64
  65. package/src/channels/github/api.test.ts +0 -64
  66. package/src/channels/github/auth.test.ts +0 -105
  67. package/src/channels/github/cursor-account.test.ts +0 -204
  68. package/src/channels/github/forward.test.ts +0 -457
  69. package/src/channels/github/github.test.ts +0 -937
  70. package/src/channels/github/replay.test.ts +0 -179
  71. package/src/channels/slack/api.post-message.test.ts +0 -148
  72. package/src/channels/slack/approvals.test.ts +0 -328
  73. package/src/channels/slack/block-actions.test.ts +0 -452
  74. package/src/channels/slack/bot-mentions.test.ts +0 -267
  75. package/src/channels/slack/channel-watch.test.ts +0 -363
  76. package/src/channels/slack/cursor-account.test.ts +0 -253
  77. package/src/channels/slack/defaults.final-post.test.ts +0 -182
  78. package/src/channels/slack/dispatch.test.ts +0 -795
  79. package/src/channels/slack/eval-directive.test.ts +0 -273
  80. package/src/channels/slack/message-body.test.ts +0 -54
  81. package/src/channels/slack/nudge-store.test.ts +0 -143
  82. package/src/channels/slack/slack.test.ts +0 -391
  83. package/src/channels/slack/stop.test.ts +0 -23
  84. package/src/channels/slack/thread-context.test.ts +0 -202
  85. package/src/evals/assertions.test.ts +0 -580
  86. package/src/evals/expect.test.ts +0 -144
  87. package/src/evals/judge.test.ts +0 -181
  88. package/src/evals/loaders.test.ts +0 -132
  89. package/src/evals/matchers.test.ts +0 -95
  90. package/src/evals/reporters.test.ts +0 -303
  91. package/src/evals/run-facts.test.ts +0 -259
  92. package/src/internal/ab-snapshot.test.ts +0 -325
  93. package/src/internal/approval-gate.test.ts +0 -49
  94. package/src/internal/approvals.integration.test.ts +0 -383
  95. package/src/internal/authored-loaders.test.ts +0 -31
  96. package/src/internal/builtin-tools/reminders.test.ts +0 -201
  97. package/src/internal/channel-route-schema.test.ts +0 -294
  98. package/src/internal/chat-attach.test.ts +0 -262
  99. package/src/internal/cli-deploy.test.ts +0 -1991
  100. package/src/internal/cli-docs.test.ts +0 -161
  101. package/src/internal/cli-mcp.test.ts +0 -789
  102. package/src/internal/cli-skills.test.ts +0 -133
  103. package/src/internal/cli-slack.test.ts +0 -1647
  104. package/src/internal/cloud-merge.test.ts +0 -74
  105. package/src/internal/cron.test.ts +0 -22
  106. package/src/internal/cursor/account-mcp.test.ts +0 -807
  107. package/src/internal/cursor/backend-client.test.ts +0 -591
  108. package/src/internal/cursor/credentials.test.ts +0 -351
  109. package/src/internal/cursor/github-credentials.test.ts +0 -136
  110. package/src/internal/cursor-account-mcp-auth.test.ts +0 -310
  111. package/src/internal/cursor-account.integration.test.ts +0 -441
  112. package/src/internal/cursor-event-relay.test.ts +0 -746
  113. package/src/internal/cursor-github-credentials.integration.test.ts +0 -271
  114. package/src/internal/cursor-slack-relay.test.ts +0 -525
  115. package/src/internal/deploy-source.test.ts +0 -111
  116. package/src/internal/discovery.builtin-tools.test.ts +0 -94
  117. package/src/internal/discovery.concurrency.test.ts +0 -60
  118. package/src/internal/discovery.cursor-account.test.ts +0 -133
  119. package/src/internal/discovery.cwd.test.ts +0 -83
  120. package/src/internal/discovery.hosting.test.ts +0 -80
  121. package/src/internal/discovery.identity.test.ts +0 -44
  122. package/src/internal/docs-site.test.ts +0 -66
  123. package/src/internal/duration.test.ts +0 -29
  124. package/src/internal/eval-judge-model.test.ts +0 -187
  125. package/src/internal/eval-run-store.cancel.test.ts +0 -142
  126. package/src/internal/eval-run-store.storage.test.ts +0 -211
  127. package/src/internal/eval-runner.http.test.ts +0 -403
  128. package/src/internal/eval-runner.run.test.ts +0 -928
  129. package/src/internal/evals-client.test.ts +0 -307
  130. package/src/internal/event-mapper.test.ts +0 -243
  131. package/src/internal/github-fanout.test.ts +0 -213
  132. package/src/internal/handleAgentServeTrigger.test.ts +0 -179
  133. package/src/internal/host-kv.test.ts +0 -82
  134. package/src/internal/host-platforms.test.ts +0 -126
  135. package/src/internal/http-channel.test.ts +0 -402
  136. package/src/internal/init-project.test.ts +0 -270
  137. package/src/internal/install-cursor-skills.test.ts +0 -262
  138. package/src/internal/local-env.test.ts +0 -120
  139. package/src/internal/log-ring.test.ts +0 -31
  140. package/src/internal/logs-client.test.ts +0 -350
  141. package/src/internal/mcp-endpoint.test.ts +0 -436
  142. package/src/internal/mcp-host.test.ts +0 -298
  143. package/src/internal/mcp-oauth.test.ts +0 -148
  144. package/src/internal/net.test.ts +0 -17
  145. package/src/internal/peer-connections.test.ts +0 -128
  146. package/src/internal/peer-mcp.integration.test.ts +0 -289
  147. package/src/internal/playground/toolchain.test.ts +0 -53
  148. package/src/internal/playground-cli.test.ts +0 -187
  149. package/src/internal/playground-proxy.test.ts +0 -376
  150. package/src/internal/prompt-context.integration.test.ts +0 -232
  151. package/src/internal/prompt-context.test.ts +0 -127
  152. package/src/internal/reminder-runner.test.ts +0 -390
  153. package/src/internal/reminder-store.test.ts +0 -53
  154. package/src/internal/request-headers.test.ts +0 -27
  155. package/src/internal/resolve-prod-target.test.ts +0 -787
  156. package/src/internal/resolved-connections.test.ts +0 -295
  157. package/src/internal/router.test.ts +0 -57
  158. package/src/internal/sdk-runner.test.ts +0 -290
  159. package/src/internal/session-engine.coalesce.test.ts +0 -169
  160. package/src/internal/session-engine.concurrency.test.ts +0 -250
  161. package/src/internal/session-engine.host-oauth-mcp.test.ts +0 -110
  162. package/src/internal/session-engine.interrupt.test.ts +0 -577
  163. package/src/internal/session-engine.storage.test.ts +0 -547
  164. package/src/internal/session-urls.test.ts +0 -28
  165. package/src/internal/sessions-client.test.ts +0 -518
  166. package/src/internal/storage-coordinator.test.ts +0 -517
  167. package/src/internal/tool-call.test.ts +0 -458
  168. package/src/internal/tool-result.test.ts +0 -52
  169. package/src/internal/trajectory.approvals.test.ts +0 -83
  170. package/src/internal/trajectory.subagents.test.ts +0 -198
  171. package/src/internal/turn-governor.test.ts +0 -137
  172. package/src/internal/update-check.test.ts +0 -485
  173. package/src/internal/workspace.test.ts +0 -207
  174. package/src/storage-backends/cursor-hosted.test.ts +0 -121
@@ -1,298 +0,0 @@
1
- import { createServer, type IncomingMessage, type Server } from "node:http";
2
- import { fileURLToPath } from "node:url";
3
- import { afterEach, describe, expect, it } from "vitest";
4
- import { isStaleMcpSessionError, McpHost } from "./mcp-host.js";
5
-
6
- const unitsServer = fileURLToPath(
7
- new URL("../../examples/weather-agent/mcp/units-server.ts", import.meta.url)
8
- );
9
-
10
- describe("isStaleMcpSessionError", () => {
11
- it("detects anytool session_terminated payloads and HTTP 404 codes", () => {
12
- expect(
13
- isStaleMcpSessionError(
14
- new Error(
15
- 'Streamable HTTP error: Error POSTing to endpoint: {"error":"session_terminated"}'
16
- )
17
- )
18
- ).toBe(true);
19
-
20
- const http404 = new Error(
21
- "Streamable HTTP error: Error POSTing to endpoint: gone"
22
- );
23
- (http404 as { code?: number }).code = 404;
24
- expect(isStaleMcpSessionError(http404)).toBe(true);
25
-
26
- expect(isStaleMcpSessionError(new Error("unauthorized"))).toBe(false);
27
- expect(isStaleMcpSessionError("session_terminated")).toBe(false);
28
- });
29
- });
30
-
31
- describe("McpHost", () => {
32
- const servers: Server[] = [];
33
- afterEach(async () => {
34
- await Promise.all(
35
- servers.splice(0).map(
36
- (server) =>
37
- new Promise<void>((resolve, reject) => {
38
- server.close((error) => (error ? reject(error) : resolve()));
39
- })
40
- )
41
- );
42
- });
43
-
44
- it("lists and calls tools on a stdio connection", async () => {
45
- const host = new McpHost([
46
- {
47
- name: "units",
48
- transport: {
49
- type: "stdio",
50
- command: "npx",
51
- args: ["-y", "tsx", unitsServer],
52
- },
53
- },
54
- ]);
55
- try {
56
- expect(host.names()).toEqual(["units"]);
57
- const tools = await host.listTools("units");
58
- expect(tools.map((tool) => tool.name).sort()).toEqual([
59
- "celsius_to_fahrenheit",
60
- "fahrenheit_to_celsius",
61
- ]);
62
-
63
- const result = await host.callTool("units", "celsius_to_fahrenheit", {
64
- value: 0,
65
- });
66
- expect(result.isError).toBeFalsy();
67
- const text = result.content.find(
68
- (part): part is { type: "text"; text: string } => part.type === "text"
69
- )?.text;
70
- expect(text).toBeDefined();
71
- expect(JSON.parse(text as string)).toEqual({
72
- celsius: 0,
73
- fahrenheit: 32,
74
- });
75
- } finally {
76
- await host.close();
77
- }
78
- }, 30_000);
79
-
80
- it("rejects unknown MCP connection names", async () => {
81
- const host = new McpHost([]);
82
- await expect(host.callTool("missing", "noop", {})).rejects.toThrow(
83
- /Unknown MCP connection "missing"/
84
- );
85
- await host.close();
86
- });
87
-
88
- it("reconnects after the remote Streamable HTTP session is terminated", async () => {
89
- const mock = await startTerminatingMcpMock();
90
- servers.push(mock.server);
91
-
92
- const host = new McpHost([
93
- {
94
- name: "anytool",
95
- transport: {
96
- type: "http",
97
- url: mock.url,
98
- },
99
- },
100
- ]);
101
- try {
102
- const first = await host.callTool("anytool", "ping", {});
103
- expect(first.isError).toBeFalsy();
104
- expect(mock.initializeCount).toBe(1);
105
-
106
- mock.terminateAllSessions();
107
-
108
- const second = await host.callTool("anytool", "ping", {});
109
- expect(second.isError).toBeFalsy();
110
- expect(mock.initializeCount).toBe(2);
111
- } finally {
112
- await host.close();
113
- }
114
- }, 30_000);
115
- });
116
-
117
- async function startTerminatingMcpMock(): Promise<{
118
- server: Server;
119
- url: string;
120
- initializeCount: number;
121
- terminateAllSessions: () => void;
122
- }> {
123
- const sessions = new Set<string>();
124
- const state = { initializeCount: 0 };
125
-
126
- const server = createServer((req, res) => {
127
- void handleMcpRequest(req, res, sessions, state);
128
- });
129
-
130
- await new Promise<void>((resolve, reject) => {
131
- server.once("error", reject);
132
- server.listen(0, "127.0.0.1", () => resolve());
133
- });
134
- const address = server.address();
135
- if (address === null || typeof address === "string") {
136
- throw new Error("expected TCP listen address");
137
- }
138
-
139
- return {
140
- server,
141
- url: `http://127.0.0.1:${address.port}/mcp`,
142
- get initializeCount() {
143
- return state.initializeCount;
144
- },
145
- terminateAllSessions() {
146
- sessions.clear();
147
- },
148
- };
149
- }
150
-
151
- async function handleMcpRequest(
152
- req: IncomingMessage,
153
- res: import("node:http").ServerResponse,
154
- sessions: Set<string>,
155
- state: { initializeCount: number }
156
- ): Promise<void> {
157
- if (req.method === "GET") {
158
- // Optional SSE stream; idle keep-alive is enough for the SDK.
159
- const sessionId = headerValue(req.headers["mcp-session-id"]);
160
- if (sessionId === undefined || !sessions.has(sessionId)) {
161
- res.writeHead(404, { "Content-Type": "application/json" });
162
- res.end(JSON.stringify({ error: "session_terminated" }));
163
- return;
164
- }
165
- res.writeHead(200, {
166
- "Content-Type": "text/event-stream",
167
- "Cache-Control": "no-cache",
168
- Connection: "keep-alive",
169
- "mcp-session-id": sessionId,
170
- });
171
- res.write(": connected\n\n");
172
- return;
173
- }
174
-
175
- if (req.method === "DELETE") {
176
- const sessionId = headerValue(req.headers["mcp-session-id"]);
177
- if (sessionId !== undefined) {
178
- sessions.delete(sessionId);
179
- }
180
- res.writeHead(200).end();
181
- return;
182
- }
183
-
184
- if (req.method !== "POST") {
185
- res.writeHead(405).end();
186
- return;
187
- }
188
-
189
- const body = await readJsonBody(req);
190
- const sessionId = headerValue(req.headers["mcp-session-id"]);
191
- const method =
192
- typeof body === "object" &&
193
- body !== null &&
194
- "method" in body &&
195
- typeof body.method === "string"
196
- ? body.method
197
- : undefined;
198
- const id =
199
- typeof body === "object" && body !== null && "id" in body
200
- ? body.id
201
- : undefined;
202
-
203
- if (method === "initialize") {
204
- state.initializeCount += 1;
205
- const nextSessionId = `session-${state.initializeCount}`;
206
- sessions.add(nextSessionId);
207
- writeSseJson(res, nextSessionId, {
208
- jsonrpc: "2.0",
209
- id,
210
- result: {
211
- protocolVersion: "2025-03-26",
212
- capabilities: { tools: {} },
213
- serverInfo: { name: "mock-anytool", version: "0.0.0" },
214
- },
215
- });
216
- return;
217
- }
218
-
219
- if (sessionId === undefined || !sessions.has(sessionId)) {
220
- res.writeHead(404, { "Content-Type": "application/json" });
221
- res.end(JSON.stringify({ error: "session_terminated" }));
222
- return;
223
- }
224
-
225
- if (method === undefined || id === undefined) {
226
- // notifications/initialized and similar
227
- res.writeHead(202).end();
228
- return;
229
- }
230
-
231
- if (method === "tools/list") {
232
- writeSseJson(res, sessionId, {
233
- jsonrpc: "2.0",
234
- id,
235
- result: {
236
- tools: [
237
- {
238
- name: "ping",
239
- description: "ping",
240
- inputSchema: { type: "object", properties: {} },
241
- },
242
- ],
243
- },
244
- });
245
- return;
246
- }
247
-
248
- if (method === "tools/call") {
249
- writeSseJson(res, sessionId, {
250
- jsonrpc: "2.0",
251
- id,
252
- result: {
253
- content: [{ type: "text", text: "pong" }],
254
- },
255
- });
256
- return;
257
- }
258
-
259
- writeSseJson(res, sessionId, {
260
- jsonrpc: "2.0",
261
- id,
262
- error: { code: -32601, message: `Method not found: ${method}` },
263
- });
264
- }
265
-
266
- function writeSseJson(
267
- res: import("node:http").ServerResponse,
268
- sessionId: string,
269
- payload: unknown
270
- ): void {
271
- res.writeHead(200, {
272
- "Content-Type": "text/event-stream",
273
- "Cache-Control": "no-cache",
274
- "mcp-session-id": sessionId,
275
- });
276
- res.end(`event: message\ndata: ${JSON.stringify(payload)}\n\n`);
277
- }
278
-
279
- function headerValue(value: string | string[] | undefined): string | undefined {
280
- if (typeof value === "string") {
281
- return value;
282
- }
283
- if (Array.isArray(value) && value.length > 0) {
284
- return value[0];
285
- }
286
- return undefined;
287
- }
288
-
289
- async function readJsonBody(req: IncomingMessage): Promise<unknown> {
290
- const chunks: Buffer[] = [];
291
- for await (const chunk of req) {
292
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
293
- }
294
- if (chunks.length === 0) {
295
- return undefined;
296
- }
297
- return JSON.parse(Buffer.concat(chunks).toString("utf8")) as unknown;
298
- }
@@ -1,148 +0,0 @@
1
- import { mkdtemp, readFile, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { describe, expect, it } from "vitest";
5
- import {
6
- createHostMcpOAuthProvider,
7
- FileMcpOAuthStore,
8
- HostMcpOAuthProvider,
9
- mcpOauthSecretNames,
10
- normalizeMcpResourceUrl,
11
- openBrowser,
12
- tokensFromEnv,
13
- } from "./mcp-oauth.js";
14
-
15
- const ANYTOOL_URL = "https://anytool.example/mcp";
16
-
17
- describe("FileMcpOAuthStore", () => {
18
- it("persists tokens and client info bound to resource URL", async () => {
19
- const dir = await mkdtemp(join(tmpdir(), "agentkit-mcp-oauth-"));
20
- const path = join(dir, "mcp-auth.json");
21
- const store = new FileMcpOAuthStore(path);
22
-
23
- await store.saveTokens("anytool", ANYTOOL_URL, {
24
- access_token: "access-1",
25
- refresh_token: "refresh-1",
26
- token_type: "Bearer",
27
- });
28
- await store.saveClientInformation("anytool", ANYTOOL_URL, {
29
- client_id: "client-1",
30
- redirect_uris: ["http://localhost:8787/callback"],
31
- });
32
-
33
- const reloaded = new FileMcpOAuthStore(path);
34
- expect(await reloaded.loadTokens("anytool", ANYTOOL_URL)).toMatchObject({
35
- access_token: "access-1",
36
- refresh_token: "refresh-1",
37
- });
38
- expect(
39
- await reloaded.loadClientInformation("anytool", ANYTOOL_URL)
40
- ).toMatchObject({
41
- client_id: "client-1",
42
- });
43
-
44
- const raw = JSON.parse(await readFile(path, "utf8")) as {
45
- anytool: { resourceUrl: string; tokens: { refresh_token: string } };
46
- };
47
- expect(raw.anytool.resourceUrl).toBe(normalizeMcpResourceUrl(ANYTOOL_URL));
48
- expect(raw.anytool.tokens.refresh_token).toBe("refresh-1");
49
- });
50
-
51
- it("ignores tokens when connection URL changes", async () => {
52
- const dir = await mkdtemp(join(tmpdir(), "agentkit-mcp-oauth-"));
53
- const store = new FileMcpOAuthStore(join(dir, "mcp-auth.json"));
54
- await store.saveTokens("anytool", ANYTOOL_URL, {
55
- access_token: "access-1",
56
- token_type: "Bearer",
57
- });
58
- expect(
59
- await store.loadTokens("anytool", "https://evil.example/mcp")
60
- ).toBeUndefined();
61
- expect(await store.loadTokens("anytool", ANYTOOL_URL)).toMatchObject({
62
- access_token: "access-1",
63
- });
64
- });
65
-
66
- it("ignores legacy entries without resourceUrl", async () => {
67
- const dir = await mkdtemp(join(tmpdir(), "agentkit-mcp-oauth-"));
68
- const path = join(dir, "mcp-auth.json");
69
- await writeFile(
70
- path,
71
- JSON.stringify({
72
- anytool: {
73
- tokens: { access_token: "legacy", token_type: "Bearer" },
74
- },
75
- })
76
- );
77
- const store = new FileMcpOAuthStore(path);
78
- expect(await store.loadTokens("anytool", ANYTOOL_URL)).toBeUndefined();
79
- });
80
- });
81
-
82
- describe("HostMcpOAuthProvider", () => {
83
- it("writes token updates back through the store", async () => {
84
- const dir = await mkdtemp(join(tmpdir(), "agentkit-mcp-oauth-"));
85
- const store = new FileMcpOAuthStore(join(dir, "mcp-auth.json"));
86
- const provider = await createHostMcpOAuthProvider({
87
- identifier: "anytool",
88
- resourceUrl: ANYTOOL_URL,
89
- store,
90
- });
91
-
92
- await provider.saveTokens({
93
- access_token: "access-2",
94
- refresh_token: "refresh-2",
95
- token_type: "Bearer",
96
- });
97
- expect(provider.tokens()?.refresh_token).toBe("refresh-2");
98
- expect(
99
- (await store.loadTokens("anytool", ANYTOOL_URL))?.refresh_token
100
- ).toBe("refresh-2");
101
- });
102
-
103
- it("records redirect URLs for the login CLI", () => {
104
- const redirects: string[] = [];
105
- const provider = new HostMcpOAuthProvider({
106
- identifier: "anytool",
107
- resourceUrl: ANYTOOL_URL,
108
- store: new FileMcpOAuthStore(join(tmpdir(), "unused.json")),
109
- onRedirect: (url) => redirects.push(url.toString()),
110
- });
111
- provider.redirectToAuthorization(
112
- new URL("https://anytool.example/oauth/authorize?x=1")
113
- );
114
- expect(provider.lastRedirectUrl?.toString()).toContain(
115
- "anytool.example/oauth/authorize"
116
- );
117
- expect(redirects).toHaveLength(1);
118
- });
119
- });
120
-
121
- describe("openBrowser", () => {
122
- it("refuses non-http(s) URLs", () => {
123
- expect(() => openBrowser("file:///etc/passwd")).toThrow(/non-http/);
124
- });
125
- });
126
-
127
- describe("mcp oauth deployment secrets", () => {
128
- it("names secrets from the connection id", () => {
129
- expect(mcpOauthSecretNames("anytool")).toEqual({
130
- accessToken: "MCP_OAUTH_ANYTOOL_ACCESS_TOKEN",
131
- refreshToken: "MCP_OAUTH_ANYTOOL_REFRESH_TOKEN",
132
- clientId: "MCP_OAUTH_ANYTOOL_CLIENT_ID",
133
- });
134
- });
135
-
136
- it("reads bearer tokens from env when local file is empty", () => {
137
- const names = mcpOauthSecretNames("anytool");
138
- expect(
139
- tokensFromEnv("anytool", {
140
- [names.accessToken]: "access-env",
141
- [names.refreshToken]: "refresh-env",
142
- })
143
- ).toMatchObject({
144
- access_token: "access-env",
145
- refresh_token: "refresh-env",
146
- });
147
- });
148
- });
@@ -1,17 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { isLoopbackHostname } from "./net.js";
3
-
4
- describe("isLoopbackHostname", () => {
5
- it("accepts localhost and loopback literals", () => {
6
- expect(isLoopbackHostname("localhost")).toBe(true);
7
- expect(isLoopbackHostname("127.0.0.1")).toBe(true);
8
- expect(isLoopbackHostname("::1")).toBe(true);
9
- });
10
-
11
- it("rejects remote hostnames used in DNS rebinding", () => {
12
- expect(isLoopbackHostname("attacker.example")).toBe(false);
13
- expect(isLoopbackHostname("localhost.evil.example")).toBe(false);
14
- expect(isLoopbackHostname("127.0.0.1.nip.io")).toBe(false);
15
- expect(isLoopbackHostname("127.0.0.1.attacker.example")).toBe(false);
16
- });
17
- });
@@ -1,128 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { httpChannel } from "../channels.js";
3
- import type { AgentProject, ConnectionTransport } from "../types.js";
4
- import {
5
- collectPeerConnections,
6
- resolvePeerConnections,
7
- } from "./peer-connections.js";
8
-
9
- function makeProject(
10
- name: string,
11
- connections: Array<{ name: string; transport: ConnectionTransport }> = []
12
- ): AgentProject {
13
- return {
14
- rootDir: `/tmp/${name}`,
15
- agentDir: `/tmp/${name}/agent`,
16
- name,
17
- agent: {
18
- name,
19
- runtime: "local",
20
- tools: [],
21
- skills: [],
22
- connections,
23
- subagents: [],
24
- seedFiles: [],
25
- },
26
- httpChannel: httpChannel(),
27
- channels: [],
28
- schedules: [],
29
- hooks: [],
30
- abs: [],
31
- diagnostics: [],
32
- };
33
- }
34
-
35
- describe("collectPeerConnections", () => {
36
- it("groups validated refs by the referencing mount slug", () => {
37
- const mounts = [
38
- {
39
- slug: "concierge",
40
- project: makeProject("concierge", [
41
- { name: "weather", transport: { agent: "weather-agent" } },
42
- { name: "linear", transport: { url: "https://mcp.linear.app/mcp" } },
43
- ]),
44
- },
45
- { slug: "weather-agent", project: makeProject("weather-agent") },
46
- ];
47
- const refs = collectPeerConnections(mounts);
48
- expect([...refs.keys()]).toEqual(["concierge"]);
49
- expect(refs.get("concierge")).toEqual([
50
- { name: "weather", targetSlug: "weather-agent" },
51
- ]);
52
- });
53
-
54
- it("throws on unknown peer slugs, listing the mounted agents", () => {
55
- const mounts = [
56
- {
57
- slug: "concierge",
58
- project: makeProject("concierge", [
59
- { name: "weather", transport: { agent: "nope" } },
60
- ]),
61
- },
62
- { slug: "weather-agent", project: makeProject("weather-agent") },
63
- ];
64
- expect(() => collectPeerConnections(mounts)).toThrow(
65
- /unknown peer agent "nope".*weather-agent/
66
- );
67
- });
68
-
69
- it("throws on self-references", () => {
70
- const mounts = [
71
- {
72
- slug: "concierge",
73
- project: makeProject("concierge", [
74
- { name: "me", transport: { agent: "concierge" } },
75
- ]),
76
- },
77
- ];
78
- expect(() => collectPeerConnections(mounts)).toThrow(/references itself/);
79
- });
80
-
81
- it("rejects peers in single-agent mode (empty slug set)", () => {
82
- const mounts = [
83
- {
84
- slug: "",
85
- project: makeProject("solo", [
86
- { name: "weather", transport: { agent: "weather-agent" } },
87
- ]),
88
- },
89
- ];
90
- expect(() => collectPeerConnections(mounts)).toThrow(
91
- /Mounted agents: \(none\)/
92
- );
93
- });
94
- });
95
-
96
- describe("resolvePeerConnections", () => {
97
- const refs = [{ name: "weather", targetSlug: "weather-agent" }];
98
-
99
- it("resolves loopback URLs and omits cloudUrl without a publicUrl", () => {
100
- const peers = resolvePeerConnections({
101
- refs,
102
- serverUrl: "http://127.0.0.1:4123",
103
- });
104
- expect(peers).toEqual([
105
- {
106
- name: "weather",
107
- localUrl: "http://127.0.0.1:4123/weather-agent/v1/mcp",
108
- },
109
- ]);
110
- });
111
-
112
- it("resolves cloudUrl from publicUrl and attaches the bearer token", () => {
113
- const peers = resolvePeerConnections({
114
- refs,
115
- serverUrl: "http://127.0.0.1:4123/",
116
- publicUrl: "https://agents.example.com/",
117
- authToken: "sekret",
118
- });
119
- expect(peers).toEqual([
120
- {
121
- name: "weather",
122
- localUrl: "http://127.0.0.1:4123/weather-agent/v1/mcp",
123
- cloudUrl: "https://agents.example.com/weather-agent/v1/mcp",
124
- headers: { authorization: "Bearer sekret" },
125
- },
126
- ]);
127
- });
128
- });