@cursor/july 0.1.62 → 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.
- package/AGENTS.md +4 -5
- package/README.md +23 -11
- package/dist/bin/agent-serve.js +11 -2
- package/dist/channels/github/api.d.ts +2 -0
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +2 -1
- package/dist/channels/github/binding.d.ts +7 -0
- package/dist/channels/github/binding.d.ts.map +1 -1
- package/dist/channels/github/binding.js +4 -0
- package/dist/channels/github/checks.d.ts +99 -0
- package/dist/channels/github/checks.d.ts.map +1 -0
- package/dist/channels/github/checks.js +108 -0
- package/dist/channels/github/cursor-account.d.ts +4 -4
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +7 -5
- package/dist/channels/github/index.d.ts +1 -0
- package/dist/channels/github/index.d.ts.map +1 -1
- package/dist/channels/github/index.js +1 -0
- package/dist/channels/github/types.d.ts +8 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.d.ts +2 -1
- package/dist/channels/slack/channel-watch.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.js +7 -3
- package/dist/channels/slack/dispatch.d.ts +9 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +41 -21
- package/dist/channels/slack/manifest.d.ts +2 -2
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +14 -11
- package/dist/channels/slack/nudge-store.d.ts +16 -0
- package/dist/channels/slack/nudge-store.d.ts.map +1 -1
- package/dist/channels/slack/nudge-store.js +53 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -1
- package/dist/channels/slack/types.d.ts +24 -7
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/connections.d.ts +17 -5
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +17 -5
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.B-biogkN.js → app.CdbcFD8T.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DQad--_z.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DZqKtFZS.js → VPLocalSearchBox.D_NKImM7.js} +1 -1
- package/dist/docs/assets/chunks/{theme.BwC5zP2r.js → theme.CMvz7cu9.js} +2 -2
- package/dist/docs/assets/{concepts.md.CqOsxbMU.js → concepts.md.BXAm6G-C.js} +1 -1
- package/dist/docs/assets/{guides_mcp-oauth.md.C7G7IykG.js → guides_mcp-oauth.md.CzEB6RaG.js} +1 -1
- package/dist/docs/assets/{reference_agent-config.md.DHkkvcEZ.js → reference_agent-config.md.BZnEKfEc.js} +3 -4
- package/dist/docs/assets/{reference_agent-config.md.DHkkvcEZ.lean.js → reference_agent-config.md.BZnEKfEc.lean.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.CSQiNzre.js → reference_connections.md.Cu3N-S3Q.js} +26 -9
- package/dist/docs/assets/reference_connections.md.Cu3N-S3Q.lean.js +1 -0
- package/dist/docs/assets/{reference_sessions.md.DBVFi2Sx.js → reference_sessions.md.B0DdlM-K.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.kJfJAP2m.js → reference_tools.md.DTg_kEsx.js} +2 -2
- package/dist/docs/assets/{reference_tools.md.kJfJAP2m.lean.js → reference_tools.md.DTg_kEsx.lean.js} +1 -1
- package/dist/docs/assets/troubleshooting.md.DQ-hYuZT.js +1 -0
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +5 -5
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/convert-automation.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +5 -5
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +5 -6
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +28 -11
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +5 -5
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +5 -5
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/templates/agentic-owners.html +3 -3
- package/dist/docs/templates/demo.html +3 -3
- package/dist/docs/templates/pr-autofixer.html +3 -3
- package/dist/docs/templates/security-reviewer.html +3 -3
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/files-backends/agent-store-presigned-url.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/advertise-tools.d.ts +79 -0
- package/dist/internal/advertise-tools.d.ts.map +1 -0
- package/dist/internal/advertise-tools.js +185 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +7 -1
- package/dist/internal/cli-manifest.d.ts +19 -0
- package/dist/internal/cli-manifest.d.ts.map +1 -0
- package/dist/internal/cli-manifest.js +49 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +9 -1
- package/dist/internal/convert-automation/convert-workflow.d.ts.map +1 -1
- package/dist/internal/convert-automation/convert-workflow.js +2 -10
- package/dist/internal/cursor/account-mcp.d.ts +29 -3
- package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
- package/dist/internal/cursor/account-mcp.js +147 -67
- package/dist/internal/cursor/backend-client.d.ts +11 -6
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +32 -10
- package/dist/internal/cursor-account-mcp-auth.d.ts +11 -0
- package/dist/internal/cursor-account-mcp-auth.d.ts.map +1 -1
- package/dist/internal/cursor-account-mcp-auth.js +69 -0
- package/dist/internal/cursor-agent-template.d.ts +9 -0
- package/dist/internal/cursor-agent-template.d.ts.map +1 -0
- package/dist/internal/cursor-agent-template.js +37 -0
- package/dist/internal/deploy-client.d.ts +1 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +18 -2
- package/dist/internal/deploy-manifest.d.ts +76 -0
- package/dist/internal/deploy-manifest.d.ts.map +1 -0
- package/dist/internal/deploy-manifest.js +117 -0
- package/dist/internal/deployment-scm-event-stream.d.ts +116 -0
- package/dist/internal/deployment-scm-event-stream.d.ts.map +1 -0
- package/dist/internal/deployment-scm-event-stream.js +474 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +111 -10
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +12 -123
- package/dist/internal/init-scaffold.d.ts +8 -0
- package/dist/internal/init-scaffold.d.ts.map +1 -0
- package/dist/internal/init-scaffold.js +168 -0
- package/dist/internal/inline-stdio-mcp.d.ts +2 -0
- package/dist/internal/inline-stdio-mcp.d.ts.map +1 -1
- package/dist/internal/inline-stdio-mcp.js +10 -8
- package/dist/internal/json-schema.d.ts +7 -0
- package/dist/internal/json-schema.d.ts.map +1 -1
- package/dist/internal/json-schema.js +18 -3
- package/dist/internal/mcp-endpoint.js +7 -7
- package/dist/internal/mcp-host.d.ts +18 -0
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +43 -5
- package/dist/internal/mcp-oauth.d.ts +22 -0
- package/dist/internal/mcp-oauth.d.ts.map +1 -1
- package/dist/internal/mcp-oauth.js +40 -1
- package/dist/internal/package-inline-stdio.d.ts.map +1 -1
- package/dist/internal/package-inline-stdio.js +5 -5
- package/dist/internal/review-comments.d.ts +97 -0
- package/dist/internal/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +151 -0
- package/dist/internal/sanitize-mcp-tool-name.d.ts +20 -0
- package/dist/internal/sanitize-mcp-tool-name.d.ts.map +1 -0
- package/dist/internal/sanitize-mcp-tool-name.js +30 -0
- package/dist/internal/sdk-runner.d.ts +1 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +36 -13
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +18 -0
- package/dist/internal/session-engine.d.ts +66 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +428 -110
- package/dist/internal/shallow-clone.d.ts +170 -0
- package/dist/internal/shallow-clone.d.ts.map +1 -0
- package/dist/internal/shallow-clone.js +412 -0
- package/dist/internal/slack-provision-client.d.ts +2 -0
- package/dist/internal/slack-provision-client.d.ts.map +1 -1
- package/dist/internal/slack-provision-client.js +1 -0
- package/dist/internal/terminal-style.d.ts +18 -0
- package/dist/internal/terminal-style.d.ts.map +1 -1
- package/dist/internal/terminal-style.js +46 -1
- package/dist/internal/turn-lease-store.d.ts +44 -0
- package/dist/internal/turn-lease-store.d.ts.map +1 -0
- package/dist/internal/turn-lease-store.js +139 -0
- package/dist/internal/workspace.d.ts +14 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +33 -0
- package/dist/playground/assets/index-77uhu5Kr.css +1 -0
- package/dist/playground/assets/index-D4buox4m.js +85 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-protocol.d.ts +122 -0
- package/dist/storage-protocol.d.ts.map +1 -0
- package/dist/storage-protocol.js +173 -0
- package/dist/storage.d.ts +2 -72
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +2 -82
- package/dist/tools.d.ts +7 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/types.d.ts +141 -6
- package/dist/types.d.ts.map +1 -1
- package/docs/concepts.md +4 -4
- package/docs/guides/mcp-oauth.md +3 -1
- package/docs/reference/agent-config.md +11 -12
- package/docs/reference/connections.md +108 -10
- package/docs/reference/sessions.md +2 -2
- package/docs/reference/tools.md +14 -0
- package/docs/troubleshooting.md +3 -2
- package/package.json +7 -2
- package/skills/create-agent/SKILL.md +12 -9
- package/skills/debug/SKILL.md +2 -1
- package/skills/framework-map/SKILL.md +6 -6
- package/skills/mcp-auth/SKILL.md +8 -6
- package/src/bin/agent-serve.ts +12 -2
- package/src/channels/github/api.ts +6 -1
- package/src/channels/github/binding.ts +21 -0
- package/src/channels/github/checks.ts +179 -0
- package/src/channels/github/cursor-account.ts +9 -6
- package/src/channels/github/index.ts +1 -0
- package/src/channels/github/types.ts +8 -0
- package/src/channels/slack/channel-watch.ts +6 -3
- package/src/channels/slack/dispatch.ts +40 -3
- package/src/channels/slack/manifest.ts +9 -10
- package/src/channels/slack/nudge-store.ts +56 -0
- package/src/channels/slack/slack-channel.ts +3 -0
- package/src/channels/slack/types.ts +24 -7
- package/src/connections.ts +17 -5
- package/src/index.ts +5 -0
- package/src/internal/advertise-tools.ts +278 -0
- package/src/internal/cli-deploy.ts +11 -1
- package/src/internal/cli-manifest.ts +63 -0
- package/src/internal/cli-slack.ts +10 -1
- package/src/internal/convert-automation/convert-workflow.ts +2 -11
- package/src/internal/cursor/account-mcp.ts +208 -73
- package/src/internal/cursor/backend-client.ts +45 -11
- package/src/internal/cursor-account-mcp-auth.ts +83 -0
- package/src/internal/cursor-agent-template.ts +46 -0
- package/src/internal/deploy-client.ts +25 -6
- package/src/internal/deploy-manifest.ts +161 -0
- package/src/internal/deployment-scm-event-stream.ts +593 -0
- package/src/internal/discovery.ts +132 -3
- package/src/internal/init-project.ts +18 -153
- package/src/internal/init-scaffold.ts +176 -0
- package/src/internal/inline-stdio-mcp.ts +13 -0
- package/src/internal/json-schema.ts +24 -3
- package/src/internal/mcp-endpoint.ts +6 -0
- package/src/internal/mcp-host.ts +70 -9
- package/src/internal/mcp-oauth.ts +55 -1
- package/src/internal/package-inline-stdio.ts +5 -1
- package/src/internal/review-comments.ts +245 -0
- package/src/internal/sanitize-mcp-tool-name.ts +33 -0
- package/src/internal/sdk-runner.ts +50 -11
- package/src/internal/server.ts +19 -0
- package/src/internal/session-engine.ts +392 -19
- package/src/internal/shallow-clone.ts +595 -0
- package/src/internal/slack-provision-client.ts +3 -0
- package/src/internal/terminal-style.ts +67 -1
- package/src/internal/turn-lease-store.ts +147 -0
- package/src/internal/workspace.ts +33 -0
- package/src/storage-protocol.ts +247 -0
- package/src/storage.ts +16 -110
- package/src/tools.ts +7 -0
- package/src/types.ts +149 -7
- package/templates/agentic-owners/agent/agent.ts +3 -0
- package/templates/demo/agent/agent.ts +3 -0
- package/templates/pr-autofixer/agent/lib/drive-schemas.ts +4 -1
- package/templates/security-reviewer/agent/agent.ts +3 -0
- package/templates/triage/agent/agent.ts +3 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.5ozYNf74.js +0 -1
- package/dist/docs/assets/reference_connections.md.CSQiNzre.lean.js +0 -1
- package/dist/docs/assets/troubleshooting.md.CuVztCKs.js +0 -1
- package/dist/playground/assets/index-B4rcOgeP.css +0 -1
- package/dist/playground/assets/index-DXcY42Jv.js +0 -85
- /package/dist/docs/assets/{concepts.md.CqOsxbMU.lean.js → concepts.md.BXAm6G-C.lean.js} +0 -0
- /package/dist/docs/assets/{guides_mcp-oauth.md.C7G7IykG.lean.js → guides_mcp-oauth.md.CzEB6RaG.lean.js} +0 -0
- /package/dist/docs/assets/{reference_sessions.md.DBVFi2Sx.lean.js → reference_sessions.md.B0DdlM-K.lean.js} +0 -0
- /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
|
-
|
|
42
|
-
|
|
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.
|
|
217
|
-
*
|
|
218
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
499
|
+
let exposedName = suffixMcpToolName(base, suffixIndex);
|
|
481
500
|
while (byExposedName.has(exposedName)) {
|
|
482
501
|
suffixIndex++;
|
|
483
|
-
exposedName =
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
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
|
|
517
|
-
|
|
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
|
|
521
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
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
|
|
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;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
const MAX = 64;
|
|
5
|
+
const TEMPLATE_NAMES = [
|
|
6
|
+
"agentic-owners",
|
|
7
|
+
"demo",
|
|
8
|
+
"pr-autofixer",
|
|
9
|
+
"security-reviewer",
|
|
10
|
+
"triage-linear",
|
|
11
|
+
"triage-jira",
|
|
12
|
+
] as const;
|
|
13
|
+
export type TemplateName = (typeof TEMPLATE_NAMES)[number];
|
|
14
|
+
const NAMES = new Set<string>(TEMPLATE_NAMES);
|
|
15
|
+
|
|
16
|
+
export function canonicalTemplateName(name: string): string {
|
|
17
|
+
return name === "triage" ? "triage-linear" : name;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isTemplateName(name: string): name is TemplateName {
|
|
21
|
+
return NAMES.has(name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Raw scaffold marker. Validation depends on the consuming boundary. */
|
|
25
|
+
export function readCursorAgentTemplate(rootDir: string): string | undefined {
|
|
26
|
+
try {
|
|
27
|
+
const pkg = JSON.parse(
|
|
28
|
+
readFileSync(join(rootDir, "package.json"), "utf8")
|
|
29
|
+
) as {
|
|
30
|
+
cursorAgentTemplate?: unknown;
|
|
31
|
+
};
|
|
32
|
+
const stamp = pkg.cursorAgentTemplate;
|
|
33
|
+
return typeof stamp === "string" ? stamp : undefined;
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function readDeployTemplate(rootDir: string): string | undefined {
|
|
40
|
+
const stamp = readCursorAgentTemplate(rootDir);
|
|
41
|
+
if (stamp === undefined || stamp.length === 0 || stamp.length > MAX) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const name = canonicalTemplateName(stamp);
|
|
45
|
+
return isTemplateName(name) ? name : undefined;
|
|
46
|
+
}
|
|
@@ -422,6 +422,7 @@ export class AgentServeDeployClient {
|
|
|
422
422
|
teamId: number;
|
|
423
423
|
slug: string;
|
|
424
424
|
source: DeploySource;
|
|
425
|
+
template?: string;
|
|
425
426
|
}): Promise<DeployAccepted> {
|
|
426
427
|
const slugError = validateDeploymentSlug(args.slug);
|
|
427
428
|
if (slugError !== undefined) {
|
|
@@ -453,13 +454,31 @@ export class AgentServeDeployClient {
|
|
|
453
454
|
: { egressAllowedDomains: args.source.egressAllowedDomains }),
|
|
454
455
|
// JSON.stringify drops undefined keys.
|
|
455
456
|
publicRoutePaths: args.source.publicRoutePaths,
|
|
457
|
+
template: args.template,
|
|
456
458
|
};
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
459
|
+
const send = (): Promise<unknown> =>
|
|
460
|
+
this.request({
|
|
461
|
+
method: "POST",
|
|
462
|
+
path: "/internal/agent-serve/deployments",
|
|
463
|
+
body,
|
|
464
|
+
context: { verb: "deploy", slug: args.slug },
|
|
465
|
+
});
|
|
466
|
+
let raw: unknown;
|
|
467
|
+
try {
|
|
468
|
+
raw = await send();
|
|
469
|
+
} catch (error) {
|
|
470
|
+
const unsupportedTemplate =
|
|
471
|
+
args.template !== undefined &&
|
|
472
|
+
error instanceof DeployApiError &&
|
|
473
|
+
error.status === 400 &&
|
|
474
|
+
error.message.toLowerCase().includes("template") &&
|
|
475
|
+
/unrecognized|unknown/i.test(error.message);
|
|
476
|
+
if (!unsupportedTemplate) {
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
delete body.template;
|
|
480
|
+
raw = await send();
|
|
481
|
+
}
|
|
463
482
|
return deployAcceptedSchema.parse(raw);
|
|
464
483
|
}
|
|
465
484
|
|