@cursor/july 0.1.68 → 0.1.73

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 (226) hide show
  1. package/AGENTS.md +4 -5
  2. package/README.md +23 -11
  3. package/dist/bin/agent-serve.js +3 -2
  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.CdbcFD8T.js} +1 -1
  32. package/dist/docs/assets/chunks/@localSearchIndexroot.DQad--_z.js +1 -0
  33. package/dist/docs/assets/chunks/{VPLocalSearchBox.CGDTAYHY.js → VPLocalSearchBox.D_NKImM7.js} +1 -1
  34. package/dist/docs/assets/chunks/{theme.lPixh250.js → theme.CMvz7cu9.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_connections.md.Bwswbm6H.js → reference_connections.md.Cu3N-S3Q.js} +18 -11
  40. package/dist/docs/assets/{reference_connections.md.Bwswbm6H.lean.js → reference_connections.md.Cu3N-S3Q.lean.js} +1 -1
  41. package/dist/docs/assets/{reference_sessions.md.DBVFi2Sx.js → reference_sessions.md.B0DdlM-K.js} +1 -1
  42. package/dist/docs/assets/{reference_tools.md.Wmvb7Lob.js → reference_tools.md.DTg_kEsx.js} +1 -1
  43. package/dist/docs/assets/troubleshooting.md.DQ-hYuZT.js +1 -0
  44. package/dist/docs/building-with-agents.html +3 -3
  45. package/dist/docs/concepts.html +5 -5
  46. package/dist/docs/deployment.html +3 -3
  47. package/dist/docs/evals.html +3 -3
  48. package/dist/docs/example-agents/approval-buddy.html +3 -3
  49. package/dist/docs/example-agents/benny.html +3 -3
  50. package/dist/docs/example-agents/bugbot.html +3 -3
  51. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  52. package/dist/docs/example-agents/codeowners-review.html +3 -3
  53. package/dist/docs/example-agents/concierge.html +3 -3
  54. package/dist/docs/example-agents/fsd.html +3 -3
  55. package/dist/docs/example-agents/index.html +3 -3
  56. package/dist/docs/example-agents/knowledge-base.html +3 -3
  57. package/dist/docs/example-agents/oncall.html +3 -3
  58. package/dist/docs/example-agents/security-reviewer.html +3 -3
  59. package/dist/docs/example-agents/slack-agent.html +3 -3
  60. package/dist/docs/example-agents/weather-agent.html +3 -3
  61. package/dist/docs/guides/agent-to-agent.html +3 -3
  62. package/dist/docs/guides/cloud-runtime.html +3 -3
  63. package/dist/docs/guides/convert-automation.html +3 -3
  64. package/dist/docs/guides/github.html +3 -3
  65. package/dist/docs/guides/human-in-the-loop.html +3 -3
  66. package/dist/docs/guides/mcp-oauth.html +5 -5
  67. package/dist/docs/guides/opentelemetry.html +3 -3
  68. package/dist/docs/guides/slack.html +3 -3
  69. package/dist/docs/guides/webhooks.html +3 -3
  70. package/dist/docs/hashmap.json +1 -1
  71. package/dist/docs/hillclimbing.html +3 -3
  72. package/dist/docs/index.html +3 -3
  73. package/dist/docs/quickstart.html +3 -3
  74. package/dist/docs/reference/agent-config.html +5 -6
  75. package/dist/docs/reference/artifacts.html +3 -3
  76. package/dist/docs/reference/channels.html +3 -3
  77. package/dist/docs/reference/cli.html +3 -3
  78. package/dist/docs/reference/connections.html +20 -13
  79. package/dist/docs/reference/hooks.html +3 -3
  80. package/dist/docs/reference/http-api.html +3 -3
  81. package/dist/docs/reference/instructions.html +3 -3
  82. package/dist/docs/reference/playground.html +3 -3
  83. package/dist/docs/reference/project-layout.html +3 -3
  84. package/dist/docs/reference/prompt.html +3 -3
  85. package/dist/docs/reference/schedules.html +3 -3
  86. package/dist/docs/reference/sessions.html +5 -5
  87. package/dist/docs/reference/skills.html +3 -3
  88. package/dist/docs/reference/subagents.html +3 -3
  89. package/dist/docs/reference/tools.html +5 -5
  90. package/dist/docs/scaffolding-agents.html +3 -3
  91. package/dist/docs/storage.html +3 -3
  92. package/dist/docs/templates/agentic-owners.html +3 -3
  93. package/dist/docs/templates/demo.html +3 -3
  94. package/dist/docs/templates/pr-autofixer.html +3 -3
  95. package/dist/docs/templates/security-reviewer.html +3 -3
  96. package/dist/docs/templates/triage.html +3 -3
  97. package/dist/docs/troubleshooting.html +5 -5
  98. package/dist/internal/advertise-tools.d.ts +11 -6
  99. package/dist/internal/advertise-tools.d.ts.map +1 -1
  100. package/dist/internal/advertise-tools.js +63 -30
  101. package/dist/internal/cli-deploy.d.ts.map +1 -1
  102. package/dist/internal/cli-deploy.js +2 -4
  103. package/dist/internal/convert-automation/convert-workflow.d.ts.map +1 -1
  104. package/dist/internal/convert-automation/convert-workflow.js +2 -10
  105. package/dist/internal/cursor/account-mcp.d.ts +29 -3
  106. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  107. package/dist/internal/cursor/account-mcp.js +147 -67
  108. package/dist/internal/cursor/backend-client.d.ts +11 -6
  109. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  110. package/dist/internal/cursor/backend-client.js +32 -10
  111. package/dist/internal/cursor-account-mcp-auth.d.ts +11 -0
  112. package/dist/internal/cursor-account-mcp-auth.d.ts.map +1 -1
  113. package/dist/internal/cursor-account-mcp-auth.js +69 -0
  114. package/dist/internal/deploy-client.d.ts +0 -7
  115. package/dist/internal/deploy-client.d.ts.map +1 -1
  116. package/dist/internal/deploy-client.js +2 -4
  117. package/dist/internal/deploy-manifest.d.ts +15 -24
  118. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  119. package/dist/internal/deploy-manifest.js +38 -27
  120. package/dist/internal/deployment-scm-event-stream.d.ts +4 -1
  121. package/dist/internal/deployment-scm-event-stream.d.ts.map +1 -1
  122. package/dist/internal/deployment-scm-event-stream.js +15 -1
  123. package/dist/internal/discovery.d.ts.map +1 -1
  124. package/dist/internal/discovery.js +42 -10
  125. package/dist/internal/init-project.d.ts.map +1 -1
  126. package/dist/internal/init-project.js +7 -106
  127. package/dist/internal/init-scaffold.d.ts +8 -0
  128. package/dist/internal/init-scaffold.d.ts.map +1 -0
  129. package/dist/internal/init-scaffold.js +168 -0
  130. package/dist/internal/inline-stdio-mcp.d.ts +2 -0
  131. package/dist/internal/inline-stdio-mcp.d.ts.map +1 -1
  132. package/dist/internal/inline-stdio-mcp.js +10 -8
  133. package/dist/internal/json-schema.d.ts +7 -0
  134. package/dist/internal/json-schema.d.ts.map +1 -1
  135. package/dist/internal/json-schema.js +18 -3
  136. package/dist/internal/mcp-endpoint.js +7 -7
  137. package/dist/internal/mcp-host.js +4 -2
  138. package/dist/internal/mcp-oauth.d.ts +22 -0
  139. package/dist/internal/mcp-oauth.d.ts.map +1 -1
  140. package/dist/internal/mcp-oauth.js +40 -1
  141. package/dist/internal/package-inline-stdio.d.ts.map +1 -1
  142. package/dist/internal/package-inline-stdio.js +5 -5
  143. package/dist/internal/review-comments.d.ts +97 -0
  144. package/dist/internal/review-comments.d.ts.map +1 -1
  145. package/dist/internal/review-comments.js +151 -0
  146. package/dist/internal/sanitize-mcp-tool-name.d.ts +20 -0
  147. package/dist/internal/sanitize-mcp-tool-name.d.ts.map +1 -0
  148. package/dist/internal/sanitize-mcp-tool-name.js +30 -0
  149. package/dist/internal/session-engine.d.ts.map +1 -1
  150. package/dist/internal/session-engine.js +4 -6
  151. package/dist/internal/shallow-clone.d.ts +94 -39
  152. package/dist/internal/shallow-clone.d.ts.map +1 -1
  153. package/dist/internal/shallow-clone.js +188 -68
  154. package/dist/internal/terminal-style.d.ts +18 -0
  155. package/dist/internal/terminal-style.d.ts.map +1 -1
  156. package/dist/internal/terminal-style.js +46 -1
  157. package/dist/internal/workspace.d.ts +2 -0
  158. package/dist/internal/workspace.d.ts.map +1 -1
  159. package/dist/internal/workspace.js +5 -0
  160. package/dist/playground/assets/index-77uhu5Kr.css +1 -0
  161. package/dist/playground/assets/{index-yxs1heea.js → index-D4buox4m.js} +42 -42
  162. package/dist/playground/index.html +2 -2
  163. package/dist/tools.d.ts +7 -0
  164. package/dist/tools.d.ts.map +1 -1
  165. package/dist/types.d.ts +28 -3
  166. package/dist/types.d.ts.map +1 -1
  167. package/docs/concepts.md +4 -4
  168. package/docs/guides/mcp-oauth.md +3 -1
  169. package/docs/reference/agent-config.md +11 -12
  170. package/docs/reference/connections.md +44 -26
  171. package/docs/reference/sessions.md +2 -2
  172. package/docs/reference/tools.md +1 -1
  173. package/docs/troubleshooting.md +3 -2
  174. package/package.json +4 -1
  175. package/skills/create-agent/SKILL.md +12 -9
  176. package/skills/debug/SKILL.md +2 -1
  177. package/skills/framework-map/SKILL.md +6 -6
  178. package/skills/mcp-auth/SKILL.md +8 -6
  179. package/src/bin/agent-serve.ts +3 -2
  180. package/src/channels/github/api.ts +6 -1
  181. package/src/channels/github/binding.ts +21 -0
  182. package/src/channels/github/checks.ts +179 -0
  183. package/src/channels/github/cursor-account.ts +9 -6
  184. package/src/channels/github/index.ts +1 -0
  185. package/src/channels/github/types.ts +8 -0
  186. package/src/channels/slack/manifest.ts +8 -10
  187. package/src/channels/slack/types.ts +2 -2
  188. package/src/connections.ts +17 -5
  189. package/src/internal/advertise-tools.ts +72 -23
  190. package/src/internal/cli-deploy.ts +4 -15
  191. package/src/internal/convert-automation/convert-workflow.ts +2 -11
  192. package/src/internal/cursor/account-mcp.ts +208 -73
  193. package/src/internal/cursor/backend-client.ts +45 -11
  194. package/src/internal/cursor-account-mcp-auth.ts +83 -0
  195. package/src/internal/deploy-client.ts +0 -10
  196. package/src/internal/deploy-manifest.ts +33 -39
  197. package/src/internal/deployment-scm-event-stream.ts +17 -1
  198. package/src/internal/discovery.ts +47 -3
  199. package/src/internal/init-project.ts +7 -133
  200. package/src/internal/init-scaffold.ts +176 -0
  201. package/src/internal/inline-stdio-mcp.ts +13 -0
  202. package/src/internal/json-schema.ts +24 -3
  203. package/src/internal/mcp-endpoint.ts +6 -0
  204. package/src/internal/mcp-host.ts +4 -0
  205. package/src/internal/mcp-oauth.ts +55 -1
  206. package/src/internal/package-inline-stdio.ts +5 -1
  207. package/src/internal/review-comments.ts +245 -0
  208. package/src/internal/sanitize-mcp-tool-name.ts +33 -0
  209. package/src/internal/session-engine.ts +7 -0
  210. package/src/internal/shallow-clone.ts +293 -105
  211. package/src/internal/terminal-style.ts +67 -1
  212. package/src/internal/workspace.ts +6 -0
  213. package/src/tools.ts +7 -0
  214. package/src/types.ts +32 -4
  215. package/templates/agentic-owners/agent/agent.ts +3 -0
  216. package/templates/demo/agent/agent.ts +3 -0
  217. package/templates/security-reviewer/agent/agent.ts +3 -0
  218. package/templates/triage/agent/agent.ts +3 -0
  219. package/dist/docs/assets/chunks/@localSearchIndexroot.DvUR3lv9.js +0 -1
  220. package/dist/docs/assets/troubleshooting.md.CuVztCKs.js +0 -1
  221. package/dist/playground/assets/index-CoL7xCJl.css +0 -1
  222. /package/dist/docs/assets/{concepts.md.CqOsxbMU.lean.js → concepts.md.BXAm6G-C.lean.js} +0 -0
  223. /package/dist/docs/assets/{guides_mcp-oauth.md.C7G7IykG.lean.js → guides_mcp-oauth.md.CzEB6RaG.lean.js} +0 -0
  224. /package/dist/docs/assets/{reference_sessions.md.DBVFi2Sx.lean.js → reference_sessions.md.B0DdlM-K.lean.js} +0 -0
  225. /package/dist/docs/assets/{reference_tools.md.Wmvb7Lob.lean.js → reference_tools.md.DTg_kEsx.lean.js} +0 -0
  226. /package/dist/docs/assets/{troubleshooting.md.CuVztCKs.lean.js → troubleshooting.md.DQ-hYuZT.lean.js} +0 -0
@@ -16,9 +16,13 @@
16
16
  * auth-less connections go through the shared `host.mcp` registry, which
17
17
  * also covers serve-resolved transports (Cursor account bridges, peers).
18
18
  *
19
- * Everything here fails closed: a listing failure, an invalid tool name,
20
- * or a name collision throws, and the engine fails the turn rather than
21
- * running it with a silently shrunken toolset.
19
+ * Advertised names follow the Cursor-account MCP bridge
20
+ * (`packages/agent-serve/src/internal/cursor/account-mcp.ts`): slug
21
+ * `[^a-zA-Z0-9_-]` to `_`, collision suffixes `_2` / `_3`, and call the
22
+ * original advertised name. That is the same mapping `packages/agent`
23
+ * `normalizeMcpToolName` uses for IDE/Sand MCP tools. A listing failure
24
+ * still fails the turn rather than running it with a silently shrunken
25
+ * toolset.
22
26
  */
23
27
 
24
28
  import type {
@@ -33,6 +37,10 @@ import type {
33
37
  } from "../types.js";
34
38
  import { brandDefinition } from "./brand.js";
35
39
  import type { HostMcpOneOff } from "./mcp-host.js";
40
+ import {
41
+ sanitizeMcpToolName,
42
+ suffixMcpToolName,
43
+ } from "./sanitize-mcp-tool-name.js";
36
44
 
37
45
  /**
38
46
  * What the resolver needs from the host MCP registry: the shared cached
@@ -50,12 +58,12 @@ export interface AdvertiseHostMcp {
50
58
  }
51
59
 
52
60
  /**
53
- * The rule advertised tool names must match — the same shape discovery
54
- * enforces for authored tool filenames (its `NAME_PATTERN`), since both
55
- * become the tool name the model sees.
61
+ * Model-facing advertised names after {@link sanitizeMcpToolName} — the
62
+ * same MCP-client shape the Cursor-account bridge documents
63
+ * (`^[a-zA-Z0-9_-]{1,128}$`). Authored `agent/tools/` filenames stay on
64
+ * discovery's stricter `NAME_PATTERN` (must start alphanumeric).
56
65
  */
57
- export const ADVERTISED_TOOL_NAME_PATTERN: RegExp =
58
- /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
66
+ export const ADVERTISED_TOOL_NAME_PATTERN: RegExp = /^[A-Za-z0-9_-]{1,128}$/;
59
67
 
60
68
  const ANY_OBJECT_SCHEMA: JsonSchemaObject = {
61
69
  type: "object",
@@ -107,9 +115,10 @@ export async function resolveAdvertisedTools(args: {
107
115
  { cause: error }
108
116
  );
109
117
  }
110
- for (const info of listed) {
111
- resolved.push(toDiscoveredTool(connection, info, claimed, source));
112
- claimed.add(info.name);
118
+ const exposed = exposeListedTools(listed, claimed);
119
+ for (const { info, exposedName } of exposed) {
120
+ resolved.push(toDiscoveredTool(connection, info, exposedName, source));
121
+ claimed.add(exposedName);
113
122
  }
114
123
  }
115
124
  return resolved;
@@ -154,30 +163,69 @@ export async function sourceForConnection(
154
163
  });
155
164
  }
156
165
 
166
+ /**
167
+ * Same catalog rule as the Cursor-account bridge: slug illegal characters,
168
+ * then `_2` / `_3` … for colliders (including authored tools already in
169
+ * `claimed`). Suffix assignment is stable for a colliding group: sort by
170
+ * original advertised name, independent of `listTools` order.
171
+ */
172
+ function exposeListedTools(
173
+ listed: HostMcpToolInfo[],
174
+ claimed: ReadonlySet<string>
175
+ ): Array<{ info: HostMcpToolInfo; exposedName: string }> {
176
+ const byBase = new Map<string, HostMcpToolInfo[]>();
177
+ for (const info of listed) {
178
+ const base = sanitizeMcpToolName(info.name);
179
+ const group = byBase.get(base);
180
+ if (group === undefined) {
181
+ byBase.set(base, [info]);
182
+ } else {
183
+ group.push(info);
184
+ }
185
+ }
186
+ const taken = new Set(claimed);
187
+ const exposedNameByInfo = new Map<HostMcpToolInfo, string>();
188
+ const sortedBases = [...byBase.keys()].sort((left, right) =>
189
+ left < right ? -1 : left > right ? 1 : 0
190
+ );
191
+ for (const base of sortedBases) {
192
+ const group = byBase.get(base)!;
193
+ group.sort((left, right) =>
194
+ left.name < right.name ? -1 : left.name > right.name ? 1 : 0
195
+ );
196
+ group.forEach((info, index) => {
197
+ let suffixIndex = index;
198
+ let exposedName = suffixMcpToolName(base, suffixIndex);
199
+ while (taken.has(exposedName)) {
200
+ suffixIndex++;
201
+ exposedName = suffixMcpToolName(base, suffixIndex);
202
+ }
203
+ taken.add(exposedName);
204
+ exposedNameByInfo.set(info, exposedName);
205
+ });
206
+ }
207
+ return listed.map((info) => ({
208
+ info,
209
+ exposedName: exposedNameByInfo.get(info)!,
210
+ }));
211
+ }
212
+
157
213
  function toDiscoveredTool(
158
214
  connection: DiscoveredConnection,
159
215
  info: HostMcpToolInfo,
160
- claimed: ReadonlySet<string>,
216
+ exposedName: string,
161
217
  source: AdvertisedToolSource
162
218
  ): DiscoveredTool {
163
- if (!ADVERTISED_TOOL_NAME_PATTERN.test(info.name)) {
164
- throw new Error(
165
- `Advertised connection "${connection.name}" serves tool name "${info.name}", which must match ${ADVERTISED_TOOL_NAME_PATTERN} (it becomes the tool name the model sees).`
166
- );
167
- }
168
- if (claimed.has(info.name)) {
169
- throw new Error(
170
- `Advertised connection "${connection.name}" serves tool "${info.name}", which collides with an authored agent/tools/ tool or another advertised connection.`
171
- );
172
- }
173
219
  const description =
174
220
  info.description === undefined || info.description.trim() === ""
175
221
  ? `${info.name} — MCP tool on the "${connection.name}" connection, forwarded 1:1.`
176
222
  : info.description;
177
223
  const inputSchema = info.inputSchema ?? ANY_OBJECT_SCHEMA;
224
+ const outputSchema = info.outputSchema;
178
225
  const definition: ToolDefinition = brandDefinition("tool", {
179
226
  description,
180
227
  inputSchema,
228
+ ...(outputSchema === undefined ? {} : { outputSchema }),
181
229
  execute: async (input: Record<string, unknown>) =>
182
230
  formatMcpToolResult(
183
231
  connection.name,
@@ -186,9 +234,10 @@ function toDiscoveredTool(
186
234
  ),
187
235
  } as unknown as ToolDefinition);
188
236
  return {
189
- name: info.name,
237
+ name: exposedName,
190
238
  description,
191
239
  inputSchema,
240
+ ...(outputSchema === undefined ? {} : { outputSchema }),
192
241
  execution: "server",
193
242
  needsApproval: false,
194
243
  definition,
@@ -39,10 +39,6 @@ import {
39
39
  TERMINAL_DEPLOY_STATUSES,
40
40
  validateDeploymentSlug,
41
41
  } from "./deploy-client.js";
42
- import {
43
- type AgentDeployManifestV1,
44
- buildAgentDeployManifest,
45
- } from "./deploy-manifest.js";
46
42
  import { type GitRunner, inferDeployGitSource } from "./deploy-source.js";
47
43
  import {
48
44
  type ChildAgentDir,
@@ -666,9 +662,10 @@ async function deployOne(
666
662
  ...(egressAllowedDomains.length === 0 ? {} : { egressAllowedDomains }),
667
663
  publicRoutePaths:
668
664
  publicRoutePaths.length === 0 ? undefined : publicRoutePaths,
669
- // Offline metadata extraction (Wd): the CP persists this before any VM
670
- // starts. Directories without an agent project send no manifest.
671
- ...(manifest === undefined ? {} : { manifest: manifest.deployManifest }),
665
+ // Deliberately NO manifest here: the control plane captures it from the
666
+ // provisioned engine (`GET /v1/manifest` after the health probe), which
667
+ // is the single write path for deploy manifests. `july manifest` remains
668
+ // available for local visibility only.
672
669
  };
673
670
 
674
671
  let accepted: DeployAccepted;
@@ -864,13 +861,6 @@ interface DeployManifest {
864
861
  * (only `/v1/channels/…` subtrees can be public); surfaced as a warning.
865
862
  */
866
863
  httpChannelPublicEndpoint: boolean;
867
- /**
868
- * The offline deploy manifest (Wd): derived event declarations, channel
869
- * kinds, egress, routes, and the full `/v1/info` payload. Attached to the
870
- * deploy request so the control plane stores metadata before any VM
871
- * starts; servers that predate manifest storage strip the field.
872
- */
873
- deployManifest: AgentDeployManifestV1;
874
864
  }
875
865
 
876
866
  /**
@@ -895,7 +885,6 @@ async function readDeployManifest(dir: string): Promise<DeployManifest> {
895
885
  httpChannelPublicEndpoint: (project.httpChannel.auth ?? []).some(
896
886
  isPublicEndpointPolicy
897
887
  ),
898
- deployManifest: buildAgentDeployManifest(project),
899
888
  };
900
889
  }
901
890
 
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { CLI_COMMAND_NAME, PACKAGE_NAME } from "../distribution.js";
10
+ import { MEMORY_TS } from "../init-scaffold.js";
10
11
  import { pascalCase, SlugRegistry, slugify } from "./slug.js";
11
12
  import type {
12
13
  ActionJson,
@@ -128,7 +129,7 @@ export function convertWorkflow(
128
129
  hasSlackAction && emitSlackAccountConnection(automation, out);
129
130
 
130
131
  if (workflow.memoryEnabled === true) {
131
- out.files.push({ path: "agent/hooks/memory.ts", contents: MEMORY_HOOK });
132
+ out.files.push({ path: "agent/hooks/memory.ts", contents: MEMORY_TS });
132
133
  }
133
134
 
134
135
  out.setupSteps.push(
@@ -1410,13 +1411,3 @@ function jsonComment(value: unknown): string[] {
1410
1411
  function dedupe(values: string[]): string[] {
1411
1412
  return [...new Set(values)];
1412
1413
  }
1413
-
1414
- /** Memory hook contents — identical to the `initProject` scaffold. */
1415
- const MEMORY_HOOK = `// Journals every turn (timestamp, session, user message, result) so
1416
- // future sessions can recall past work. The journal is shared across every
1417
- // session — and every user — of this agent. Pass your own MemoryBackend to
1418
- // change where memories live, or delete this file to opt out.
1419
- import { memoryHook } from "${PACKAGE_NAME}/memory";
1420
-
1421
- export default memoryHook();
1422
- `;
@@ -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: …)`). */