@cursor/july 0.1.9 → 0.1.11

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 (200) hide show
  1. package/AGENTS.md +1 -0
  2. package/dist/bin/agent-serve.js +26 -3
  3. package/dist/channels/github/github-channel.d.ts.map +1 -1
  4. package/dist/channels/github/github-channel.js +2 -1
  5. package/dist/channels/github/types.d.ts +4 -0
  6. package/dist/channels/github/types.d.ts.map +1 -1
  7. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  8. package/dist/channels/slack/cursor-account.js +1 -1
  9. package/dist/channels/slack/dispatch.d.ts +13 -0
  10. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  11. package/dist/channels/slack/dispatch.js +36 -11
  12. package/dist/channels/slack/eval-directive.d.ts +63 -0
  13. package/dist/channels/slack/eval-directive.d.ts.map +1 -0
  14. package/dist/channels/slack/eval-directive.js +283 -0
  15. package/dist/channels/slack/index.d.ts +1 -0
  16. package/dist/channels/slack/index.d.ts.map +1 -1
  17. package/dist/channels/slack/index.js +1 -0
  18. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  19. package/dist/channels/slack/slack-channel.js +4 -2
  20. package/dist/channels/slack/types.d.ts +7 -0
  21. package/dist/channels/slack/types.d.ts.map +1 -1
  22. package/dist/connections.d.ts +10 -5
  23. package/dist/connections.d.ts.map +1 -1
  24. package/dist/connections.js +10 -5
  25. package/dist/docs/404.html +2 -2
  26. package/dist/docs/ab.html +4 -4
  27. package/dist/docs/assets/{app.Ciosf24M.js → app.BcXUGca7.js} +1 -1
  28. package/dist/docs/assets/chunks/@localSearchIndexroot.BIbvK8M1.js +1 -0
  29. package/dist/docs/assets/chunks/{VPLocalSearchBox.BcRDly7n.js → VPLocalSearchBox.CWur6XGZ.js} +1 -1
  30. package/dist/docs/assets/chunks/{theme.CMpbjdfx.js → theme.BnvulOX2.js} +2 -2
  31. package/dist/docs/assets/{deployment.md.DC5-ImcM.js → deployment.md.TecHo0_2.js} +3 -3
  32. package/dist/docs/assets/{deployment.md.DC5-ImcM.lean.js → deployment.md.TecHo0_2.lean.js} +1 -1
  33. package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.js +29 -0
  34. package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.lean.js +1 -0
  35. package/dist/docs/assets/{index.md.COiu-1jL.js → index.md.t0TM2Qzz.js} +3 -3
  36. package/dist/docs/assets/{index.md.COiu-1jL.lean.js → index.md.t0TM2Qzz.lean.js} +1 -1
  37. package/dist/docs/assets/{reference_cli.md.D189RBCH.js → reference_cli.md.B74NrVzb.js} +5 -4
  38. package/dist/docs/assets/{reference_cli.md.D189RBCH.lean.js → reference_cli.md.B74NrVzb.lean.js} +1 -1
  39. package/dist/docs/assets/{reference_connections.md.zaEYCLHT.js → reference_connections.md.CsSr3rDz.js} +9 -4
  40. package/dist/docs/assets/{reference_connections.md.zaEYCLHT.lean.js → reference_connections.md.CsSr3rDz.lean.js} +1 -1
  41. package/dist/docs/assets/reference_prompt.md.BaiweQxE.js +1 -0
  42. package/dist/docs/assets/reference_prompt.md.BaiweQxE.lean.js +1 -0
  43. package/dist/docs/assets/{reference_tools.md.CqgJroI0.js → reference_tools.md.BRSDnTbN.js} +16 -3
  44. package/dist/docs/assets/{reference_tools.md.CqgJroI0.lean.js → reference_tools.md.BRSDnTbN.lean.js} +1 -1
  45. package/dist/docs/assets/{troubleshooting.md.DkovjS7h.js → troubleshooting.md.CmQkmnzC.js} +1 -1
  46. package/dist/docs/assets/{troubleshooting.md.DkovjS7h.lean.js → troubleshooting.md.CmQkmnzC.lean.js} +1 -1
  47. package/dist/docs/building-with-agents.html +4 -4
  48. package/dist/docs/concepts.html +4 -4
  49. package/dist/docs/deployment.html +7 -7
  50. package/dist/docs/evals.html +4 -4
  51. package/dist/docs/example-agents/approval-buddy.html +4 -4
  52. package/dist/docs/example-agents/benny.html +4 -4
  53. package/dist/docs/example-agents/bugbot.html +4 -4
  54. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  55. package/dist/docs/example-agents/codeowners-review.html +4 -4
  56. package/dist/docs/example-agents/concierge.html +4 -4
  57. package/dist/docs/example-agents/fsd.html +4 -4
  58. package/dist/docs/example-agents/index.html +4 -4
  59. package/dist/docs/example-agents/knowledge-base.html +4 -4
  60. package/dist/docs/example-agents/oncall.html +4 -4
  61. package/dist/docs/example-agents/security-reviewer.html +4 -4
  62. package/dist/docs/example-agents/slack-agent.html +4 -4
  63. package/dist/docs/example-agents/weather-agent.html +4 -4
  64. package/dist/docs/guides/agent-to-agent.html +4 -4
  65. package/dist/docs/guides/cloud-runtime.html +4 -4
  66. package/dist/docs/guides/github.html +4 -4
  67. package/dist/docs/guides/human-in-the-loop.html +4 -4
  68. package/dist/docs/guides/mcp-oauth.html +53 -0
  69. package/dist/docs/guides/slack.html +4 -4
  70. package/dist/docs/guides/webhooks.html +4 -4
  71. package/dist/docs/hashmap.json +1 -1
  72. package/dist/docs/hillclimbing.html +4 -4
  73. package/dist/docs/index.html +6 -6
  74. package/dist/docs/quickstart.html +4 -4
  75. package/dist/docs/reference/agent-config.html +4 -4
  76. package/dist/docs/reference/channels.html +4 -4
  77. package/dist/docs/reference/cli.html +8 -7
  78. package/dist/docs/reference/connections.html +13 -8
  79. package/dist/docs/reference/hooks.html +4 -4
  80. package/dist/docs/reference/http-api.html +4 -4
  81. package/dist/docs/reference/instructions.html +4 -4
  82. package/dist/docs/reference/playground.html +4 -4
  83. package/dist/docs/reference/project-layout.html +4 -4
  84. package/dist/docs/reference/prompt.html +33 -0
  85. package/dist/docs/reference/schedules.html +4 -4
  86. package/dist/docs/reference/sessions.html +4 -4
  87. package/dist/docs/reference/skills.html +5 -5
  88. package/dist/docs/reference/subagents.html +4 -4
  89. package/dist/docs/reference/tools.html +20 -7
  90. package/dist/docs/scaffolding-agents.html +4 -4
  91. package/dist/docs/storage.html +4 -4
  92. package/dist/docs/troubleshooting.html +5 -5
  93. package/dist/evals.d.ts +39 -0
  94. package/dist/evals.d.ts.map +1 -1
  95. package/dist/index.d.ts +3 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +2 -1
  98. package/dist/internal/cli-ax.d.ts.map +1 -1
  99. package/dist/internal/cli-ax.js +7 -4
  100. package/dist/internal/cli-mcp-oauth.d.ts +16 -0
  101. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -0
  102. package/dist/internal/cli-mcp-oauth.js +194 -0
  103. package/dist/internal/cli-mcp.d.ts +4 -1
  104. package/dist/internal/cli-mcp.d.ts.map +1 -1
  105. package/dist/internal/cli-mcp.js +19 -1
  106. package/dist/internal/corp-ca.d.ts +9 -0
  107. package/dist/internal/corp-ca.d.ts.map +1 -0
  108. package/dist/internal/corp-ca.js +44 -0
  109. package/dist/internal/cursor/account-mcp.d.ts +17 -4
  110. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  111. package/dist/internal/cursor/account-mcp.js +105 -17
  112. package/dist/internal/discovery.d.ts.map +1 -1
  113. package/dist/internal/discovery.js +64 -10
  114. package/dist/internal/distribution.d.ts.map +1 -1
  115. package/dist/internal/distribution.js +1 -0
  116. package/dist/internal/logs-client.js +1 -1
  117. package/dist/internal/mcp-host.d.ts.map +1 -1
  118. package/dist/internal/mcp-host.js +49 -26
  119. package/dist/internal/mcp-oauth.d.ts +88 -0
  120. package/dist/internal/mcp-oauth.d.ts.map +1 -0
  121. package/dist/internal/mcp-oauth.js +385 -0
  122. package/dist/internal/resolved-connections.d.ts +6 -0
  123. package/dist/internal/resolved-connections.d.ts.map +1 -1
  124. package/dist/internal/resolved-connections.js +10 -3
  125. package/dist/internal/sdk-runner.d.ts +9 -0
  126. package/dist/internal/sdk-runner.d.ts.map +1 -1
  127. package/dist/internal/sdk-runner.js +28 -2
  128. package/dist/internal/server.d.ts.map +1 -1
  129. package/dist/internal/server.js +24 -6
  130. package/dist/internal/session-engine.d.ts +30 -10
  131. package/dist/internal/session-engine.d.ts.map +1 -1
  132. package/dist/internal/session-engine.js +166 -74
  133. package/dist/internal/sessions-client.d.ts +16 -2
  134. package/dist/internal/sessions-client.d.ts.map +1 -1
  135. package/dist/internal/sessions-client.js +30 -13
  136. package/dist/internal/storage-coordinator.d.ts +11 -0
  137. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  138. package/dist/internal/storage-coordinator.js +30 -0
  139. package/dist/internal/stream-progress.d.ts.map +1 -1
  140. package/dist/internal/stream-progress.js +8 -4
  141. package/dist/internal/turn-governor.d.ts +54 -0
  142. package/dist/internal/turn-governor.d.ts.map +1 -0
  143. package/dist/internal/turn-governor.js +111 -0
  144. package/dist/playground/assets/{index-C0-QEG4O.js → index-BIGLubwK.js} +46 -46
  145. package/dist/playground/assets/{index-z3JY9O2x.css → index-C17ZQKDG.css} +1 -1
  146. package/dist/playground/index.html +2 -2
  147. package/dist/prompt.d.ts +39 -0
  148. package/dist/prompt.d.ts.map +1 -0
  149. package/dist/prompt.js +70 -0
  150. package/dist/storage-backends/cursor-hosted.d.ts +10 -0
  151. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  152. package/dist/storage-backends/cursor-hosted.js +14 -0
  153. package/dist/types.d.ts +93 -5
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/types.js +6 -0
  156. package/docs/README.md +4 -0
  157. package/docs/deployment.md +6 -1
  158. package/docs/guides/mcp-oauth.md +157 -0
  159. package/docs/reference/cli.md +30 -0
  160. package/docs/reference/connections.md +32 -2
  161. package/docs/reference/prompt.md +47 -0
  162. package/docs/reference/tools.md +21 -0
  163. package/docs/troubleshooting.md +11 -0
  164. package/package.json +9 -2
  165. package/skills/create-agent/SKILL.md +7 -3
  166. package/skills/framework-map/SKILL.md +1 -0
  167. package/skills/mcp-auth/SKILL.md +138 -0
  168. package/src/bin/agent-serve.ts +30 -3
  169. package/src/channels/github/github-channel.ts +2 -0
  170. package/src/channels/github/types.ts +4 -0
  171. package/src/channels/slack/cursor-account.ts +2 -0
  172. package/src/channels/slack/dispatch.ts +49 -0
  173. package/src/channels/slack/eval-directive.ts +348 -0
  174. package/src/channels/slack/index.ts +1 -0
  175. package/src/channels/slack/slack-channel.ts +3 -0
  176. package/src/channels/slack/types.ts +7 -0
  177. package/src/connections.ts +10 -5
  178. package/src/evals.ts +43 -0
  179. package/src/index.ts +3 -0
  180. package/src/internal/cli-ax.ts +10 -4
  181. package/src/internal/cli-mcp-oauth.ts +254 -0
  182. package/src/internal/cli-mcp.ts +25 -2
  183. package/src/internal/cursor/account-mcp.ts +153 -41
  184. package/src/internal/discovery.ts +70 -8
  185. package/src/internal/distribution.ts +1 -0
  186. package/src/internal/logs-client.ts +1 -1
  187. package/src/internal/mcp-host.ts +33 -9
  188. package/src/internal/mcp-oauth.ts +496 -0
  189. package/src/internal/resolved-connections.ts +20 -5
  190. package/src/internal/sdk-runner.ts +37 -1
  191. package/src/internal/server.ts +28 -6
  192. package/src/internal/session-engine.ts +192 -76
  193. package/src/internal/sessions-client.ts +51 -15
  194. package/src/internal/storage-coordinator.ts +36 -0
  195. package/src/internal/stream-progress.ts +8 -0
  196. package/src/internal/turn-governor.ts +145 -0
  197. package/src/prompt.ts +83 -0
  198. package/src/storage-backends/cursor-hosted.ts +21 -0
  199. package/src/types.ts +92 -6
  200. package/dist/docs/assets/chunks/@localSearchIndexroot.D154Detj.js +0 -1
package/src/evals.ts CHANGED
@@ -315,6 +315,49 @@ export interface EvalRunSnapshot {
315
315
  };
316
316
  }
317
317
 
318
+ /** One discovered eval datapoint as returned by {@link HostEvalsApi.list}. */
319
+ export interface HostEvalListItem {
320
+ id: string;
321
+ fileId: string;
322
+ description?: string;
323
+ tags?: string[];
324
+ }
325
+
326
+ /** Config slice returned with {@link HostEvalsApi.list}. */
327
+ export interface HostEvalListConfig {
328
+ maxConcurrency?: number;
329
+ timeoutMs?: number;
330
+ maxPlaygroundRuns: number;
331
+ /** True when `agent/storage.ts` declares an `evals` table. */
332
+ durableRuns: boolean;
333
+ }
334
+
335
+ /** Input for {@link HostEvalsApi.start} (same filters as `POST /v1/dev/evals/runs`). */
336
+ export interface HostStartEvalRunInput {
337
+ filterIds?: string[];
338
+ tags?: string[];
339
+ /** Per-case timeout override (same semantics as CLI `--timeout-ms`). */
340
+ timeoutMs?: number;
341
+ }
342
+
343
+ /**
344
+ * Host-facing playground eval batches — the same surface as
345
+ * `/v1/dev/evals*` and `agentkit eval --prod`. Attached to
346
+ * {@link import("./types.js").HostContext.evals} by `serve`.
347
+ */
348
+ export interface HostEvalsApi {
349
+ list(): Promise<{
350
+ evals: HostEvalListItem[];
351
+ config: HostEvalListConfig;
352
+ }>;
353
+ get(runId: string): Promise<EvalRunSnapshot | undefined>;
354
+ listRunsWithActive(): Promise<{
355
+ runs: EvalRunSnapshot[];
356
+ activeRunId?: string;
357
+ }>;
358
+ start(input?: HostStartEvalRunInput): Promise<EvalRunSnapshot>;
359
+ }
360
+
318
361
  export interface EvalConfigFile {
319
362
  timeoutMs?: number;
320
363
  /**
package/src/index.ts CHANGED
@@ -89,6 +89,8 @@ export {
89
89
  UnknownSessionError,
90
90
  UnknownToolError,
91
91
  } from "./internal/session-engine.js";
92
+ export type { PromptTag } from "./prompt.js";
93
+ export { prompt } from "./prompt.js";
92
94
  export type {
93
95
  ReminderBusyPolicy,
94
96
  ReminderCreateInput,
@@ -127,6 +129,7 @@ export {
127
129
  isCursorAccountTransport,
128
130
  isPeerConnectionTransport,
129
131
  isSymbolicConnectionTransport,
132
+ MAX_RUNNING_TURNS_LIMIT,
130
133
  } from "./types.js";
131
134
 
132
135
  /**
@@ -54,7 +54,7 @@ import { runSession } from "./run-client.js";
54
54
  import {
55
55
  fetchSessionEvents,
56
56
  formatSessionsTable,
57
- listSessions,
57
+ listSessionsWithSource,
58
58
  resolvePreviousSessionId,
59
59
  } from "./sessions-client.js";
60
60
  import { createStreamProgress } from "./stream-progress.js";
@@ -1383,7 +1383,7 @@ export async function cmdSessions(options: AxCliOptions): Promise<number> {
1383
1383
  return target;
1384
1384
  }
1385
1385
  try {
1386
- const sessions = await listSessions({
1386
+ const listed = await listSessionsWithSource({
1387
1387
  baseUrl: target.agentUrl,
1388
1388
  auth: {
1389
1389
  bearerToken: options.bearerToken,
@@ -1392,10 +1392,16 @@ export async function cmdSessions(options: AxCliOptions): Promise<number> {
1392
1392
  fetchImpl: options.fetchImpl,
1393
1393
  });
1394
1394
  if (options.json === true) {
1395
- process.stdout.write(`${JSON.stringify({ sessions }, null, 2)}\n`);
1395
+ const payload =
1396
+ listed.source === undefined
1397
+ ? { sessions: listed.sessions }
1398
+ : { sessions: listed.sessions, source: listed.source };
1399
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
1396
1400
  return 0;
1397
1401
  }
1398
- process.stdout.write(formatSessionsTable(sessions));
1402
+ process.stdout.write(
1403
+ formatSessionsTable(listed.sessions, { source: listed.source })
1404
+ );
1399
1405
  return 0;
1400
1406
  } catch (error) {
1401
1407
  process.stderr.write(
@@ -0,0 +1,254 @@
1
+ /**
2
+ * `agentkit mcp oauth <connection> [--store]` for `defineConnection({ url, oauth: true })`.
3
+ */
4
+
5
+ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
6
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
8
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
9
+ import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
10
+ import { resolveDeployAuth } from "./cli-deploy.js";
11
+ import { loadAgentProject } from "./discovery.js";
12
+ import { CLI_COMMAND_NAME as CLI, LOG_PREFIX } from "./distribution.js";
13
+ import {
14
+ createHostMcpOAuthProvider,
15
+ MCP_OAUTH_CALLBACK_URL,
16
+ mcpOauthSecretNames,
17
+ openBrowser,
18
+ waitForMcpOAuthCallback,
19
+ } from "./mcp-oauth.js";
20
+ import { resolveProdSlug } from "./resolve-prod-target.js";
21
+
22
+ export type McpOauthCliOptions = {
23
+ dir: string;
24
+ connection: string;
25
+ /** Upsert MCP_OAUTH_* on the hosted deployment. */
26
+ store?: boolean;
27
+ slug?: string;
28
+ team?: string;
29
+ apiKey?: string;
30
+ err?: (text: string) => void;
31
+ out?: (text: string) => void;
32
+ };
33
+
34
+ function describe(error: unknown): string {
35
+ if (!(error instanceof Error)) {
36
+ return String(error);
37
+ }
38
+ const cause = error.cause;
39
+ if (!(cause instanceof Error)) {
40
+ return error.message;
41
+ }
42
+ const code =
43
+ "code" in cause && typeof cause.code === "string" ? cause.code : undefined;
44
+ const detail =
45
+ code === undefined ? cause.message : `${cause.message} (${code})`;
46
+ return `${error.message}: ${detail}`;
47
+ }
48
+
49
+ export async function cmdMcpOauth(
50
+ options: McpOauthCliOptions
51
+ ): Promise<number> {
52
+ const err = options.err ?? ((text) => process.stderr.write(text));
53
+ const out = options.out ?? ((text) => process.stdout.write(text));
54
+ const project = await loadAgentProject(options.dir);
55
+ const connection = project.agent.connections.find(
56
+ (entry) => entry.name === options.connection
57
+ );
58
+ if (connection === undefined) {
59
+ const known =
60
+ project.agent.connections.map((entry) => entry.name).join(", ") ||
61
+ "(none)";
62
+ err(
63
+ `${LOG_PREFIX} Unknown MCP connection "${options.connection}". Authored: ${known}.\n`
64
+ );
65
+ return 1;
66
+ }
67
+
68
+ const transport = connection.transport;
69
+ if (!("url" in transport) || transport.oauth !== true) {
70
+ err(
71
+ `${LOG_PREFIX} "${options.connection}" must be defineConnection({ url, oauth: true }).\n`
72
+ );
73
+ return 1;
74
+ }
75
+
76
+ out(
77
+ `${LOG_PREFIX} Authorizing "${options.connection}" (${transport.url})…\n`
78
+ );
79
+
80
+ let authorizationUrl: URL | undefined;
81
+ const provider = await createHostMcpOAuthProvider({
82
+ identifier: options.connection,
83
+ resourceUrl: transport.url,
84
+ onRedirect: (url) => {
85
+ authorizationUrl = url;
86
+ },
87
+ });
88
+
89
+ const client = new Client({ name: "agentkit-mcp-oauth", version: "0.1.0" });
90
+ const url = new URL(transport.url);
91
+ const requestInit =
92
+ transport.headers === undefined
93
+ ? undefined
94
+ : { headers: transport.headers };
95
+
96
+ const makeTransport = ():
97
+ | StreamableHTTPClientTransport
98
+ | SSEClientTransport => {
99
+ if (transport.type === "sse") {
100
+ return new SSEClientTransport(url, {
101
+ authProvider: provider,
102
+ requestInit,
103
+ });
104
+ }
105
+ return new StreamableHTTPClientTransport(url, {
106
+ authProvider: provider,
107
+ requestInit,
108
+ });
109
+ };
110
+
111
+ let mcpTransport = makeTransport();
112
+ let authorized = false;
113
+ try {
114
+ await client.connect(mcpTransport);
115
+ out(`${LOG_PREFIX} "${options.connection}" already authorized.\n`);
116
+ await client.close().catch(() => undefined);
117
+ authorized = true;
118
+ } catch (error) {
119
+ if (!(error instanceof UnauthorizedError)) {
120
+ err(
121
+ `${LOG_PREFIX} Failed to connect to "${options.connection}": ${describe(error)}\n`
122
+ );
123
+ return 1;
124
+ }
125
+ }
126
+
127
+ if (!authorized) {
128
+ const redirect = authorizationUrl ?? provider.lastRedirectUrl;
129
+ if (redirect === undefined) {
130
+ err(
131
+ `${LOG_PREFIX} Auth required but no authorization URL was provided.\n`
132
+ );
133
+ return 1;
134
+ }
135
+
136
+ out(`${LOG_PREFIX} Open if needed:\n ${redirect.toString()}\n`);
137
+ out(`${LOG_PREFIX} Waiting on ${MCP_OAUTH_CALLBACK_URL}…\n`);
138
+ openBrowser(redirect.toString());
139
+
140
+ let callback: Awaited<ReturnType<typeof waitForMcpOAuthCallback>>;
141
+ try {
142
+ callback = await waitForMcpOAuthCallback();
143
+ } catch (error) {
144
+ err(`${LOG_PREFIX} OAuth callback failed: ${describe(error)}\n`);
145
+ return 1;
146
+ }
147
+
148
+ try {
149
+ await mcpTransport.finishAuth(callback.code);
150
+ } catch (error) {
151
+ err(`${LOG_PREFIX} Token exchange failed: ${describe(error)}\n`);
152
+ return 1;
153
+ }
154
+
155
+ mcpTransport = makeTransport();
156
+ try {
157
+ await client.connect(mcpTransport);
158
+ const tools = await client.listTools();
159
+ out(
160
+ `${LOG_PREFIX} Authorized "${options.connection}" (${tools.tools.length} tools). Local tokens in the agent-serve config dir.\n`
161
+ );
162
+ await client.close().catch(() => undefined);
163
+ } catch (error) {
164
+ err(
165
+ `${LOG_PREFIX} Reconnect failed: ${describe(error)}. Re-run \`${CLI} mcp oauth ${options.connection}\`.\n`
166
+ );
167
+ return 1;
168
+ }
169
+ }
170
+
171
+ if (options.store !== true) {
172
+ return 0;
173
+ }
174
+ return storeOauthSecrets({
175
+ connection: options.connection,
176
+ dir: options.dir,
177
+ slug: options.slug,
178
+ team: options.team,
179
+ apiKey: options.apiKey,
180
+ tokens: provider.tokens(),
181
+ clientId: provider.clientInformation()?.client_id,
182
+ err,
183
+ out,
184
+ });
185
+ }
186
+
187
+ async function storeOauthSecrets(args: {
188
+ connection: string;
189
+ dir: string;
190
+ slug?: string;
191
+ team?: string;
192
+ apiKey?: string;
193
+ tokens: OAuthTokens | undefined;
194
+ clientId: string | undefined;
195
+ err: (text: string) => void;
196
+ out: (text: string) => void;
197
+ }): Promise<number> {
198
+ const tokens = args.tokens;
199
+ if (tokens === undefined || tokens.access_token.trim() === "") {
200
+ args.err(
201
+ `${LOG_PREFIX} No access token to store for "${args.connection}".\n`
202
+ );
203
+ return 1;
204
+ }
205
+
206
+ let slug: string;
207
+ try {
208
+ slug = resolveProdSlug({ dir: args.dir, slug: args.slug });
209
+ } catch (error) {
210
+ args.err(`${LOG_PREFIX} ${describe(error)}\n`);
211
+ return 1;
212
+ }
213
+
214
+ const auth = await resolveDeployAuth({
215
+ team: args.team,
216
+ apiKey: args.apiKey,
217
+ err: args.err,
218
+ });
219
+ if (typeof auth === "number") {
220
+ return auth;
221
+ }
222
+
223
+ const names = mcpOauthSecretNames(args.connection);
224
+ const secrets: Array<{ name: string; value: string }> = [
225
+ { name: names.accessToken, value: tokens.access_token },
226
+ ];
227
+ if (
228
+ tokens.refresh_token !== undefined &&
229
+ tokens.refresh_token.trim() !== ""
230
+ ) {
231
+ secrets.push({ name: names.refreshToken, value: tokens.refresh_token });
232
+ }
233
+ if (args.clientId !== undefined && args.clientId.trim() !== "") {
234
+ secrets.push({ name: names.clientId, value: args.clientId });
235
+ }
236
+
237
+ try {
238
+ const secretNames = await auth.client.setSecrets({
239
+ teamId: auth.teamId,
240
+ slug,
241
+ secrets,
242
+ });
243
+ args.out(
244
+ `${LOG_PREFIX} Stored on ${slug}: ${secrets.map((s) => s.name).join(", ")}.\n` +
245
+ `${LOG_PREFIX} Secrets now: ${secretNames.join(", ")}. Next deploy picks them up.\n`
246
+ );
247
+ return 0;
248
+ } catch (error) {
249
+ args.err(
250
+ `${LOG_PREFIX} Failed to store secrets on ${slug}: ${describe(error)}\n`
251
+ );
252
+ return 1;
253
+ }
254
+ }
@@ -89,7 +89,10 @@ export interface McpCliOptions {
89
89
 
90
90
  export async function cmdMcp(
91
91
  subcommand: string | undefined,
92
- options: McpCliOptions
92
+ options: McpCliOptions & {
93
+ oauthConnection?: string;
94
+ store?: boolean;
95
+ }
93
96
  ): Promise<number> {
94
97
  const err = options.err ?? defaultErr;
95
98
  if (subcommand === undefined) {
@@ -98,9 +101,29 @@ export async function cmdMcp(
98
101
  if (subcommand === "install") {
99
102
  return cmdMcpInstall(options);
100
103
  }
104
+ if (subcommand === "oauth") {
105
+ const connection = options.oauthConnection;
106
+ if (connection === undefined || connection.trim() === "") {
107
+ err(
108
+ `Usage: ${CLI} mcp oauth <connection> [--dir .] [--store] [--slug <slug>] [--team <id>]\n` +
109
+ `Authorize defineConnection({ url, oauth: true }) (host PKCE).\n` +
110
+ `--store upserts MCP_OAUTH_<NAME>_* secrets on the hosted deployment.\n`
111
+ );
112
+ return 1;
113
+ }
114
+ const { cmdMcpOauth } = await import("./cli-mcp-oauth.js");
115
+ return cmdMcpOauth({
116
+ dir: options.dir,
117
+ connection,
118
+ store: options.store,
119
+ slug: options.slug,
120
+ team: options.team,
121
+ apiKey: options.apiKey,
122
+ });
123
+ }
101
124
  err(
102
125
  `Unknown mcp subcommand "${subcommand}".\n` +
103
- `Usage: ${CLI} mcp [install] --prod [--slug <slug>] | --url <baseUrl>\n`
126
+ `Usage: ${CLI} mcp [install|oauth <connection>] --prod [--slug <slug>] | --url <baseUrl>\n`
104
127
  );
105
128
  return 1;
106
129
  }
@@ -213,48 +213,89 @@ export class CursorAccountMcpBridge {
213
213
 
214
214
  /**
215
215
  * Startup DX: log the account's connector statuses for `refs` without
216
- * blocking serve startup. Surfaces "needsAuth" connectors (authorize in
217
- * Cursor) and filters that matched nothing (typo'd connector names).
216
+ * blocking serve startup. Surfaces "needsAuth" / "error" connectors with
217
+ * remediation hints (and a backend probe detail for `error`), and filters
218
+ * that matched nothing (typo'd connector names).
219
+ *
220
+ * Returns a promise that settles when every ref's log lines are written —
221
+ * callers may ignore it; tests can await it.
218
222
  */
219
- logPreflight(refs: CursorAccountConnectionRef[]): void {
220
- for (const ref of refs) {
221
- void this.getCatalog(ref)
222
- .then((catalog) => {
223
- const summary = catalog.servers
224
- .map(
225
- (server) =>
226
- `${server.serverIdentifier} (${
227
- server.status === "connected"
228
- ? `${server.tools.length} tools`
229
- : server.status
230
- })`
231
- )
232
- .join(", ");
233
- this.logger(
234
- `[agentkit] Cursor account MCP connection "${ref.name}": ${
235
- catalog.servers.length === 0
236
- ? "no matching connectors on this account — connect servers in Cursor (dashboard → MCP) or fix the `servers` filter"
237
- : summary
238
- }`
239
- );
240
- const needsAuth = catalog.servers.filter(
241
- (server) => server.status === "needsAuth"
242
- );
243
- if (needsAuth.length > 0) {
244
- this.logger(
245
- `[agentkit] Cursor account MCP connection "${ref.name}": ${needsAuth
246
- .map((server) => server.serverIdentifier)
247
- .join(
248
- ", "
249
- )} need re-authorization in Cursor before their tools appear`
223
+ logPreflight(refs: CursorAccountConnectionRef[]): Promise<void> {
224
+ return Promise.all(refs.map((ref) => this.runPreflight(ref))).then(
225
+ () => undefined
226
+ );
227
+ }
228
+
229
+ private async runPreflight(ref: CursorAccountConnectionRef): Promise<void> {
230
+ try {
231
+ const catalog = await this.getCatalog(ref);
232
+ if (catalog.servers.length === 0) {
233
+ this.logger(
234
+ `[agentkit] Cursor account MCP connection "${ref.name}": no matching connectors on this account — connect servers in Cursor (dashboard → MCP) or fix the \`servers\` filter`
235
+ );
236
+ return;
237
+ }
238
+
239
+ const errorDetails = new Map<string, string>();
240
+ await Promise.all(
241
+ catalog.servers
242
+ .filter((server) => server.status === "error")
243
+ .map(async (server) => {
244
+ const detail = await diagnoseConnectorError(
245
+ this.client,
246
+ server.serverIdentifier
250
247
  );
251
- }
252
- })
253
- .catch((error: unknown) => {
254
- this.logger(
255
- `[agentkit] Cursor account MCP connection "${ref.name}" preflight failed: ${describeError(error)}`
256
- );
257
- });
248
+ if (detail !== undefined) {
249
+ errorDetails.set(server.serverIdentifier, detail);
250
+ }
251
+ })
252
+ );
253
+
254
+ const summary = catalog.servers
255
+ .map((server) =>
256
+ formatServerStatusSummary(
257
+ server,
258
+ errorDetails.get(server.serverIdentifier)
259
+ )
260
+ )
261
+ .join(", ");
262
+ this.logger(
263
+ `[agentkit] Cursor account MCP connection "${ref.name}": ${summary}`
264
+ );
265
+
266
+ const needsAuth = catalog.servers.filter(
267
+ (server) => server.status === "needsAuth"
268
+ );
269
+ if (needsAuth.length > 0) {
270
+ this.logger(
271
+ `[agentkit] Cursor account MCP connection "${ref.name}": ${needsAuth
272
+ .map((server) => server.serverIdentifier)
273
+ .join(
274
+ ", "
275
+ )} need re-authorization in Cursor (dashboard → MCP / Settings → MCP) before their tools appear`
276
+ );
277
+ }
278
+
279
+ const errored = catalog.servers.filter(
280
+ (server) => server.status === "error"
281
+ );
282
+ if (errored.length > 0) {
283
+ const details = errored
284
+ .map((server) => {
285
+ const detail = errorDetails.get(server.serverIdentifier);
286
+ return detail === undefined
287
+ ? server.serverIdentifier
288
+ : `${server.serverIdentifier} (${detail})`;
289
+ })
290
+ .join("; ");
291
+ this.logger(
292
+ `[agentkit] Cursor account MCP connection "${ref.name}": ${details} — Cursor backend could not use this connector (not a missing agentkit login). Re-check the server in Cursor dashboard → MCP, confirm the URL is healthy, and retry after the backend can reach it`
293
+ );
294
+ }
295
+ } catch (error: unknown) {
296
+ this.logger(
297
+ `[agentkit] Cursor account MCP connection "${ref.name}" preflight failed: ${describeError(error)}`
298
+ );
258
299
  }
259
300
  }
260
301
 
@@ -283,13 +324,22 @@ export class CursorAccountMcpBridge {
283
324
  const tool = await this.findTool(ref, request.params.name);
284
325
  if (tool === undefined) {
285
326
  const catalog = await this.getCatalog(ref);
327
+ const unhealthy = catalog.servers.filter(
328
+ (server) => server.status !== "connected"
329
+ );
330
+ const statusHint =
331
+ unhealthy.length === 0
332
+ ? ""
333
+ : ` Unhealthy connectors: ${unhealthy
334
+ .map((server) => `${server.serverIdentifier}=${server.status}`)
335
+ .join(", ")}.`;
286
336
  return {
287
337
  content: [
288
338
  {
289
339
  type: "text",
290
340
  text: `Unknown tool "${request.params.name}". Available: ${
291
341
  catalog.tools.map((t) => t.exposedName).join(", ") || "(none)"
292
- }.`,
342
+ }.${statusHint}`,
293
343
  },
294
344
  ],
295
345
  isError: true,
@@ -470,3 +520,65 @@ function trimTrailingSlash(url: string): string {
470
520
  function describeError(error: unknown): string {
471
521
  return error instanceof Error ? error.message : String(error);
472
522
  }
523
+
524
+ /** One-line status for the preflight summary (`name (12 tools)` / `name (error: …)`). */
525
+ export function formatServerStatusSummary(
526
+ server: AccountMcpServer,
527
+ errorDetail?: string
528
+ ): string {
529
+ switch (server.status) {
530
+ case "connected":
531
+ return `${server.serverIdentifier} (${server.tools.length} tools)`;
532
+ case "needsAuth":
533
+ return `${server.serverIdentifier} (needsAuth — re-authorize in Cursor)`;
534
+ case "loading":
535
+ return `${server.serverIdentifier} (loading)`;
536
+ case "error": {
537
+ const detail =
538
+ errorDetail === undefined || errorDetail.trim() === ""
539
+ ? "backend could not reach connector"
540
+ : errorDetail.trim();
541
+ return `${server.serverIdentifier} (error: ${detail})`;
542
+ }
543
+ default:
544
+ return `${server.serverIdentifier} (${server.status})`;
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Best-effort detail for an `error`-status connector. ListSandMcpTools only
550
+ * returns the status enum today, so probe ExecuteSandMcpTool (ping) and surface
551
+ * the backend failure text (e.g. `HTTP MCP tool execution failed: fetch failed`).
552
+ */
553
+ export async function diagnoseConnectorError(
554
+ client: Pick<CursorBackendClient, "executeAccountMcpTool">,
555
+ serverIdentifier: string
556
+ ): Promise<string | undefined> {
557
+ try {
558
+ const result = await client.executeAccountMcpTool({
559
+ serverIdentifier,
560
+ // Conventional health tool; even a tool-not-found path usually still
561
+ // surfaces the same transport failure when the connector is down.
562
+ toolName: `${serverIdentifier}-ping`,
563
+ args: {},
564
+ });
565
+ const text = result.content
566
+ .filter(
567
+ (part): part is { type: "text"; text: string } => part.type === "text"
568
+ )
569
+ .map((part) => part.text.trim())
570
+ .filter((part) => part.length > 0)
571
+ .join(" ");
572
+ if (result.isError) {
573
+ return text.length > 0
574
+ ? text
575
+ : "backend reported an error with no detail";
576
+ }
577
+ // Listing said error but ping succeeded — surface that inconsistency.
578
+ return text.length > 0
579
+ ? `listing reported error but probe succeeded: ${text}`
580
+ : "listing reported error but probe succeeded";
581
+ } catch (error: unknown) {
582
+ return describeError(error);
583
+ }
584
+ }