@cursor/july 0.1.10 → 0.1.12

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 (197) hide show
  1. package/AGENTS.md +1 -0
  2. package/dist/bin/agent-serve.js +17 -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.CY2buaEE.js → app.Dh_JkJjI.js} +1 -1
  28. package/dist/docs/assets/chunks/@localSearchIndexroot.Er_R50vY.js +1 -0
  29. package/dist/docs/assets/chunks/{VPLocalSearchBox.Ix954SFJ.js → VPLocalSearchBox.CptQNLkR.js} +1 -1
  30. package/dist/docs/assets/chunks/{theme.PDwuJbyi.js → theme.DWflH-LQ.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.CjfiV2Q-.js → reference_cli.md.B74NrVzb.js} +3 -3
  38. package/dist/docs/assets/{reference_cli.md.CjfiV2Q-.lean.js → reference_cli.md.B74NrVzb.lean.js} +1 -1
  39. package/dist/docs/assets/{reference_connections.md.FbWupyzW.js → reference_connections.md.CsSr3rDz.js} +8 -3
  40. package/dist/docs/assets/{reference_connections.md.FbWupyzW.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 +6 -6
  78. package/dist/docs/reference/connections.html +12 -7
  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-mcp-oauth.d.ts +16 -0
  99. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -0
  100. package/dist/internal/cli-mcp-oauth.js +194 -0
  101. package/dist/internal/cli-mcp.d.ts +4 -1
  102. package/dist/internal/cli-mcp.d.ts.map +1 -1
  103. package/dist/internal/cli-mcp.js +19 -1
  104. package/dist/internal/cursor/account-mcp.d.ts +17 -4
  105. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  106. package/dist/internal/cursor/account-mcp.js +105 -17
  107. package/dist/internal/cursor-event-relay.d.ts +2 -0
  108. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  109. package/dist/internal/cursor-event-relay.js +3 -0
  110. package/dist/internal/discovery.d.ts.map +1 -1
  111. package/dist/internal/discovery.js +64 -10
  112. package/dist/internal/distribution.d.ts.map +1 -1
  113. package/dist/internal/distribution.js +1 -0
  114. package/dist/internal/json-dir-store.js +100 -0
  115. package/dist/internal/logs-client.js +1 -1
  116. package/dist/internal/mcp-host.d.ts.map +1 -1
  117. package/dist/internal/mcp-host.js +49 -26
  118. package/dist/internal/mcp-oauth.d.ts +88 -0
  119. package/dist/internal/mcp-oauth.d.ts.map +1 -0
  120. package/dist/internal/mcp-oauth.js +385 -0
  121. package/dist/internal/resolved-connections.d.ts.map +1 -1
  122. package/dist/internal/resolved-connections.js +4 -1
  123. package/dist/internal/sdk-runner.d.ts +9 -0
  124. package/dist/internal/sdk-runner.d.ts.map +1 -1
  125. package/dist/internal/sdk-runner.js +28 -2
  126. package/dist/internal/server.d.ts.map +1 -1
  127. package/dist/internal/server.js +13 -5
  128. package/dist/internal/session-engine.d.ts +7 -9
  129. package/dist/internal/session-engine.d.ts.map +1 -1
  130. package/dist/internal/session-engine.js +96 -64
  131. package/dist/internal/sessions-client.d.ts.map +1 -1
  132. package/dist/internal/sessions-client.js +2 -1
  133. package/dist/internal/stream-progress.d.ts.map +1 -1
  134. package/dist/internal/stream-progress.js +8 -4
  135. package/dist/internal/turn-governor.d.ts +54 -0
  136. package/dist/internal/turn-governor.d.ts.map +1 -0
  137. package/dist/internal/turn-governor.js +111 -0
  138. package/dist/playground/assets/index-1dzluIcW.js +86 -0
  139. package/dist/playground/assets/index-kZjeJs1v.css +1 -0
  140. package/dist/playground/index.html +2 -2
  141. package/dist/prompt.d.ts +39 -0
  142. package/dist/prompt.d.ts.map +1 -0
  143. package/dist/prompt.js +70 -0
  144. package/dist/types.d.ts +78 -5
  145. package/dist/types.d.ts.map +1 -1
  146. package/dist/types.js +6 -0
  147. package/docs/README.md +4 -0
  148. package/docs/deployment.md +6 -1
  149. package/docs/guides/mcp-oauth.md +157 -0
  150. package/docs/reference/cli.md +28 -0
  151. package/docs/reference/connections.md +28 -0
  152. package/docs/reference/prompt.md +47 -0
  153. package/docs/reference/tools.md +21 -0
  154. package/docs/troubleshooting.md +11 -0
  155. package/package.json +9 -2
  156. package/skills/create-agent/SKILL.md +7 -3
  157. package/skills/framework-map/SKILL.md +1 -0
  158. package/skills/mcp-auth/SKILL.md +138 -0
  159. package/src/bin/agent-serve.ts +20 -3
  160. package/src/channels/github/github-channel.ts +2 -0
  161. package/src/channels/github/types.ts +4 -0
  162. package/src/channels/slack/cursor-account.ts +2 -0
  163. package/src/channels/slack/dispatch.ts +49 -0
  164. package/src/channels/slack/eval-directive.ts +348 -0
  165. package/src/channels/slack/index.ts +1 -0
  166. package/src/channels/slack/slack-channel.ts +3 -0
  167. package/src/channels/slack/types.ts +7 -0
  168. package/src/connections.ts +10 -5
  169. package/src/evals.ts +43 -0
  170. package/src/index.ts +3 -0
  171. package/src/internal/cli-mcp-oauth.ts +254 -0
  172. package/src/internal/cli-mcp.ts +25 -2
  173. package/src/internal/cursor/account-mcp.ts +153 -41
  174. package/src/internal/cursor-event-relay.ts +5 -0
  175. package/src/internal/discovery.ts +70 -8
  176. package/src/internal/distribution.ts +1 -0
  177. package/src/internal/logs-client.ts +1 -1
  178. package/src/internal/mcp-host.ts +33 -9
  179. package/src/internal/mcp-oauth.ts +496 -0
  180. package/src/internal/resolved-connections.ts +6 -1
  181. package/src/internal/sdk-runner.ts +37 -1
  182. package/src/internal/server.ts +13 -5
  183. package/src/internal/session-engine.ts +115 -74
  184. package/src/internal/sessions-client.ts +2 -1
  185. package/src/internal/stream-progress.ts +8 -0
  186. package/src/internal/turn-governor.ts +145 -0
  187. package/src/prompt.ts +83 -0
  188. package/src/types.ts +76 -6
  189. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  190. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  191. package/dist/docs/assets/chunks/@localSearchIndexroot.OfDZ5CXg.js +0 -1
  192. package/dist/internal/persistence-coordinator.d.ts +0 -127
  193. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  194. package/dist/persistence.d.ts +0 -184
  195. package/dist/persistence.d.ts.map +0 -1
  196. package/dist/playground/assets/index-C0-QEG4O.js +0 -86
  197. package/dist/playground/assets/index-z3JY9O2x.css +0 -1
@@ -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
+ }
@@ -25,6 +25,8 @@ export type CursorScmPullRequestMeta = {
25
25
  title?: string;
26
26
  body?: string | null;
27
27
  draft?: boolean;
28
+ /** True when the PR was merged — required by closed+merged channel hooks. */
29
+ merged?: boolean;
28
30
  authorLogin?: string;
29
31
  headRef?: string;
30
32
  headSha?: string;
@@ -163,6 +165,9 @@ export function scmMetadataToGitHubPayload(event: {
163
165
  head: { ref: headRef, sha: headSha },
164
166
  base: { ref: baseRef, sha: prMeta?.baseSha },
165
167
  };
168
+ if (prMeta?.merged !== undefined) {
169
+ pullRequest.merged = prMeta.merged;
170
+ }
166
171
  if (prMeta?.authorLogin !== undefined) {
167
172
  pullRequest.user = { login: prMeta.authorLogin };
168
173
  }
@@ -21,6 +21,7 @@ import { type ABConfigFile, resolveABMaxPlaygroundSessions } from "../ab.js";
21
21
  import { httpChannel } from "../channels.js";
22
22
  import type { StorageDefinition } from "../storage.js";
23
23
  import {
24
+ type AgentConcurrencyOptions,
24
25
  type AgentConfigDefinition,
25
26
  type AgentHostingOptions,
26
27
  type AgentProject,
@@ -40,6 +41,7 @@ import {
40
41
  type HookDefinition,
41
42
  type HttpChannelDefinition,
42
43
  type InstructionsDefinition,
44
+ MAX_RUNNING_TURNS_LIMIT,
43
45
  type ModelSetting,
44
46
  type ResolvedAgent,
45
47
  type ScheduleDefinition,
@@ -50,6 +52,7 @@ import {
50
52
  toPublicCloudInfo,
51
53
  } from "../types.js";
52
54
  import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
55
+ import { normalizePositiveInt } from "./bounded-int.js";
53
56
  import { getDefinitionKind } from "./brand.js";
54
57
  import { isValidCron } from "./cron.js";
55
58
  import { parseFrontmatter } from "./frontmatter.js";
@@ -372,6 +375,7 @@ async function loadAgent(
372
375
  cwd: localCwd,
373
376
  };
374
377
  const hosting = resolveHosting(ctx, agentDir, config?.hosting);
378
+ const concurrency = resolveConcurrency(ctx, agentDir, config?.concurrency);
375
379
 
376
380
  if (runtime === "cloud") {
377
381
  warnCloudRuntimeLimitations(ctx, agentDir, {
@@ -393,6 +397,7 @@ async function loadAgent(
393
397
  cloud,
394
398
  local,
395
399
  hosting,
400
+ concurrency,
396
401
  tools,
397
402
  skills,
398
403
  connections,
@@ -469,6 +474,37 @@ function resolveHosting(
469
474
  };
470
475
  }
471
476
 
477
+ /**
478
+ * Validate the authored `concurrency` block. Invalid values are **error**
479
+ * diagnostics (serve refuses to start): silently dropping a turn ceiling
480
+ * would fail open to unlimited parallelism, the opposite of what the
481
+ * author asked for.
482
+ */
483
+ function resolveConcurrency(
484
+ ctx: DiscoveryContext,
485
+ agentDir: string,
486
+ concurrency: AgentConcurrencyOptions | undefined
487
+ ): AgentConcurrencyOptions | undefined {
488
+ if (concurrency === undefined || concurrency.maxRunningTurns === undefined) {
489
+ return undefined;
490
+ }
491
+ try {
492
+ return {
493
+ maxRunningTurns: normalizePositiveInt(concurrency.maxRunningTurns, {
494
+ label: "concurrency.maxRunningTurns",
495
+ max: MAX_RUNNING_TURNS_LIMIT,
496
+ }),
497
+ };
498
+ } catch (error) {
499
+ ctx.diagnostics.push({
500
+ severity: "error",
501
+ path: displayPath(ctx, join(agentDir, "agent.ts")),
502
+ message: error instanceof Error ? error.message : String(error),
503
+ });
504
+ return undefined;
505
+ }
506
+ }
507
+
472
508
  async function loadAgentConfig(
473
509
  ctx: DiscoveryContext,
474
510
  agentDir: string,
@@ -831,11 +867,24 @@ function connectionTransportLabel(transport: ConnectionTransport): string {
831
867
  return `agent:${transport.agent}`;
832
868
  }
833
869
  if ("url" in transport) {
834
- return transport.url;
870
+ return transport.oauth === true
871
+ ? `${transport.url} (oauth)`
872
+ : transport.url;
835
873
  }
836
874
  return transport.command;
837
875
  }
838
876
 
877
+ function hasAuthorizationHeader(
878
+ headers: Record<string, string> | undefined
879
+ ): boolean {
880
+ if (headers === undefined) {
881
+ return false;
882
+ }
883
+ return Object.keys(headers).some(
884
+ (key) => key.toLowerCase() === "authorization"
885
+ );
886
+ }
887
+
839
888
  function connectionTransport(
840
889
  connection: ConnectionDefinition
841
890
  ): ConnectionTransport | undefined {
@@ -852,11 +901,10 @@ function connectionTransport(
852
901
  }
853
902
  if ("url" in connection && !isBlank(connection.url)) {
854
903
  return {
855
- ...(connection.type === undefined ? {} : { type: connection.type }),
904
+ type: connection.type,
856
905
  url: connection.url,
857
- ...(connection.headers === undefined
858
- ? {}
859
- : { headers: connection.headers }),
906
+ headers: connection.headers,
907
+ oauth: connection.oauth === true ? true : undefined,
860
908
  };
861
909
  }
862
910
  if ("command" in connection && !isBlank(connection.command)) {
@@ -974,6 +1022,19 @@ async function loadConnections(
974
1022
  continue;
975
1023
  }
976
1024
  const connection = definition as ConnectionDefinition;
1025
+ if (
1026
+ "url" in connection &&
1027
+ connection.oauth === true &&
1028
+ hasAuthorizationHeader(connection.headers)
1029
+ ) {
1030
+ ctx.diagnostics.push({
1031
+ severity: "error",
1032
+ path: displayPath(ctx, entryPath),
1033
+ message:
1034
+ "MCP connection `oauth: true` cannot set an Authorization header. Drop the header or remove `oauth`.",
1035
+ });
1036
+ continue;
1037
+ }
977
1038
  const transport = connectionTransport(connection);
978
1039
  if (transport === undefined) {
979
1040
  ctx.diagnostics.push({
@@ -1009,9 +1070,8 @@ async function loadConnections(
1009
1070
  connections.push({
1010
1071
  name,
1011
1072
  transport,
1012
- ...(connection.description === undefined
1013
- ? {}
1014
- : { description: connection.description }),
1073
+ description: connection.description,
1074
+ hostOnly: connection.hostOnly,
1015
1075
  });
1016
1076
  }
1017
1077
  return connections;
@@ -1529,6 +1589,7 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
1529
1589
  agent.cloud === undefined ? undefined : toPublicCloudInfo(agent.cloud),
1530
1590
  local: agent.local,
1531
1591
  hosting: agent.hosting,
1592
+ concurrency: agent.concurrency,
1532
1593
  mcp: { path: MCP_ENDPOINT_PATH, tools: mcpEndpointToolNames(project) },
1533
1594
  instructions:
1534
1595
  agent.instructions === undefined
@@ -1554,6 +1615,7 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
1554
1615
  connections: agent.connections.map((connection) => ({
1555
1616
  name: connection.name,
1556
1617
  transport: connectionTransportLabel(connection.transport),
1618
+ hostOnly: connection.hostOnly,
1557
1619
  })),
1558
1620
  subagents: agent.subagents.map((subagent) => ({
1559
1621
  name: subagent.name,