@cursor/july 0.1.68 → 0.1.74

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 (252) hide show
  1. package/AGENTS.md +17 -13
  2. package/README.md +23 -11
  3. package/dist/bin/agent-serve.js +10 -7
  4. package/dist/channels/github/api.d.ts +2 -0
  5. package/dist/channels/github/api.d.ts.map +1 -1
  6. package/dist/channels/github/api.js +2 -1
  7. package/dist/channels/github/binding.d.ts +7 -0
  8. package/dist/channels/github/binding.d.ts.map +1 -1
  9. package/dist/channels/github/binding.js +4 -0
  10. package/dist/channels/github/checks.d.ts +99 -0
  11. package/dist/channels/github/checks.d.ts.map +1 -0
  12. package/dist/channels/github/checks.js +108 -0
  13. package/dist/channels/github/cursor-account.d.ts +4 -4
  14. package/dist/channels/github/cursor-account.d.ts.map +1 -1
  15. package/dist/channels/github/cursor-account.js +7 -5
  16. package/dist/channels/github/index.d.ts +1 -0
  17. package/dist/channels/github/index.d.ts.map +1 -1
  18. package/dist/channels/github/index.js +1 -0
  19. package/dist/channels/github/types.d.ts +8 -0
  20. package/dist/channels/github/types.d.ts.map +1 -1
  21. package/dist/channels/slack/manifest.d.ts +2 -2
  22. package/dist/channels/slack/manifest.d.ts.map +1 -1
  23. package/dist/channels/slack/manifest.js +13 -11
  24. package/dist/channels/slack/types.d.ts +2 -2
  25. package/dist/channels/slack/types.d.ts.map +1 -1
  26. package/dist/connections.d.ts +17 -5
  27. package/dist/connections.d.ts.map +1 -1
  28. package/dist/connections.js +17 -5
  29. package/dist/docs/404.html +2 -2
  30. package/dist/docs/ab.html +3 -3
  31. package/dist/docs/assets/{app.C5XUTCw9.js → app.BETd-WqU.js} +1 -1
  32. package/dist/docs/assets/chunks/@localSearchIndexroot.DM5INS71.js +1 -0
  33. package/dist/docs/assets/chunks/{VPLocalSearchBox.CGDTAYHY.js → VPLocalSearchBox.wEaGEmV9.js} +1 -1
  34. package/dist/docs/assets/chunks/{theme.lPixh250.js → theme.DNDicFOP.js} +2 -2
  35. package/dist/docs/assets/{concepts.md.CqOsxbMU.js → concepts.md.BXAm6G-C.js} +1 -1
  36. package/dist/docs/assets/{guides_mcp-oauth.md.C7G7IykG.js → guides_mcp-oauth.md.CzEB6RaG.js} +1 -1
  37. package/dist/docs/assets/{reference_agent-config.md.DHkkvcEZ.js → reference_agent-config.md.BZnEKfEc.js} +3 -4
  38. package/dist/docs/assets/{reference_agent-config.md.DHkkvcEZ.lean.js → reference_agent-config.md.BZnEKfEc.lean.js} +1 -1
  39. package/dist/docs/assets/{reference_cli.md.Cl14Jfo3.js → reference_cli.md.TAaYU8br.js} +3 -3
  40. package/dist/docs/assets/{reference_cli.md.Cl14Jfo3.lean.js → reference_cli.md.TAaYU8br.lean.js} +1 -1
  41. package/dist/docs/assets/{reference_connections.md.Bwswbm6H.js → reference_connections.md.Cu3N-S3Q.js} +18 -11
  42. package/dist/docs/assets/{reference_connections.md.Bwswbm6H.lean.js → reference_connections.md.Cu3N-S3Q.lean.js} +1 -1
  43. package/dist/docs/assets/{reference_sessions.md.DBVFi2Sx.js → reference_sessions.md.B0DdlM-K.js} +1 -1
  44. package/dist/docs/assets/{reference_tools.md.Wmvb7Lob.js → reference_tools.md.DTg_kEsx.js} +1 -1
  45. package/dist/docs/assets/scaffolding-agents.md.CiGsJ1aw.js +1 -0
  46. package/dist/docs/assets/{scaffolding-agents.md.CrWSA0Ko.lean.js → scaffolding-agents.md.CiGsJ1aw.lean.js} +1 -1
  47. package/dist/docs/assets/troubleshooting.md.DQ-hYuZT.js +1 -0
  48. package/dist/docs/building-with-agents.html +3 -3
  49. package/dist/docs/concepts.html +5 -5
  50. package/dist/docs/deployment.html +3 -3
  51. package/dist/docs/evals.html +3 -3
  52. package/dist/docs/example-agents/approval-buddy.html +3 -3
  53. package/dist/docs/example-agents/benny.html +3 -3
  54. package/dist/docs/example-agents/bugbot.html +3 -3
  55. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  56. package/dist/docs/example-agents/codeowners-review.html +3 -3
  57. package/dist/docs/example-agents/concierge.html +3 -3
  58. package/dist/docs/example-agents/fsd.html +3 -3
  59. package/dist/docs/example-agents/index.html +3 -3
  60. package/dist/docs/example-agents/knowledge-base.html +3 -3
  61. package/dist/docs/example-agents/oncall.html +3 -3
  62. package/dist/docs/example-agents/security-reviewer.html +3 -3
  63. package/dist/docs/example-agents/slack-agent.html +3 -3
  64. package/dist/docs/example-agents/weather-agent.html +3 -3
  65. package/dist/docs/guides/agent-to-agent.html +3 -3
  66. package/dist/docs/guides/cloud-runtime.html +3 -3
  67. package/dist/docs/guides/convert-automation.html +3 -3
  68. package/dist/docs/guides/github.html +3 -3
  69. package/dist/docs/guides/human-in-the-loop.html +3 -3
  70. package/dist/docs/guides/mcp-oauth.html +5 -5
  71. package/dist/docs/guides/opentelemetry.html +3 -3
  72. package/dist/docs/guides/slack.html +3 -3
  73. package/dist/docs/guides/webhooks.html +3 -3
  74. package/dist/docs/hashmap.json +1 -1
  75. package/dist/docs/hillclimbing.html +3 -3
  76. package/dist/docs/index.html +3 -3
  77. package/dist/docs/quickstart.html +3 -3
  78. package/dist/docs/reference/agent-config.html +5 -6
  79. package/dist/docs/reference/artifacts.html +3 -3
  80. package/dist/docs/reference/channels.html +3 -3
  81. package/dist/docs/reference/cli.html +6 -6
  82. package/dist/docs/reference/connections.html +20 -13
  83. package/dist/docs/reference/hooks.html +3 -3
  84. package/dist/docs/reference/http-api.html +3 -3
  85. package/dist/docs/reference/instructions.html +3 -3
  86. package/dist/docs/reference/playground.html +3 -3
  87. package/dist/docs/reference/project-layout.html +3 -3
  88. package/dist/docs/reference/prompt.html +3 -3
  89. package/dist/docs/reference/schedules.html +3 -3
  90. package/dist/docs/reference/sessions.html +5 -5
  91. package/dist/docs/reference/skills.html +3 -3
  92. package/dist/docs/reference/subagents.html +3 -3
  93. package/dist/docs/reference/tools.html +5 -5
  94. package/dist/docs/scaffolding-agents.html +5 -5
  95. package/dist/docs/storage.html +3 -3
  96. package/dist/docs/templates/agentic-owners.html +3 -3
  97. package/dist/docs/templates/demo.html +3 -3
  98. package/dist/docs/templates/pr-autofixer.html +3 -3
  99. package/dist/docs/templates/security-reviewer.html +3 -3
  100. package/dist/docs/templates/triage.html +3 -3
  101. package/dist/docs/troubleshooting.html +5 -5
  102. package/dist/internal/advertise-tools.d.ts +11 -6
  103. package/dist/internal/advertise-tools.d.ts.map +1 -1
  104. package/dist/internal/advertise-tools.js +63 -30
  105. package/dist/internal/cli-deploy.d.ts.map +1 -1
  106. package/dist/internal/cli-deploy.js +2 -4
  107. package/dist/internal/cli-skills.d.ts +4 -2
  108. package/dist/internal/cli-skills.d.ts.map +1 -1
  109. package/dist/internal/cli-skills.js +8 -3
  110. package/dist/internal/convert-automation/convert-workflow.d.ts.map +1 -1
  111. package/dist/internal/convert-automation/convert-workflow.js +2 -10
  112. package/dist/internal/cursor/account-mcp.d.ts +29 -3
  113. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  114. package/dist/internal/cursor/account-mcp.js +147 -67
  115. package/dist/internal/cursor/backend-client.d.ts +11 -6
  116. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  117. package/dist/internal/cursor/backend-client.js +32 -10
  118. package/dist/internal/cursor-account-mcp-auth.d.ts +11 -0
  119. package/dist/internal/cursor-account-mcp-auth.d.ts.map +1 -1
  120. package/dist/internal/cursor-account-mcp-auth.js +69 -0
  121. package/dist/internal/deploy-client.d.ts +0 -7
  122. package/dist/internal/deploy-client.d.ts.map +1 -1
  123. package/dist/internal/deploy-client.js +2 -4
  124. package/dist/internal/deploy-manifest.d.ts +15 -24
  125. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  126. package/dist/internal/deploy-manifest.js +38 -27
  127. package/dist/internal/deployment-scm-event-stream.d.ts +4 -1
  128. package/dist/internal/deployment-scm-event-stream.d.ts.map +1 -1
  129. package/dist/internal/deployment-scm-event-stream.js +15 -1
  130. package/dist/internal/discovery.d.ts.map +1 -1
  131. package/dist/internal/discovery.js +42 -10
  132. package/dist/internal/init-project.d.ts.map +1 -1
  133. package/dist/internal/init-project.js +7 -106
  134. package/dist/internal/init-scaffold.d.ts +8 -0
  135. package/dist/internal/init-scaffold.d.ts.map +1 -0
  136. package/dist/internal/init-scaffold.js +168 -0
  137. package/dist/internal/inline-stdio-mcp.d.ts +2 -0
  138. package/dist/internal/inline-stdio-mcp.d.ts.map +1 -1
  139. package/dist/internal/inline-stdio-mcp.js +10 -8
  140. package/dist/internal/install-cursor-skills.d.ts +71 -16
  141. package/dist/internal/install-cursor-skills.d.ts.map +1 -1
  142. package/dist/internal/install-cursor-skills.js +160 -48
  143. package/dist/internal/json-schema.d.ts +7 -0
  144. package/dist/internal/json-schema.d.ts.map +1 -1
  145. package/dist/internal/json-schema.js +18 -3
  146. package/dist/internal/mcp-endpoint.js +7 -7
  147. package/dist/internal/mcp-host.js +4 -2
  148. package/dist/internal/mcp-oauth.d.ts +22 -0
  149. package/dist/internal/mcp-oauth.d.ts.map +1 -1
  150. package/dist/internal/mcp-oauth.js +40 -1
  151. package/dist/internal/package-inline-stdio.d.ts.map +1 -1
  152. package/dist/internal/package-inline-stdio.js +5 -5
  153. package/dist/internal/review-comments.d.ts +97 -0
  154. package/dist/internal/review-comments.d.ts.map +1 -1
  155. package/dist/internal/review-comments.js +151 -0
  156. package/dist/internal/sanitize-mcp-tool-name.d.ts +20 -0
  157. package/dist/internal/sanitize-mcp-tool-name.d.ts.map +1 -0
  158. package/dist/internal/sanitize-mcp-tool-name.js +30 -0
  159. package/dist/internal/sdk-runner.d.ts.map +1 -1
  160. package/dist/internal/sdk-runner.js +6 -2
  161. package/dist/internal/session-engine.d.ts.map +1 -1
  162. package/dist/internal/session-engine.js +4 -6
  163. package/dist/internal/shallow-clone.d.ts +94 -39
  164. package/dist/internal/shallow-clone.d.ts.map +1 -1
  165. package/dist/internal/shallow-clone.js +188 -68
  166. package/dist/internal/terminal-style.d.ts +18 -0
  167. package/dist/internal/terminal-style.d.ts.map +1 -1
  168. package/dist/internal/terminal-style.js +46 -1
  169. package/dist/internal/workspace.d.ts +2 -0
  170. package/dist/internal/workspace.d.ts.map +1 -1
  171. package/dist/internal/workspace.js +5 -0
  172. package/dist/playground/assets/index-D_8I9TM0.js +85 -0
  173. package/dist/playground/assets/index-M3nSMc6_.css +1 -0
  174. package/dist/playground/index.html +2 -2
  175. package/dist/tools.d.ts +7 -0
  176. package/dist/tools.d.ts.map +1 -1
  177. package/dist/types.d.ts +28 -3
  178. package/dist/types.d.ts.map +1 -1
  179. package/docs/concepts.md +4 -4
  180. package/docs/guides/mcp-oauth.md +3 -1
  181. package/docs/reference/agent-config.md +11 -12
  182. package/docs/reference/cli.md +16 -8
  183. package/docs/reference/connections.md +44 -26
  184. package/docs/reference/sessions.md +2 -2
  185. package/docs/reference/tools.md +1 -1
  186. package/docs/scaffolding-agents.md +14 -6
  187. package/docs/troubleshooting.md +3 -2
  188. package/package.json +6 -1
  189. package/scripts/install-cursor-skills.mjs +36 -0
  190. package/skills/ab/SKILL.md +1 -1
  191. package/skills/create-agent/SKILL.md +13 -10
  192. package/skills/debug/SKILL.md +3 -2
  193. package/skills/evals/SKILL.md +1 -1
  194. package/skills/framework-map/SKILL.md +7 -7
  195. package/skills/github/SKILL.md +1 -1
  196. package/skills/hillclimb/SKILL.md +1 -1
  197. package/skills/mcp-auth/SKILL.md +9 -7
  198. package/skills/otel/SKILL.md +1 -1
  199. package/skills/setup-slack/SKILL.md +1 -1
  200. package/src/bin/agent-serve.ts +10 -7
  201. package/src/channels/github/api.ts +6 -1
  202. package/src/channels/github/binding.ts +21 -0
  203. package/src/channels/github/checks.ts +179 -0
  204. package/src/channels/github/cursor-account.ts +9 -6
  205. package/src/channels/github/index.ts +1 -0
  206. package/src/channels/github/types.ts +8 -0
  207. package/src/channels/slack/manifest.ts +8 -10
  208. package/src/channels/slack/types.ts +2 -2
  209. package/src/connections.ts +17 -5
  210. package/src/internal/advertise-tools.ts +72 -23
  211. package/src/internal/cli-deploy.ts +4 -15
  212. package/src/internal/cli-skills.ts +10 -2
  213. package/src/internal/convert-automation/convert-workflow.ts +2 -11
  214. package/src/internal/cursor/account-mcp.ts +208 -73
  215. package/src/internal/cursor/backend-client.ts +45 -11
  216. package/src/internal/cursor-account-mcp-auth.ts +83 -0
  217. package/src/internal/deploy-client.ts +0 -10
  218. package/src/internal/deploy-manifest.ts +33 -39
  219. package/src/internal/deployment-scm-event-stream.ts +17 -1
  220. package/src/internal/discovery.ts +47 -3
  221. package/src/internal/init-project.ts +7 -133
  222. package/src/internal/init-scaffold.ts +176 -0
  223. package/src/internal/inline-stdio-mcp.ts +13 -0
  224. package/src/internal/install-cursor-skills.ts +204 -49
  225. package/src/internal/json-schema.ts +24 -3
  226. package/src/internal/mcp-endpoint.ts +6 -0
  227. package/src/internal/mcp-host.ts +4 -0
  228. package/src/internal/mcp-oauth.ts +55 -1
  229. package/src/internal/package-inline-stdio.ts +5 -1
  230. package/src/internal/review-comments.ts +245 -0
  231. package/src/internal/sanitize-mcp-tool-name.ts +33 -0
  232. package/src/internal/sdk-runner.ts +6 -3
  233. package/src/internal/session-engine.ts +7 -0
  234. package/src/internal/shallow-clone.ts +293 -105
  235. package/src/internal/terminal-style.ts +67 -1
  236. package/src/internal/workspace.ts +6 -0
  237. package/src/tools.ts +7 -0
  238. package/src/types.ts +32 -4
  239. package/templates/agentic-owners/agent/agent.ts +3 -0
  240. package/templates/demo/agent/agent.ts +3 -0
  241. package/templates/security-reviewer/agent/agent.ts +3 -0
  242. package/templates/triage/agent/agent.ts +3 -0
  243. package/dist/docs/assets/chunks/@localSearchIndexroot.DvUR3lv9.js +0 -1
  244. package/dist/docs/assets/scaffolding-agents.md.CrWSA0Ko.js +0 -1
  245. package/dist/docs/assets/troubleshooting.md.CuVztCKs.js +0 -1
  246. package/dist/playground/assets/index-CoL7xCJl.css +0 -1
  247. package/dist/playground/assets/index-yxs1heea.js +0 -85
  248. /package/dist/docs/assets/{concepts.md.CqOsxbMU.lean.js → concepts.md.BXAm6G-C.lean.js} +0 -0
  249. /package/dist/docs/assets/{guides_mcp-oauth.md.C7G7IykG.lean.js → guides_mcp-oauth.md.CzEB6RaG.lean.js} +0 -0
  250. /package/dist/docs/assets/{reference_sessions.md.DBVFi2Sx.lean.js → reference_sessions.md.B0DdlM-K.lean.js} +0 -0
  251. /package/dist/docs/assets/{reference_tools.md.Wmvb7Lob.lean.js → reference_tools.md.DTg_kEsx.lean.js} +0 -0
  252. /package/dist/docs/assets/{troubleshooting.md.CuVztCKs.lean.js → troubleshooting.md.DQ-hYuZT.lean.js} +0 -0
@@ -23,9 +23,22 @@ import {
23
23
  import { bearerAuth } from "../../channels.js";
24
24
  import type { AgentProject, AuthPolicy } from "../../types.js";
25
25
  import { isCursorAccountTransport } from "../../types.js";
26
+ import { probeConnectorAuthUrls } from "../cursor-account-mcp-auth.js";
27
+ import { LOG_PREFIX } from "../distribution.js";
26
28
  import { handleStatelessMcpRequest } from "../mcp-endpoint.js";
27
29
  import { isLoopbackAddress } from "../net.js";
30
+ import {
31
+ sanitizeMcpToolName,
32
+ suffixMcpToolName,
33
+ } from "../sanitize-mcp-tool-name.js";
28
34
  import type { EngineResolvedConnection } from "../session-engine.js";
35
+ import {
36
+ colorEnabledFor,
37
+ formatTerminalBox,
38
+ stdoutPalette,
39
+ type TerminalPalette,
40
+ terminalHyperlink,
41
+ } from "../terminal-style.js";
29
42
  import type {
30
43
  AccountMcpServer,
31
44
  CursorBackendClient,
@@ -38,8 +51,13 @@ const CATALOG_TTL_MS = 60_000;
38
51
  * a model hallucinating tool names cannot turn into a backend hot loop.
39
52
  */
40
53
  const FORCE_REFRESH_MIN_INTERVAL_MS = 5_000;
41
- /** MCP clients commonly enforce `^[a-zA-Z0-9_-]{1,128}$` for tool names. */
42
- const MAX_TOOL_NAME_LENGTH = 128;
54
+
55
+ /** Dashboard Integrations & MCP — reconnect team / account connectors. */
56
+ export const CURSOR_ACCOUNT_MCP_DASHBOARD_URL =
57
+ "https://cursor.com/dashboard/integrations";
58
+ /** Cursor app Settings → MCP (Connect / Authenticate on the named row). */
59
+ export const CURSOR_ACCOUNT_MCP_SETTINGS_DEEPLINK =
60
+ "cursor://anysphere.cursor-deeplink/customize?tab=mcp";
43
61
 
44
62
  // ============================================================================
45
63
  // Authored account MCP connections (validated refs, mirroring peer MCP connections)
@@ -131,6 +149,8 @@ interface CatalogTool {
131
149
  serverIdentifier: string;
132
150
  description: string;
133
151
  inputSchema: Record<string, unknown>;
152
+ /** Present only when the connector's server declares one. */
153
+ outputSchema?: Record<string, unknown>;
134
154
  }
135
155
 
136
156
  interface Catalog {
@@ -155,6 +175,7 @@ export class CursorAccountMcpBridge {
155
175
  readonly secret: string = randomBytes(32).toString("hex");
156
176
  private readonly client: CursorBackendClient;
157
177
  private readonly logger: (line: string) => void;
178
+ private readonly palette: TerminalPalette;
158
179
  private readonly secretPolicy: AuthPolicy;
159
180
  private readonly allowRemoteCallers: boolean;
160
181
  private readonly catalogs = new Map<string, CatalogEntry>();
@@ -162,6 +183,8 @@ export class CursorAccountMcpBridge {
162
183
  constructor(args: {
163
184
  client: CursorBackendClient;
164
185
  logger?: (line: string) => void;
186
+ /** Defaults to stdout; tests pass `makePalette(false)` for a plain box. */
187
+ palette?: TerminalPalette;
165
188
  /**
166
189
  * Admit non-loopback callers presenting the secret. Only set when the
167
190
  * serve host configured a `publicUrl` (cloud turns reach the bridge
@@ -171,6 +194,7 @@ export class CursorAccountMcpBridge {
171
194
  }) {
172
195
  this.client = args.client;
173
196
  this.logger = args.logger ?? (() => {});
197
+ this.palette = args.palette ?? stdoutPalette();
174
198
  this.secretPolicy = bearerAuth(this.secret);
175
199
  this.allowRemoteCallers = args.allowRemoteCallers === true;
176
200
  }
@@ -213,9 +237,9 @@ export class CursorAccountMcpBridge {
213
237
 
214
238
  /**
215
239
  * Startup DX: log the account's connector statuses for `refs` without
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).
240
+ * blocking serve startup. Healthy catalogs stay a one-line summary;
241
+ * `needsAuth` / `error` / empty catalogs render a yellow warning box with
242
+ * reconnect URLs.
219
243
  *
220
244
  * Returns a promise that settles when every ref's log lines are written —
221
245
  * callers may ignore it; tests can await it.
@@ -229,28 +253,50 @@ export class CursorAccountMcpBridge {
229
253
  private async runPreflight(ref: CursorAccountConnectionRef): Promise<void> {
230
254
  try {
231
255
  const catalog = await this.getCatalog(ref);
232
- if (catalog.servers.length === 0) {
256
+ const errorDetails = new Map<string, string>();
257
+ const needsAuthNames = catalog.servers
258
+ .filter((server) => server.status === "needsAuth")
259
+ .map((server) => server.serverIdentifier);
260
+ const [authUrls] = await Promise.all([
261
+ probeConnectorAuthUrls({
262
+ client: this.client,
263
+ identifiers: needsAuthNames,
264
+ }),
265
+ Promise.all(
266
+ catalog.servers
267
+ .filter((server) => server.status === "error")
268
+ .map(async (server) => {
269
+ const detail = await diagnoseConnectorError(
270
+ this.client,
271
+ server.serverIdentifier
272
+ );
273
+ if (detail !== undefined) {
274
+ errorDetails.set(server.serverIdentifier, detail);
275
+ }
276
+ })
277
+ ),
278
+ ]);
279
+
280
+ const unhealthy = catalog.servers.some(
281
+ (server) => server.status === "needsAuth" || server.status === "error"
282
+ );
283
+ if (catalog.servers.length === 0 || unhealthy) {
233
284
  this.logger(
234
- `[agent-sdk] Cursor account MCP connection "${ref.name}": no matching connectors on this account — connect servers in Cursor (dashboard → MCP) or fix the \`servers\` filter`
285
+ formatCursorAccountMcpWarningBox({
286
+ connectionName: ref.name,
287
+ servers: catalog.servers.map((server) => ({
288
+ serverIdentifier: server.serverIdentifier,
289
+ status: server.status,
290
+ toolCount: server.tools.length,
291
+ errorDetail: errorDetails.get(server.serverIdentifier),
292
+ authUrl: authUrls.get(server.serverIdentifier),
293
+ })),
294
+ palette: this.palette,
295
+ })
235
296
  );
236
297
  return;
237
298
  }
238
299
 
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
247
- );
248
- if (detail !== undefined) {
249
- errorDetails.set(server.serverIdentifier, detail);
250
- }
251
- })
252
- );
253
-
254
300
  const summary = catalog.servers
255
301
  .map((server) =>
256
302
  formatServerStatusSummary(
@@ -260,41 +306,11 @@ export class CursorAccountMcpBridge {
260
306
  )
261
307
  .join(", ");
262
308
  this.logger(
263
- `[agent-sdk] Cursor account MCP connection "${ref.name}": ${summary}`
264
- );
265
-
266
- const needsAuth = catalog.servers.filter(
267
- (server) => server.status === "needsAuth"
309
+ `${LOG_PREFIX} Cursor account MCP connection "${ref.name}": ${summary}`
268
310
  );
269
- if (needsAuth.length > 0) {
270
- this.logger(
271
- `[agent-sdk] 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
- `[agent-sdk] Cursor account MCP connection "${ref.name}": ${details} — Cursor backend could not use this connector (not a missing agent-sdk 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
311
  } catch (error: unknown) {
296
312
  this.logger(
297
- `[agent-sdk] Cursor account MCP connection "${ref.name}" preflight failed: ${describeError(error)}`
313
+ `${LOG_PREFIX} Cursor account MCP connection "${ref.name}" preflight failed: ${describeError(error)}`
298
314
  );
299
315
  }
300
316
  }
@@ -316,6 +332,9 @@ export class CursorAccountMcpBridge {
316
332
  name: tool.exposedName,
317
333
  description: tool.description,
318
334
  inputSchema: tool.inputSchema,
335
+ ...(tool.outputSchema === undefined
336
+ ? {}
337
+ : { outputSchema: tool.outputSchema }),
319
338
  })),
320
339
  };
321
340
  });
@@ -448,7 +467,7 @@ function buildCatalog(servers: AccountMcpServer[]): Catalog {
448
467
  >();
449
468
  for (const server of servers) {
450
469
  for (const tool of server.tools) {
451
- const base = sanitizeToolName(tool.name);
470
+ const base = sanitizeMcpToolName(tool.name);
452
471
  const group = byBase.get(base);
453
472
  const item = { serverIdentifier: server.serverIdentifier, tool };
454
473
  if (group === undefined) {
@@ -477,10 +496,10 @@ function buildCatalog(servers: AccountMcpServer[]): Catalog {
477
496
  });
478
497
  group.forEach(({ serverIdentifier, tool }, index) => {
479
498
  let suffixIndex = index;
480
- let exposedName = suffixToolName(base, suffixIndex);
499
+ let exposedName = suffixMcpToolName(base, suffixIndex);
481
500
  while (byExposedName.has(exposedName)) {
482
501
  suffixIndex++;
483
- exposedName = suffixToolName(base, suffixIndex);
502
+ exposedName = suffixMcpToolName(base, suffixIndex);
484
503
  }
485
504
  const entry: CatalogTool = {
486
505
  exposedName,
@@ -488,6 +507,9 @@ function buildCatalog(servers: AccountMcpServer[]): Catalog {
488
507
  serverIdentifier,
489
508
  description: tool.description,
490
509
  inputSchema: tool.inputSchema,
510
+ ...(tool.outputSchema === undefined
511
+ ? {}
512
+ : { outputSchema: tool.outputSchema }),
491
513
  };
492
514
  tools.push(entry);
493
515
  byExposedName.set(entry.exposedName, entry);
@@ -496,29 +518,142 @@ function buildCatalog(servers: AccountMcpServer[]): Catalog {
496
518
  return { servers, tools, byExposedName };
497
519
  }
498
520
 
499
- /** Connector display names can contain anything (spaces, dots, emoji). */
500
- function sanitizeToolName(name: string): string {
501
- const cleaned = name
502
- .replace(/[^a-zA-Z0-9_-]/g, "_")
503
- .slice(0, MAX_TOOL_NAME_LENGTH);
504
- return cleaned === "" ? "tool" : cleaned;
521
+ function trimTrailingSlash(url: string): string {
522
+ return url.endsWith("/") ? url.slice(0, -1) : url;
523
+ }
524
+
525
+ function describeError(error: unknown): string {
526
+ return error instanceof Error ? error.message : String(error);
527
+ }
528
+
529
+ export interface CursorAccountMcpWarningServer {
530
+ serverIdentifier: string;
531
+ status: string;
532
+ toolCount: number;
533
+ errorDetail?: string;
534
+ /** Minted OAuth URL for a `needsAuth` connector, when the probe succeeded. */
535
+ authUrl?: string;
505
536
  }
506
537
 
507
- /** `base` for the first claimant, `base_2` / `base_3` … for colliders. */
508
- function suffixToolName(base: string, index: number): string {
509
- if (index === 0) {
510
- return base;
538
+ const MAX_ERROR_DETAIL_CHARS = 72;
539
+
540
+ function truncateDetail(detail: string): string {
541
+ const trimmed = detail.trim();
542
+ if (trimmed.length <= MAX_ERROR_DETAIL_CHARS) {
543
+ return trimmed;
511
544
  }
512
- const suffix = `_${index + 1}`;
513
- return `${base.slice(0, MAX_TOOL_NAME_LENGTH - suffix.length)}${suffix}`;
545
+ return `${trimmed.slice(0, MAX_ERROR_DETAIL_CHARS - 1)}…`;
514
546
  }
515
547
 
516
- function trimTrailingSlash(url: string): string {
517
- return url.endsWith("/") ? url.slice(0, -1) : url;
548
+ function formatWarningStatusRow(
549
+ server: CursorAccountMcpWarningServer,
550
+ nameWidth: number,
551
+ hyperlinks: boolean
552
+ ): string {
553
+ const name = server.serverIdentifier.padEnd(nameWidth);
554
+ switch (server.status) {
555
+ case "needsAuth":
556
+ return `${name} ${terminalHyperlink(connectUrl(server.authUrl), "Connect", hyperlinks)}`;
557
+ case "connected":
558
+ return `${name} ${server.toolCount} tools`;
559
+ case "error": {
560
+ const detail =
561
+ server.errorDetail === undefined || server.errorDetail.trim() === ""
562
+ ? "backend could not reach connector"
563
+ : truncateDetail(server.errorDetail);
564
+ return `${name} error: ${detail}`;
565
+ }
566
+ default:
567
+ return `${name} ${server.status}`;
568
+ }
518
569
  }
519
570
 
520
- function describeError(error: unknown): string {
521
- return error instanceof Error ? error.message : String(error);
571
+ function connectUrl(authUrl: string | undefined): string {
572
+ const minted = authUrl?.trim() ?? "";
573
+ if (
574
+ minted.startsWith("https://") &&
575
+ !minted.includes("\u001b") &&
576
+ !minted.includes("\u0007")
577
+ ) {
578
+ return minted;
579
+ }
580
+ return CURSOR_ACCOUNT_MCP_DASHBOARD_URL;
581
+ }
582
+
583
+ /**
584
+ * Yellow warning panel for unhealthy / missing Cursor-account connectors.
585
+ * One box replaces the old stacked `[agent-sdk]` log lines.
586
+ */
587
+ export function formatCursorAccountMcpWarningBox(args: {
588
+ connectionName: string;
589
+ servers: readonly CursorAccountMcpWarningServer[];
590
+ palette?: TerminalPalette;
591
+ hyperlinks?: boolean;
592
+ }): string {
593
+ const palette = args.palette ?? stdoutPalette();
594
+ const hyperlinks = args.hyperlinks ?? colorEnabledFor();
595
+ const copy = warningCopy({ ...args, hyperlinks });
596
+ return `\n${formatTerminalBox(
597
+ [copy.title, "", ...copy.intro, ...copy.actions],
598
+ {
599
+ color: palette.yellow,
600
+ padding: 2,
601
+ }
602
+ )}`;
603
+ }
604
+
605
+ function warningCopy(args: {
606
+ connectionName: string;
607
+ servers: readonly CursorAccountMcpWarningServer[];
608
+ hyperlinks: boolean;
609
+ }): { title: string; intro: string[]; actions: string[] } {
610
+ const empty = args.servers.length === 0;
611
+ const hasAuth = args.servers.some((server) => server.status === "needsAuth");
612
+ const hasError = args.servers.some((server) => server.status === "error");
613
+ const nameWidth = Math.max(
614
+ 0,
615
+ ...args.servers.map((server) => server.serverIdentifier.length)
616
+ );
617
+ const statusRows = args.servers.map((server) =>
618
+ formatWarningStatusRow(server, nameWidth, args.hyperlinks)
619
+ );
620
+ const links = [
621
+ ` ${CURSOR_ACCOUNT_MCP_SETTINGS_DEEPLINK}`,
622
+ ` ${CURSOR_ACCOUNT_MCP_DASHBOARD_URL}`,
623
+ ];
624
+ const restart = "Then press Enter to restart this serve.";
625
+
626
+ if (empty) {
627
+ return {
628
+ title: "No matching MCP connectors",
629
+ intro: [
630
+ `No matching connectors on this account ("${args.connectionName}").`,
631
+ ],
632
+ actions: [
633
+ "",
634
+ "Connect servers in Cursor, or fix the `servers` filter.",
635
+ ...links,
636
+ ],
637
+ };
638
+ }
639
+ if (hasAuth && !hasError) {
640
+ return {
641
+ title: "MCP authorization needed",
642
+ intro: statusRows,
643
+ actions: ["", restart, "", "Or reconnect at:", ...links],
644
+ };
645
+ }
646
+ return {
647
+ title: hasAuth ? "MCP authorization needed" : "MCP connector error",
648
+ intro: statusRows,
649
+ actions: [
650
+ "",
651
+ "Re-check the server URL is healthy, then reconnect at:",
652
+ ...links,
653
+ "",
654
+ restart,
655
+ ],
656
+ };
522
657
  }
523
658
 
524
659
  /** One-line status for the preflight summary (`name (12 tools)` / `name (error: …)`). */
@@ -83,6 +83,8 @@ const mcpToolSchema = z.object({
83
83
  description: z.string().optional(),
84
84
  /** `google.protobuf.Value` — an arbitrary JSON Schema object. */
85
85
  inputSchema: z.unknown().optional(),
86
+ /** Flat JSON-string output schema (`output_schema_json` on the proto). */
87
+ outputSchemaJson: z.string().optional(),
86
88
  });
87
89
 
88
90
  const getAvailableMcpServersResponseSchema = z.object({
@@ -146,6 +148,12 @@ export interface AccountMcpTool {
146
148
  description: string;
147
149
  /** JSON Schema for the tool input (defaulted when the server omits it). */
148
150
  inputSchema: Record<string, unknown>;
151
+ /**
152
+ * JSON Schema for the tool's structured output. Present only when the
153
+ * connector's server declares one — absent stays absent (never defaulted),
154
+ * matching the optional MCP `Tool.outputSchema` field.
155
+ */
156
+ outputSchema?: Record<string, unknown>;
149
157
  }
150
158
 
151
159
  export interface AccountMcpServer {
@@ -321,11 +329,15 @@ export class CursorBackendClient {
321
329
  return (parsed.servers ?? []).map((server) => ({
322
330
  serverIdentifier: server.serverIdentifier,
323
331
  status: server.status,
324
- tools: (server.tools ?? []).map((tool) => ({
325
- name: tool.name,
326
- description: tool.description ?? "",
327
- inputSchema: normalizeInputSchema(tool.inputSchema),
328
- })),
332
+ tools: (server.tools ?? []).map((tool) => {
333
+ const outputSchema = parseOutputSchemaJson(tool.outputSchemaJson);
334
+ return {
335
+ name: tool.name,
336
+ description: tool.description ?? "",
337
+ inputSchema: normalizeInputSchema(tool.inputSchema),
338
+ ...(outputSchema === undefined ? {} : { outputSchema }),
339
+ };
340
+ }),
329
341
  }));
330
342
  }
331
343
 
@@ -376,16 +388,15 @@ export class CursorBackendClient {
376
388
  }
377
389
 
378
390
  /**
379
- * Probe connectors for a service account and mint OAuth `authUrl`s where
380
- * authorization is needed. This is the dashboard's Authenticate flow: the
381
- * caller (a team member) opens the returned URL, the cursor.com callback
382
- * stores the token backend-side against the service account, and repeat
383
- * probes flip `requiresAuth` off.
391
+ * Probe connectors and mint OAuth `authUrl`s where authorization is
392
+ * needed. With `serviceAccountId`, this is the dashboard Authenticate
393
+ * flow for a hosted deployment's service account. Omit it to probe the
394
+ * signed-in user (local `serve` preflight).
384
395
  */
385
396
  async checkHttpMcpStatus(args: {
386
397
  serverIds: number[];
387
398
  oauthRedirectUri: string;
388
- serviceAccountId: string;
399
+ serviceAccountId?: string;
389
400
  }): Promise<McpConnectorStatus[]> {
390
401
  const raw = await this.rpc(
391
402
  "CheckHttpMcpStatus",
@@ -669,6 +680,29 @@ function normalizeInputSchema(schema: unknown): Record<string, unknown> {
669
680
  return { type: "object", properties: {} };
670
681
  }
671
682
 
683
+ /**
684
+ * Parse the proto's flat-JSON-string output schema. Unlike
685
+ * {@link normalizeInputSchema} there is no fallback: outputSchema is optional
686
+ * on the MCP wire, so absence (or malformed JSON crossing the backend
687
+ * boundary) means omitted.
688
+ */
689
+ function parseOutputSchemaJson(
690
+ outputSchemaJson: string | undefined
691
+ ): Record<string, unknown> | undefined {
692
+ if (outputSchemaJson === undefined) {
693
+ return undefined;
694
+ }
695
+ let parsed: unknown;
696
+ try {
697
+ parsed = JSON.parse(outputSchemaJson);
698
+ } catch {
699
+ return undefined;
700
+ }
701
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
702
+ ? (parsed as Record<string, unknown>)
703
+ : undefined;
704
+ }
705
+
672
706
  type McpResultJson = z.infer<typeof mcpResultSchema>;
673
707
 
674
708
  function toCallResult(result: McpResultJson): AccountMcpCallResult {
@@ -204,6 +204,89 @@ function selectConnectors(
204
204
  return { servers, unmatched };
205
205
  }
206
206
 
207
+ function uniqueConnectorMatch(
208
+ available: AvailableMcpServer[],
209
+ identifier: string
210
+ ): AvailableMcpServer | undefined {
211
+ const wanted = identifier.trim().toLowerCase();
212
+ if (wanted === "") {
213
+ return undefined;
214
+ }
215
+ const byIdentifier = available.filter(
216
+ (server) => server.serverIdentifier?.toLowerCase() === wanted
217
+ );
218
+ if (byIdentifier.length === 1) {
219
+ return byIdentifier[0];
220
+ }
221
+ if (byIdentifier.length > 1) {
222
+ return undefined;
223
+ }
224
+ const byName = available.filter(
225
+ (server) => server.name.toLowerCase() === wanted
226
+ );
227
+ if (byName.length === 1) {
228
+ return byName[0];
229
+ }
230
+ return undefined;
231
+ }
232
+
233
+ /**
234
+ * Mint OAuth `authUrl`s for connectors matching `identifiers` (name or
235
+ * `serverIdentifier`). Omit `serviceAccountId` to probe the signed-in user
236
+ * (local `serve` preflight). Failures return an empty map so the caller
237
+ * can fall back to the dashboard URL.
238
+ */
239
+ export async function probeConnectorAuthUrls(args: {
240
+ client: Pick<
241
+ CursorBackendClient,
242
+ "getAvailableMcpServers" | "checkHttpMcpStatus"
243
+ >;
244
+ identifiers: readonly string[];
245
+ serviceAccountId?: string;
246
+ }): Promise<Map<string, string>> {
247
+ const urls = new Map<string, string>();
248
+ if (args.identifiers.length === 0) {
249
+ return urls;
250
+ }
251
+ try {
252
+ const available = await args.client.getAvailableMcpServers();
253
+ const matched: AvailableMcpServer[] = [];
254
+ const seen = new Set<number>();
255
+ for (const identifier of args.identifiers) {
256
+ const match = uniqueConnectorMatch(available, identifier);
257
+ if (match === undefined || seen.has(match.id)) {
258
+ continue;
259
+ }
260
+ seen.add(match.id);
261
+ matched.push(match);
262
+ }
263
+ if (matched.length === 0) {
264
+ return urls;
265
+ }
266
+ const statuses = await args.client.checkHttpMcpStatus({
267
+ serverIds: matched.map((server) => server.id),
268
+ oauthRedirectUri: mcpOauthRedirectUri(),
269
+ serviceAccountId: args.serviceAccountId,
270
+ });
271
+ const statusById = new Map(
272
+ statuses.map((status) => [status.id, status] as const)
273
+ );
274
+ for (const identifier of args.identifiers) {
275
+ const match = uniqueConnectorMatch(available, identifier);
276
+ if (match === undefined) {
277
+ continue;
278
+ }
279
+ const authUrl = statusById.get(match.id)?.authUrl?.trim();
280
+ if (authUrl !== undefined && authUrl !== "") {
281
+ urls.set(identifier, authUrl);
282
+ }
283
+ }
284
+ return urls;
285
+ } catch {
286
+ return urls;
287
+ }
288
+ }
289
+
207
290
  async function pollUntilAuthorized(args: {
208
291
  backendClient: Pick<CursorBackendClient, "checkHttpMcpStatus">;
209
292
  serverId: number;
@@ -11,7 +11,6 @@
11
11
  */
12
12
 
13
13
  import { z } from "zod";
14
- import type { AgentDeployManifestV1 } from "./deploy-manifest.js";
15
14
  import {
16
15
  MAX_SECRET_VALUE_BYTES,
17
16
  MAX_SECRETS,
@@ -75,12 +74,6 @@ export type DeploySource = {
75
74
  * `X-Agent-Alias-Token` (channels authored with `publicEndpoint()`).
76
75
  */
77
76
  publicRoutePaths?: string[];
78
- /**
79
- * Offline deploy manifest (Wd): derived declarations + `/v1/info`
80
- * payload, extracted at deploy time with no VM. Optional — servers that
81
- * predate manifest storage strip unknown fields.
82
- */
83
- manifest?: AgentDeployManifestV1;
84
77
  };
85
78
 
86
79
  const SLUG_PATTERN = /^[a-z0-9_-]{1,64}$/;
@@ -461,9 +454,6 @@ export class AgentServeDeployClient {
461
454
  : { egressAllowedDomains: args.source.egressAllowedDomains }),
462
455
  // JSON.stringify drops undefined keys.
463
456
  publicRoutePaths: args.source.publicRoutePaths,
464
- ...(args.source.manifest === undefined
465
- ? {}
466
- : { manifest: args.source.manifest }),
467
457
  template: args.template,
468
458
  };
469
459
  const send = (): Promise<unknown> =>