@zackbart/connecta 0.28.1 → 0.30.0
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/CHANGELOG.md +855 -5
- package/README.md +125 -116
- package/bin/connecta.mjs +121 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/argument-redaction.js +230 -0
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1584 -1580
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +93 -71
- package/dist/auth-elicitation.js +288 -0
- package/dist/authorization-handoff.js +111 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +409 -225
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +633 -0
- package/dist/config.js +508 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +87 -38
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1774 -320
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +128 -0
- package/dist/downstream-input.js +325 -0
- package/dist/errors.d.ts +132 -31
- package/dist/errors.js +175 -87
- package/dist/execute.js +798 -318
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +30 -13
- package/dist/executors/quickjs-child.js +15 -9
- package/dist/executors/quickjs-protocol.js +5 -6
- package/dist/executors/quickjs-runtime.js +152 -59
- package/dist/executors/quickjs.js +49 -39
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +238 -0
- package/dist/guest-types.d.ts +218 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +24 -256
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +262 -79
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +61 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +419 -272
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +194 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-result.js +5 -0
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +60 -19
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/_shared/request-token.js +83 -0
- package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/infisical/index.d.ts +7 -0
- package/dist/providers/infisical/index.js +713 -0
- package/dist/providers/infisical/skill.generated.js +8 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +110 -171
- package/dist/registry.js +588 -927
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +103 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +365 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +110 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +93 -0
- package/dist/sent-secrets.js +1013 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +416 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +181 -0
- package/dist/storage/keys.js +443 -0
- package/dist/storage/memory.js +16 -4
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +543 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +336 -56
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +146 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +435 -0
- package/documentation/README.md +63 -0
- package/documentation/architecture.md +696 -231
- package/documentation/auth.md +1069 -546
- package/documentation/code-mode.md +662 -356
- package/documentation/deploying.md +146 -0
- package/documentation/integrating.md +159 -0
- package/documentation/meta-tools.md +570 -216
- package/documentation/operating.md +135 -0
- package/documentation/operator-tests.md +97 -0
- package/documentation/operator-ui.md +132 -0
- package/documentation/provider-migration-0.29.md +65 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +293 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +126 -54
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +140 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/basecamp.js +0 -445
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
|
@@ -0,0 +1,1038 @@
|
|
|
1
|
+
import { skill } from "./skill.generated.js";
|
|
2
|
+
import { HOSTED_REST_OPERATIONS, UNCOVERED_REST_OPERATIONS } from "./mcp-ownership.js";
|
|
3
|
+
/**
|
|
4
|
+
* No `@vercel/sdk` on purpose — not a dependency and not an optional peer.
|
|
5
|
+
* Direct fetch keeps the root Workers-safe, avoids shipping the SDK's generated
|
|
6
|
+
* model graph, and lets the reviewed named operations and the REST hatches share
|
|
7
|
+
* one guarded transport. The trade is API drift, handled explicitly rather than
|
|
8
|
+
* by the SDK's version bumps: `src/providers/vercel/drift.json` records the
|
|
9
|
+
* method, versioned path, spec revision, and request/response digest for every
|
|
10
|
+
* fixed endpoint, and `npm run providers:check -- --provider vercel` compares
|
|
11
|
+
* those rows with Vercel's published OpenAPI document at
|
|
12
|
+
* https://openapi.vercel.sh/ without needing a credential.
|
|
13
|
+
*/
|
|
14
|
+
import { apiConnector as api } from "../../connectors/api-connector.js";
|
|
15
|
+
import { remoteMcp } from "../../connectors/remote-mcp.js";
|
|
16
|
+
import { reviewedCatalog } from "../../catalog-drift.js";
|
|
17
|
+
import { guardedFetch, retryAfterMs, } from "../../connectors/guarded-fetch.js";
|
|
18
|
+
import { ConnectorCallError } from "../../errors.js";
|
|
19
|
+
import { keys, optionsOf, variants } from "../../config-schema.js";
|
|
20
|
+
import { PROVIDER_COMMON } from "../../connectors/option-shapes.js";
|
|
21
|
+
import { defineProvider } from "../../provider.js";
|
|
22
|
+
/** Vercel's public REST origin. Override only for a proxy or test double. */
|
|
23
|
+
export const VERCEL_API_BASE_URL = "https://api.vercel.com";
|
|
24
|
+
/** Vercel's official hosted MCP endpoint. */
|
|
25
|
+
export const VERCEL_MCP_ENDPOINT = "https://mcp.vercel.com";
|
|
26
|
+
const MAX_PAGE_SIZE = 100;
|
|
27
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
28
|
+
const VERCEL_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
29
|
+
function asRecord(value) {
|
|
30
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
31
|
+
}
|
|
32
|
+
function asArray(value) {
|
|
33
|
+
return Array.isArray(value) ? value : [];
|
|
34
|
+
}
|
|
35
|
+
function compact(value) {
|
|
36
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
37
|
+
}
|
|
38
|
+
function detailFor(payload, status) {
|
|
39
|
+
const root = asRecord(payload);
|
|
40
|
+
const error = asRecord(root["error"]);
|
|
41
|
+
const code = typeof error["code"] === "string" ? error["code"] : undefined;
|
|
42
|
+
const message = typeof error["message"] === "string" && error["message"].trim()
|
|
43
|
+
? error["message"].trim()
|
|
44
|
+
: typeof root["message"] === "string" && root["message"].trim()
|
|
45
|
+
? root["message"].trim()
|
|
46
|
+
: `Vercel returned HTTP ${status}.`;
|
|
47
|
+
return code ? `Vercel ${code}: ${message}` : message;
|
|
48
|
+
}
|
|
49
|
+
function resetAfterMs(headers) {
|
|
50
|
+
const retryAfter = retryAfterMs(headers);
|
|
51
|
+
if (retryAfter !== undefined)
|
|
52
|
+
return retryAfter;
|
|
53
|
+
const raw = headers.get("x-ratelimit-reset");
|
|
54
|
+
if (!raw)
|
|
55
|
+
return undefined;
|
|
56
|
+
const seconds = Number(raw);
|
|
57
|
+
if (!Number.isFinite(seconds))
|
|
58
|
+
return undefined;
|
|
59
|
+
return Math.max(0, Math.trunc(seconds * 1_000 - Date.now()));
|
|
60
|
+
}
|
|
61
|
+
/** Map Vercel failures by the caller's useful next move. */
|
|
62
|
+
function vercelFailure(status, headers, payload) {
|
|
63
|
+
const detail = detailFor(payload, status);
|
|
64
|
+
if (status === 429) {
|
|
65
|
+
const wait = resetAfterMs(headers);
|
|
66
|
+
return new ConnectorCallError("rate_limited", `${detail} Vercel meters endpoints separately; wait for the reported reset before retrying this operation.`, wait === undefined ? {} : { retryAfterMs: wait });
|
|
67
|
+
}
|
|
68
|
+
if (status === 401) {
|
|
69
|
+
return new ConnectorCallError("auth_required", `${detail} The configured access token is invalid, expired, outside this team, or lacks the required scope. An operator must replace it or widen its Vercel scope.`);
|
|
70
|
+
}
|
|
71
|
+
if (status === 403) {
|
|
72
|
+
return new ConnectorCallError("provider_permission_denied", `${detail} Vercel refused access to this team or resource. Ask the team administrator to grant access and an operator to widen the configured token's Vercel scope.`);
|
|
73
|
+
}
|
|
74
|
+
if (status === 404) {
|
|
75
|
+
return new ConnectorCallError("not_found", `${detail} Confirm the project, deployment, domain, or environment-variable id with its list tool.`);
|
|
76
|
+
}
|
|
77
|
+
if (status === 400 || status === 409 || status === 422) {
|
|
78
|
+
return new ConnectorCallError("invalid_args", detail);
|
|
79
|
+
}
|
|
80
|
+
if (status >= 500) {
|
|
81
|
+
const wait = resetAfterMs(headers);
|
|
82
|
+
return new ConnectorCallError("unavailable", `${detail} Vercel is failing upstream.`, wait === undefined ? {} : { retryAfterMs: wait });
|
|
83
|
+
}
|
|
84
|
+
return new ConnectorCallError("connector_call_failed", detail, {
|
|
85
|
+
retryable: false,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function parseBody(text, contentType, strict = false) {
|
|
89
|
+
if (!text)
|
|
90
|
+
return undefined;
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(text);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
if (contentType?.includes("stream+json") || contentType?.includes("ndjson")) {
|
|
96
|
+
if (strict)
|
|
97
|
+
return parseStreamRows(text);
|
|
98
|
+
const rows = [];
|
|
99
|
+
for (const line of text.split("\n")) {
|
|
100
|
+
if (!line.trim())
|
|
101
|
+
continue;
|
|
102
|
+
try {
|
|
103
|
+
rows.push(JSON.parse(line));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
rows.push({ message: line });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return rows;
|
|
110
|
+
}
|
|
111
|
+
if (strict) {
|
|
112
|
+
throw new ConnectorCallError("connector_call_failed", "Vercel returned a malformed successful response.", {
|
|
113
|
+
retryable: false,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return text;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function parseStreamRows(text) {
|
|
120
|
+
if (!text.trim())
|
|
121
|
+
return [];
|
|
122
|
+
try {
|
|
123
|
+
const payload = JSON.parse(text);
|
|
124
|
+
return Array.isArray(payload) ? payload : [payload];
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
const rows = [];
|
|
128
|
+
for (const line of text.split("\n")) {
|
|
129
|
+
if (!line.trim())
|
|
130
|
+
continue;
|
|
131
|
+
try {
|
|
132
|
+
rows.push(JSON.parse(line));
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
throw new ConnectorCallError("connector_call_failed", "Vercel returned a malformed runtime-log stream.", {
|
|
136
|
+
retryable: false,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return rows;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function vercelTransport(baseUrl) {
|
|
144
|
+
return guardedFetch({
|
|
145
|
+
provider: "Vercel",
|
|
146
|
+
baseUrl,
|
|
147
|
+
headers: { Accept: "application/json" },
|
|
148
|
+
maxResponseBytes: VERCEL_MAX_RESPONSE_BYTES,
|
|
149
|
+
authenticate: async (ctx) => {
|
|
150
|
+
const token = (await ctx.credential?.get())?.trim();
|
|
151
|
+
if (!token) {
|
|
152
|
+
throw new ConnectorCallError("auth_required", "No Vercel access token is configured for this connector. Call authorize_connector for recovery options. When available, an operator can add the token in this connection in the operator UI.");
|
|
153
|
+
}
|
|
154
|
+
return { Authorization: `Bearer ${token}` };
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function callVercel(send, request, ctx, success = {}) {
|
|
159
|
+
return await send(request, ctx, async (response) => {
|
|
160
|
+
const text = await response.text();
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
throw vercelFailure(response.status, response.headers, parseBody(text, response.headers.get("content-type")));
|
|
163
|
+
}
|
|
164
|
+
return success.parse ? success.parse(text) : parseBody(text, response.headers.get("content-type"), !success.raw);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function teamQuery(args, defaultTeamId) {
|
|
168
|
+
return {
|
|
169
|
+
teamId: args["teamId"] === null ? undefined : (args["teamId"] ?? defaultTeamId),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function projectDomain(value) {
|
|
173
|
+
const domain = asRecord(value);
|
|
174
|
+
return compact({
|
|
175
|
+
name: domain["name"],
|
|
176
|
+
apexName: domain["apexName"],
|
|
177
|
+
projectId: domain["projectId"],
|
|
178
|
+
verified: domain["verified"] === true,
|
|
179
|
+
verification: domain["verification"],
|
|
180
|
+
redirect: domain["redirect"],
|
|
181
|
+
redirectStatusCode: domain["redirectStatusCode"],
|
|
182
|
+
gitBranch: domain["gitBranch"],
|
|
183
|
+
customEnvironmentId: domain["customEnvironmentId"],
|
|
184
|
+
createdAt: domain["createdAt"],
|
|
185
|
+
updatedAt: domain["updatedAt"],
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/** Deliberately omits `value`, even if a raw Vercel response happens to carry it. */
|
|
189
|
+
function projectEnvironmentVariable(value) {
|
|
190
|
+
const variable = asRecord(value);
|
|
191
|
+
return compact({
|
|
192
|
+
id: variable["id"],
|
|
193
|
+
key: variable["key"],
|
|
194
|
+
type: variable["type"],
|
|
195
|
+
visibility: variable["visibility"],
|
|
196
|
+
target: variable["target"],
|
|
197
|
+
gitBranch: variable["gitBranch"],
|
|
198
|
+
customEnvironmentIds: variable["customEnvironmentIds"],
|
|
199
|
+
comment: variable["comment"],
|
|
200
|
+
createdAt: variable["createdAt"],
|
|
201
|
+
updatedAt: variable["updatedAt"],
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const TEAM_ID_PROPERTY = {
|
|
205
|
+
type: ["string", "null"],
|
|
206
|
+
minLength: 1,
|
|
207
|
+
description: "Vercel team id. Omit for the configured default; pass null for the token owner's personal account.",
|
|
208
|
+
};
|
|
209
|
+
const PROJECT_ID_PROPERTY = {
|
|
210
|
+
type: "string",
|
|
211
|
+
minLength: 1,
|
|
212
|
+
description: "Project id or project name from list_projects.",
|
|
213
|
+
};
|
|
214
|
+
const DEPLOYMENT_ID_PROPERTY = {
|
|
215
|
+
type: "string",
|
|
216
|
+
minLength: 1,
|
|
217
|
+
description: "Deployment id from list_deployments.",
|
|
218
|
+
};
|
|
219
|
+
const DOMAIN_SCHEMA = {
|
|
220
|
+
type: "object",
|
|
221
|
+
properties: {
|
|
222
|
+
name: { type: "string" },
|
|
223
|
+
apexName: { type: "string" },
|
|
224
|
+
projectId: { type: "string" },
|
|
225
|
+
verified: { type: "boolean" },
|
|
226
|
+
verification: { type: "array" },
|
|
227
|
+
redirect: { type: ["string", "null"] },
|
|
228
|
+
redirectStatusCode: { type: ["integer", "null"] },
|
|
229
|
+
gitBranch: { type: ["string", "null"] },
|
|
230
|
+
customEnvironmentId: { type: ["string", "null"] },
|
|
231
|
+
createdAt: { type: "number" },
|
|
232
|
+
updatedAt: { type: "number" },
|
|
233
|
+
},
|
|
234
|
+
required: ["name", "projectId", "verified"],
|
|
235
|
+
};
|
|
236
|
+
const ENV_SCHEMA = {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: {
|
|
239
|
+
id: { type: "string" },
|
|
240
|
+
key: { type: "string" },
|
|
241
|
+
type: { type: "string" },
|
|
242
|
+
visibility: { type: "string" },
|
|
243
|
+
target: { type: ["array", "string"], items: { type: "string" } },
|
|
244
|
+
gitBranch: { type: ["string", "null"] },
|
|
245
|
+
customEnvironmentIds: { type: "array", items: { type: "string" } },
|
|
246
|
+
comment: { type: "string" },
|
|
247
|
+
createdAt: { type: "number" },
|
|
248
|
+
updatedAt: { type: "number" },
|
|
249
|
+
},
|
|
250
|
+
required: ["id", "key", "type"],
|
|
251
|
+
};
|
|
252
|
+
function namedInput(properties, required) {
|
|
253
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
254
|
+
}
|
|
255
|
+
function queryPairs(value) {
|
|
256
|
+
const query = {};
|
|
257
|
+
for (const row of asArray(value)) {
|
|
258
|
+
const pair = asRecord(row);
|
|
259
|
+
if (typeof pair["name"] !== "string")
|
|
260
|
+
continue;
|
|
261
|
+
const item = pair["value"];
|
|
262
|
+
if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
|
|
263
|
+
query[pair["name"]] = item;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return query;
|
|
267
|
+
}
|
|
268
|
+
const QUERY_PROPERTY = {
|
|
269
|
+
type: "array",
|
|
270
|
+
description: "Provider query parameters as name/value pairs.",
|
|
271
|
+
items: {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {
|
|
274
|
+
name: { type: "string", minLength: 1, description: "Query parameter name." },
|
|
275
|
+
value: {
|
|
276
|
+
type: ["string", "number", "boolean"],
|
|
277
|
+
description: "Query parameter value; guarded transport stringifies it once.",
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
required: ["name", "value"],
|
|
281
|
+
additionalProperties: false,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
const HEADERS_PROPERTY = {
|
|
285
|
+
type: "array",
|
|
286
|
+
description: "Endpoint-specific request headers. Credential, cookie, host, framing, and content-type headers are connector-owned.",
|
|
287
|
+
items: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
name: { type: "string", minLength: 1, description: "HTTP header name." },
|
|
291
|
+
value: { type: "string", description: "HTTP header value." },
|
|
292
|
+
},
|
|
293
|
+
required: ["name", "value"],
|
|
294
|
+
additionalProperties: false,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
const FORBIDDEN_HATCH_HEADERS = new Set([
|
|
298
|
+
"authorization",
|
|
299
|
+
"content-length",
|
|
300
|
+
"content-type",
|
|
301
|
+
"cookie",
|
|
302
|
+
"host",
|
|
303
|
+
"transfer-encoding",
|
|
304
|
+
]);
|
|
305
|
+
function headerPairs(value) {
|
|
306
|
+
const headers = {};
|
|
307
|
+
for (const row of asArray(value)) {
|
|
308
|
+
const pair = asRecord(row);
|
|
309
|
+
if (typeof pair["name"] === "string" && typeof pair["value"] === "string") {
|
|
310
|
+
const normalized = pair["name"].trim().toLowerCase();
|
|
311
|
+
if (FORBIDDEN_HATCH_HEADERS.has(normalized)) {
|
|
312
|
+
throw new ConnectorCallError("invalid_args", `A Vercel upload may not set the ${normalized} header; the connector owns credentials, cookies, origin, framing, and content type.`);
|
|
313
|
+
}
|
|
314
|
+
headers[pair["name"]] = pair["value"];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return headers;
|
|
318
|
+
}
|
|
319
|
+
function uploadBody(args) {
|
|
320
|
+
const hasText = typeof args["textBody"] === "string";
|
|
321
|
+
const hasBase64 = typeof args["base64Body"] === "string";
|
|
322
|
+
if (hasText === hasBase64) {
|
|
323
|
+
throw new ConnectorCallError("invalid_args", "Provide exactly one of textBody or base64Body for a Vercel upload.");
|
|
324
|
+
}
|
|
325
|
+
if (hasText)
|
|
326
|
+
return args["textBody"];
|
|
327
|
+
try {
|
|
328
|
+
return Uint8Array.from(atob(args["base64Body"]), (character) => character.charCodeAt(0));
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
throw new ConnectorCallError("invalid_args", "base64Body is not valid base64.");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function rawRequest(args, defaultTeamId) {
|
|
335
|
+
const method = args["method"] ?? "GET";
|
|
336
|
+
let path;
|
|
337
|
+
try {
|
|
338
|
+
path = decodeURIComponent(new URL(`https://api.vercel.com${String(args["path"])}`).pathname).replace(/\/{2,}/g, "/");
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
throw new ConnectorCallError("invalid_args", "The REST path contains an invalid escape or URL.");
|
|
342
|
+
}
|
|
343
|
+
const matches = ([verb, pattern]) => verb === method && pattern.test(path);
|
|
344
|
+
const canonical = VERCEL_EXACT_CANONICAL_ROUTES.find(matches) ?? VERCEL_CANONICAL_ROUTES.find(matches);
|
|
345
|
+
if (canonical?.[2]) {
|
|
346
|
+
throw new ConnectorCallError("invalid_args", `Use ${canonical[2]} on its owning connector. The REST complement cannot repeat that operation.`);
|
|
347
|
+
}
|
|
348
|
+
const query = queryPairs(args["query"]);
|
|
349
|
+
if (args["personalAccount"] === true && (query["teamId"] !== undefined || query["slug"] !== undefined)) {
|
|
350
|
+
throw new ConnectorCallError("invalid_args", "personalAccount cannot be combined with a teamId or slug query parameter.");
|
|
351
|
+
}
|
|
352
|
+
if (args["personalAccount"] !== true &&
|
|
353
|
+
defaultTeamId &&
|
|
354
|
+
query["teamId"] === undefined &&
|
|
355
|
+
query["slug"] === undefined) {
|
|
356
|
+
query["teamId"] = defaultTeamId;
|
|
357
|
+
}
|
|
358
|
+
return { path: String(args["path"]), query };
|
|
359
|
+
}
|
|
360
|
+
function hostedRestRoute([method, path, name], anyVersion) {
|
|
361
|
+
const pattern = path
|
|
362
|
+
.replace(/\/+$/, "")
|
|
363
|
+
.split("/")
|
|
364
|
+
.map((segment, index) => {
|
|
365
|
+
if (anyVersion && index === 1 && /^v\d+$/.test(segment))
|
|
366
|
+
return "v\\d+";
|
|
367
|
+
if (/^\{[^}]+\}$/.test(segment))
|
|
368
|
+
return "[^/]+";
|
|
369
|
+
return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
370
|
+
})
|
|
371
|
+
.join("/");
|
|
372
|
+
return [method, new RegExp(`^${pattern}\\/?$`), `MCP ${name}`];
|
|
373
|
+
}
|
|
374
|
+
// Resolve actual published method/path/version contracts first, including
|
|
375
|
+
// uncovered concrete endpoints. An id wildcard cannot consume a REST gap.
|
|
376
|
+
const VERCEL_EXACT_CANONICAL_ROUTES = [
|
|
377
|
+
...UNCOVERED_REST_OPERATIONS.map(([method, path]) => [
|
|
378
|
+
method,
|
|
379
|
+
hostedRestRoute([method, path, ""], false)[1],
|
|
380
|
+
null,
|
|
381
|
+
]),
|
|
382
|
+
...HOSTED_REST_OPERATIONS.map((operation) => hostedRestRoute(operation, false)),
|
|
383
|
+
];
|
|
384
|
+
// Version-independent matches keep a raw hatch from restoring a removed
|
|
385
|
+
// duplicate by selecting an older REST version. Env routes also protect the
|
|
386
|
+
// value-safe named API implementations from an unprojected response.
|
|
387
|
+
const VERCEL_CANONICAL_ROUTES = [
|
|
388
|
+
// Specific published routes precede generic id patterns (e.g. projects/traces).
|
|
389
|
+
...HOSTED_REST_OPERATIONS.map((operation) => hostedRestRoute(operation, true)),
|
|
390
|
+
["GET", /^\/v\d+\/teams\/?$/, "MCP list_teams"],
|
|
391
|
+
["GET", /^\/v\d+\/projects\/?$/, "MCP list_projects"],
|
|
392
|
+
["GET", /^\/v\d+\/projects\/[^/]+\/?$/, "MCP get_project"],
|
|
393
|
+
["GET", /^\/v\d+\/deployments\/?$/, "MCP list_deployments"],
|
|
394
|
+
["GET", /^\/v\d+\/deployments\/[^/]+\/?$/, "MCP get_deployment"],
|
|
395
|
+
["GET", /^\/v\d+\/deployments\/[^/]+\/events\/?$/, "MCP get_deployment_build_logs or list_deployment_events"],
|
|
396
|
+
["GET", /^\/v\d+\/projects\/[^/]+\/deployments\/[^/]+\/runtime-logs\/?$/, "MCP get_runtime_logs"],
|
|
397
|
+
["GET", /^\/v\d+\/projects\/[^/]+\/domains\/?$/, "MCP list_project_domains"],
|
|
398
|
+
["POST", /^\/v\d+\/projects\/[^/]+\/domains\/?$/, "MCP add_project_domain"],
|
|
399
|
+
["PATCH", /^\/v\d+\/deployments\/[^/]+\/cancel\/?$/, "MCP cancel_deployment"],
|
|
400
|
+
["POST", /^\/v\d+\/files\/?$/, "MCP upload_file"],
|
|
401
|
+
["POST", /^\/v\d+\/deployments\/?$/, "MCP create_deployment"],
|
|
402
|
+
["DELETE", /^\/v\d+\/deployments\/[^/]+\/?$/, "API delete_deployment"],
|
|
403
|
+
["POST", /^\/v\d+\/projects\/[^/]+\/promote\/[^/]+\/?$/, "MCP request_promote"],
|
|
404
|
+
["POST", /^\/v\d+\/projects\/[^/]+\/domains\/[^/]+\/verify\/?$/, "API verify_project_domain"],
|
|
405
|
+
["DELETE", /^\/v\d+\/projects\/[^/]+\/domains\/[^/]+\/?$/, "API remove_project_domain"],
|
|
406
|
+
["GET", /^\/v\d+\/projects\/[^/]+\/env(?:\/[^/]+)?\/?$/, "API list_project_env_vars"],
|
|
407
|
+
["POST", /^\/v\d+\/projects\/[^/]+\/env\/?$/, "API upsert_project_env_var"],
|
|
408
|
+
["PATCH", /^\/v\d+\/projects\/[^/]+\/env\/[^/]+\/?$/, "API update_project_env_var"],
|
|
409
|
+
["DELETE", /^\/v\d+\/projects\/[^/]+\/env\/[^/]+\/?$/, "API delete_project_env_var"],
|
|
410
|
+
];
|
|
411
|
+
const API_OWNED_MCP_TOOLS = new Set([
|
|
412
|
+
"filter_project_envs",
|
|
413
|
+
"get_project_env",
|
|
414
|
+
"create_project_env",
|
|
415
|
+
"edit_project_env",
|
|
416
|
+
]);
|
|
417
|
+
/** Preserve vendor contracts for retained tools and refuse hidden direct calls. */
|
|
418
|
+
function vercelCatalog(connector) {
|
|
419
|
+
return Object.assign(Object.create(connector), connector, {
|
|
420
|
+
async listTools(ctx) {
|
|
421
|
+
const tools = (await connector.listTools(ctx)).filter((tool) => !API_OWNED_MCP_TOOLS.has(tool.name));
|
|
422
|
+
if (new Set(tools.map((tool) => tool.name)).size !== tools.length) {
|
|
423
|
+
throw new ConnectorCallError("connector_call_failed", "Vercel returned duplicate tool names.", {
|
|
424
|
+
retryable: false,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return tools;
|
|
428
|
+
},
|
|
429
|
+
async callTool(name, args, ctx, options) {
|
|
430
|
+
if (API_OWNED_MCP_TOOLS.has(name)) {
|
|
431
|
+
throw new ConnectorCallError("invalid_args", "Project environment variables belong to the value-safe Vercel REST complement.");
|
|
432
|
+
}
|
|
433
|
+
return await connector.callTool(name, args, ctx, options);
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
const PERSONAL_ACCOUNT_PROPERTY = {
|
|
438
|
+
type: "boolean",
|
|
439
|
+
description: "True omits the configured default team. Do not combine with a teamId or slug query parameter.",
|
|
440
|
+
};
|
|
441
|
+
function tools(send, defaultTeamId) {
|
|
442
|
+
const readOnly = { readOnlyHint: true };
|
|
443
|
+
const destructive = { readOnlyHint: false, destructiveHint: true };
|
|
444
|
+
const team = (args) => teamQuery(args, defaultTeamId);
|
|
445
|
+
return [
|
|
446
|
+
{
|
|
447
|
+
name: "vercel_api_get",
|
|
448
|
+
description: "Call any Vercel REST GET endpoint and return its untouched response. Use named reads first for smaller results and stable projections.",
|
|
449
|
+
annotations: readOnly,
|
|
450
|
+
inputSchema: namedInput({
|
|
451
|
+
path: {
|
|
452
|
+
type: "string",
|
|
453
|
+
minLength: 1,
|
|
454
|
+
description: "Path below api.vercel.com beginning with '/', including its API version. No query string.",
|
|
455
|
+
},
|
|
456
|
+
query: QUERY_PROPERTY,
|
|
457
|
+
personalAccount: PERSONAL_ACCOUNT_PROPERTY,
|
|
458
|
+
}, ["path"]),
|
|
459
|
+
outputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
properties: { result: { description: "Vercel's untouched response body." } },
|
|
462
|
+
required: ["result"],
|
|
463
|
+
},
|
|
464
|
+
handler: async (args, ctx) => ({
|
|
465
|
+
result: (await callVercel(send, { method: "GET", ...rawRequest(args, defaultTeamId) }, ctx, { raw: true })) ?? null,
|
|
466
|
+
}),
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: "vercel_api_mutate",
|
|
470
|
+
description: "Call any JSON Vercel REST mutation endpoint. The approval-gated hatch for API operations the named tools do not cover; no file uploads.",
|
|
471
|
+
annotations: destructive,
|
|
472
|
+
inputSchema: namedInput({
|
|
473
|
+
method: {
|
|
474
|
+
type: "string",
|
|
475
|
+
enum: ["POST", "PUT", "PATCH", "DELETE"],
|
|
476
|
+
description: "HTTP mutation method required by the Vercel endpoint.",
|
|
477
|
+
},
|
|
478
|
+
path: {
|
|
479
|
+
type: "string",
|
|
480
|
+
minLength: 1,
|
|
481
|
+
description: "Path below api.vercel.com beginning with '/', including its API version. No query string.",
|
|
482
|
+
},
|
|
483
|
+
query: QUERY_PROPERTY,
|
|
484
|
+
personalAccount: PERSONAL_ACCOUNT_PROPERTY,
|
|
485
|
+
body: {
|
|
486
|
+
type: ["object", "array", "string", "number", "boolean", "null"],
|
|
487
|
+
description: "JSON body exactly as documented by Vercel. Omit when the endpoint has no body.",
|
|
488
|
+
},
|
|
489
|
+
}, ["method", "path"]),
|
|
490
|
+
outputSchema: {
|
|
491
|
+
type: "object",
|
|
492
|
+
properties: { result: { description: "Vercel's untouched response body, or null for an empty response." } },
|
|
493
|
+
required: ["result"],
|
|
494
|
+
},
|
|
495
|
+
handler: async (args, ctx) => ({
|
|
496
|
+
result: (await callVercel(send, {
|
|
497
|
+
method: args["method"],
|
|
498
|
+
...rawRequest(args, defaultTeamId),
|
|
499
|
+
...(args["body"] !== undefined ? { body: args["body"] } : {}),
|
|
500
|
+
}, ctx, { raw: true })) ?? null,
|
|
501
|
+
}),
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: "vercel_api_upload",
|
|
505
|
+
description: "Upload explicit text or base64 bytes to a Vercel POST or PUT endpoint. Covers deployment files and other raw-body APIs; reads no local files.",
|
|
506
|
+
annotations: destructive,
|
|
507
|
+
inputSchema: namedInput({
|
|
508
|
+
method: {
|
|
509
|
+
type: "string",
|
|
510
|
+
enum: ["POST", "PUT"],
|
|
511
|
+
description: "Upload method required by the Vercel endpoint.",
|
|
512
|
+
},
|
|
513
|
+
path: {
|
|
514
|
+
type: "string",
|
|
515
|
+
minLength: 1,
|
|
516
|
+
description: "Path below api.vercel.com beginning with '/', including its API version. No query string.",
|
|
517
|
+
},
|
|
518
|
+
query: QUERY_PROPERTY,
|
|
519
|
+
personalAccount: PERSONAL_ACCOUNT_PROPERTY,
|
|
520
|
+
headers: HEADERS_PROPERTY,
|
|
521
|
+
contentType: {
|
|
522
|
+
type: "string",
|
|
523
|
+
minLength: 1,
|
|
524
|
+
description: "Content-Type for the raw body, such as application/octet-stream.",
|
|
525
|
+
},
|
|
526
|
+
textBody: {
|
|
527
|
+
type: "string",
|
|
528
|
+
description: "Raw UTF-8 body. Exclusive with base64Body.",
|
|
529
|
+
},
|
|
530
|
+
base64Body: {
|
|
531
|
+
type: "string",
|
|
532
|
+
description: "Base64-encoded bytes. Exclusive with textBody.",
|
|
533
|
+
},
|
|
534
|
+
}, ["method", "path", "contentType"]),
|
|
535
|
+
outputSchema: {
|
|
536
|
+
type: "object",
|
|
537
|
+
properties: {
|
|
538
|
+
result: {
|
|
539
|
+
description: "Vercel's untouched upload response body, or null for an empty response.",
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
required: ["result"],
|
|
543
|
+
},
|
|
544
|
+
handler: async (args, ctx) => ({
|
|
545
|
+
result: (await callVercel(send, {
|
|
546
|
+
method: args["method"],
|
|
547
|
+
...rawRequest(args, defaultTeamId),
|
|
548
|
+
headers: {
|
|
549
|
+
...headerPairs(args["headers"]),
|
|
550
|
+
"Content-Type": args["contentType"],
|
|
551
|
+
},
|
|
552
|
+
rawBody: uploadBody(args),
|
|
553
|
+
}, ctx, { raw: true })) ?? null,
|
|
554
|
+
}),
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: "verify_project_domain",
|
|
558
|
+
description: "Ask Vercel to verify a project's pending domain after its DNS challenge has been completed. Returns the current domain state.",
|
|
559
|
+
annotations: destructive,
|
|
560
|
+
inputSchema: namedInput({
|
|
561
|
+
projectId: PROJECT_ID_PROPERTY,
|
|
562
|
+
domain: { type: "string", minLength: 1, description: "Pending domain name from list_project_domains." },
|
|
563
|
+
teamId: TEAM_ID_PROPERTY,
|
|
564
|
+
}, ["projectId", "domain"]),
|
|
565
|
+
outputSchema: DOMAIN_SCHEMA,
|
|
566
|
+
handler: async (args, ctx) => projectDomain(await callVercel(send, {
|
|
567
|
+
method: "POST",
|
|
568
|
+
path: `/v9/projects/${encodeURIComponent(args["projectId"])}/domains/${encodeURIComponent(args["domain"])}/verify`,
|
|
569
|
+
query: team(args),
|
|
570
|
+
}, ctx)),
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: "remove_project_domain",
|
|
574
|
+
description: "Remove a domain from one Vercel project. Optionally remove project domains that redirect to it; this does not delete the account-level domain.",
|
|
575
|
+
annotations: destructive,
|
|
576
|
+
inputSchema: namedInput({
|
|
577
|
+
projectId: PROJECT_ID_PROPERTY,
|
|
578
|
+
domain: { type: "string", minLength: 1, description: "Project domain name from list_project_domains." },
|
|
579
|
+
removeRedirects: { type: "boolean", description: "Also remove project domains that redirect to this one." },
|
|
580
|
+
teamId: TEAM_ID_PROPERTY,
|
|
581
|
+
}, ["projectId", "domain"]),
|
|
582
|
+
outputSchema: {
|
|
583
|
+
type: "object",
|
|
584
|
+
properties: { removed: { type: "boolean" }, domain: { type: "string" } },
|
|
585
|
+
required: ["removed", "domain"],
|
|
586
|
+
},
|
|
587
|
+
handler: async (args, ctx) => {
|
|
588
|
+
await callVercel(send, {
|
|
589
|
+
method: "DELETE",
|
|
590
|
+
path: `/v9/projects/${encodeURIComponent(args["projectId"])}/domains/${encodeURIComponent(args["domain"])}`,
|
|
591
|
+
query: team(args),
|
|
592
|
+
body: args["removeRedirects"] === undefined ? undefined : { removeRedirects: args["removeRedirects"] },
|
|
593
|
+
}, ctx);
|
|
594
|
+
return { removed: true, domain: args["domain"] };
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: "list_project_env_vars",
|
|
599
|
+
description: "List a project's environment-variable metadata without decrypting or returning values. Includes targets, visibility, branches, and custom environments.",
|
|
600
|
+
annotations: readOnly,
|
|
601
|
+
inputSchema: namedInput({
|
|
602
|
+
projectId: PROJECT_ID_PROPERTY,
|
|
603
|
+
teamId: TEAM_ID_PROPERTY,
|
|
604
|
+
gitBranch: { type: "string", description: "Preview branch filter." },
|
|
605
|
+
customEnvironmentId: { type: "string", description: "Custom environment filter." },
|
|
606
|
+
}, ["projectId"]),
|
|
607
|
+
outputSchema: {
|
|
608
|
+
type: "object",
|
|
609
|
+
properties: { variables: { type: "array", items: ENV_SCHEMA } },
|
|
610
|
+
required: ["variables"],
|
|
611
|
+
},
|
|
612
|
+
handler: async (args, ctx) => {
|
|
613
|
+
const payload = await callVercel(send, {
|
|
614
|
+
method: "GET",
|
|
615
|
+
path: `/v10/projects/${encodeURIComponent(args["projectId"])}/env`,
|
|
616
|
+
query: {
|
|
617
|
+
...team(args),
|
|
618
|
+
gitBranch: args["gitBranch"],
|
|
619
|
+
customEnvironmentId: args["customEnvironmentId"],
|
|
620
|
+
decrypt: "false",
|
|
621
|
+
},
|
|
622
|
+
}, ctx);
|
|
623
|
+
return { variables: asArray(asRecord(payload)["envs"]).map(projectEnvironmentVariable) };
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: "upsert_project_env_var",
|
|
628
|
+
description: "Create or replace one Vercel project environment variable. Changes affect only future deployments; trigger a new deployment separately.",
|
|
629
|
+
annotations: destructive,
|
|
630
|
+
inputSchema: namedInput({
|
|
631
|
+
projectId: PROJECT_ID_PROPERTY,
|
|
632
|
+
teamId: TEAM_ID_PROPERTY,
|
|
633
|
+
key: { type: "string", minLength: 1, maxLength: 256, description: "Environment variable name." },
|
|
634
|
+
value: {
|
|
635
|
+
type: "string",
|
|
636
|
+
maxLength: 65536,
|
|
637
|
+
description: "New value. Vercel's total project-environment payload is capped at 64 KB.",
|
|
638
|
+
},
|
|
639
|
+
type: {
|
|
640
|
+
type: "string",
|
|
641
|
+
enum: ["plain", "encrypted", "sensitive"],
|
|
642
|
+
description: "Storage type. Sensitive values cannot be read back.",
|
|
643
|
+
},
|
|
644
|
+
targets: {
|
|
645
|
+
type: "array",
|
|
646
|
+
minItems: 1,
|
|
647
|
+
uniqueItems: true,
|
|
648
|
+
items: { type: "string", enum: ["production", "preview", "development"] },
|
|
649
|
+
description: "Default Vercel environments that receive this value.",
|
|
650
|
+
},
|
|
651
|
+
gitBranch: { type: "string", description: "Optional preview-only Git branch." },
|
|
652
|
+
customEnvironmentIds: {
|
|
653
|
+
type: "array",
|
|
654
|
+
uniqueItems: true,
|
|
655
|
+
items: { type: "string", minLength: 1 },
|
|
656
|
+
description: "Custom environment ids that receive this value.",
|
|
657
|
+
},
|
|
658
|
+
comment: { type: "string", maxLength: 500, description: "Operator-facing note explaining the variable." },
|
|
659
|
+
upsert: {
|
|
660
|
+
type: "boolean",
|
|
661
|
+
description: "Defaults to true. Set false for create-only behavior that refuses to overwrite an existing variable.",
|
|
662
|
+
},
|
|
663
|
+
}, ["projectId", "key", "value", "type", "targets"]),
|
|
664
|
+
outputSchema: ENV_SCHEMA,
|
|
665
|
+
handler: async (args, ctx) => {
|
|
666
|
+
const payload = asRecord(await callVercel(send, {
|
|
667
|
+
method: "POST",
|
|
668
|
+
path: `/v10/projects/${encodeURIComponent(args["projectId"])}/env`,
|
|
669
|
+
query: { ...team(args), upsert: args["upsert"] === false ? "false" : "true" },
|
|
670
|
+
body: compact({
|
|
671
|
+
key: args["key"],
|
|
672
|
+
value: args["value"],
|
|
673
|
+
type: args["type"],
|
|
674
|
+
target: args["targets"],
|
|
675
|
+
gitBranch: args["gitBranch"],
|
|
676
|
+
customEnvironmentIds: args["customEnvironmentIds"],
|
|
677
|
+
comment: args["comment"],
|
|
678
|
+
}),
|
|
679
|
+
}, ctx));
|
|
680
|
+
const failed = asArray(payload["failed"]);
|
|
681
|
+
if (failed.length > 0) {
|
|
682
|
+
const error = asRecord(asRecord(failed[0])["error"]);
|
|
683
|
+
const code = typeof error["code"] === "string" ? `${error["code"]}: ` : "";
|
|
684
|
+
const message = typeof error["message"] === "string" ? error["message"] : "Vercel rejected the environment-variable write.";
|
|
685
|
+
throw new ConnectorCallError("invalid_args", `Vercel ${code}${message}`);
|
|
686
|
+
}
|
|
687
|
+
const created = Array.isArray(payload["created"]) ? payload["created"][0] : payload["created"];
|
|
688
|
+
const result = projectEnvironmentVariable(created ?? payload);
|
|
689
|
+
if (!result["id"] || !result["key"] || !result["type"]) {
|
|
690
|
+
throw new ConnectorCallError("connector_call_failed", "Vercel accepted the environment-variable write without returning the created variable.", { retryable: false });
|
|
691
|
+
}
|
|
692
|
+
return result;
|
|
693
|
+
},
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
name: "update_project_env_var",
|
|
697
|
+
description: "Update one Vercel project environment variable by id. Send only fields that should change; deployments keep their previous values.",
|
|
698
|
+
annotations: destructive,
|
|
699
|
+
inputSchema: namedInput({
|
|
700
|
+
projectId: PROJECT_ID_PROPERTY,
|
|
701
|
+
teamId: TEAM_ID_PROPERTY,
|
|
702
|
+
envVarId: {
|
|
703
|
+
type: "string",
|
|
704
|
+
minLength: 1,
|
|
705
|
+
description: "Environment-variable id from list_project_env_vars.",
|
|
706
|
+
},
|
|
707
|
+
key: { type: "string", minLength: 1, maxLength: 256, description: "Replacement variable name." },
|
|
708
|
+
value: { type: "string", maxLength: 65536, description: "Replacement value." },
|
|
709
|
+
type: { type: "string", enum: ["plain", "encrypted", "sensitive"], description: "Replacement storage type." },
|
|
710
|
+
targets: {
|
|
711
|
+
type: "array",
|
|
712
|
+
minItems: 1,
|
|
713
|
+
uniqueItems: true,
|
|
714
|
+
items: { type: "string", enum: ["production", "preview", "development"] },
|
|
715
|
+
description: "Replacement default environments.",
|
|
716
|
+
},
|
|
717
|
+
gitBranch: { type: ["string", "null"], description: "Replacement preview branch, or null to clear it." },
|
|
718
|
+
customEnvironmentIds: {
|
|
719
|
+
type: "array",
|
|
720
|
+
uniqueItems: true,
|
|
721
|
+
items: { type: "string", minLength: 1 },
|
|
722
|
+
description: "Replacement custom environment ids.",
|
|
723
|
+
},
|
|
724
|
+
comment: { type: "string", maxLength: 500, description: "Replacement operator-facing note." },
|
|
725
|
+
}, ["projectId", "envVarId"]),
|
|
726
|
+
outputSchema: ENV_SCHEMA,
|
|
727
|
+
handler: async (args, ctx) => {
|
|
728
|
+
const body = compact({
|
|
729
|
+
key: args["key"],
|
|
730
|
+
value: args["value"],
|
|
731
|
+
type: args["type"],
|
|
732
|
+
target: args["targets"],
|
|
733
|
+
gitBranch: args["gitBranch"],
|
|
734
|
+
customEnvironmentIds: args["customEnvironmentIds"],
|
|
735
|
+
comment: args["comment"],
|
|
736
|
+
});
|
|
737
|
+
if (Object.keys(body).length === 0) {
|
|
738
|
+
throw new ConnectorCallError("invalid_args", "Nothing to update: provide key, value, type, targets, gitBranch, customEnvironmentIds, or comment.");
|
|
739
|
+
}
|
|
740
|
+
return projectEnvironmentVariable(await callVercel(send, {
|
|
741
|
+
method: "PATCH",
|
|
742
|
+
path: `/v9/projects/${encodeURIComponent(args["projectId"])}/env/${encodeURIComponent(args["envVarId"])}`,
|
|
743
|
+
query: team(args),
|
|
744
|
+
body,
|
|
745
|
+
}, ctx));
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
name: "delete_project_env_var",
|
|
750
|
+
description: "Delete one environment variable from a Vercel project by id. Existing deployments keep their embedded value; future deployments do not.",
|
|
751
|
+
annotations: destructive,
|
|
752
|
+
inputSchema: namedInput({
|
|
753
|
+
projectId: PROJECT_ID_PROPERTY,
|
|
754
|
+
envVarId: {
|
|
755
|
+
type: "string",
|
|
756
|
+
minLength: 1,
|
|
757
|
+
description: "Environment-variable id from list_project_env_vars.",
|
|
758
|
+
},
|
|
759
|
+
teamId: TEAM_ID_PROPERTY,
|
|
760
|
+
}, ["projectId", "envVarId"]),
|
|
761
|
+
outputSchema: {
|
|
762
|
+
type: "object",
|
|
763
|
+
properties: { deleted: { type: "boolean" }, envVarId: { type: "string" } },
|
|
764
|
+
required: ["deleted", "envVarId"],
|
|
765
|
+
},
|
|
766
|
+
handler: async (args, ctx) => {
|
|
767
|
+
await callVercel(send, {
|
|
768
|
+
method: "DELETE",
|
|
769
|
+
path: `/v9/projects/${encodeURIComponent(args["projectId"])}/env/${encodeURIComponent(args["envVarId"])}`,
|
|
770
|
+
query: team(args),
|
|
771
|
+
}, ctx);
|
|
772
|
+
return { deleted: true, envVarId: args["envVarId"] };
|
|
773
|
+
},
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
name: "delete_deployment",
|
|
777
|
+
description: "Permanently delete one Vercel deployment and its deployment URL. This cannot be undone; use cancel_deployment for work still running.",
|
|
778
|
+
annotations: destructive,
|
|
779
|
+
inputSchema: namedInput({ deploymentId: DEPLOYMENT_ID_PROPERTY, teamId: TEAM_ID_PROPERTY }, ["deploymentId"]),
|
|
780
|
+
outputSchema: {
|
|
781
|
+
type: "object",
|
|
782
|
+
properties: { deleted: { type: "boolean" }, deploymentId: { type: "string" } },
|
|
783
|
+
required: ["deleted", "deploymentId"],
|
|
784
|
+
},
|
|
785
|
+
handler: async (args, ctx) => {
|
|
786
|
+
await callVercel(send, { method: "DELETE", path: `/v13/deployments/${encodeURIComponent(args["deploymentId"])}`, query: team(args) }, ctx);
|
|
787
|
+
return { deleted: true, deploymentId: args["deploymentId"] };
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
];
|
|
791
|
+
}
|
|
792
|
+
function apiUsageGuide(purpose, teamId, instructions) {
|
|
793
|
+
const accountInstructions = instructions?.trim();
|
|
794
|
+
return `# Vercel usage
|
|
795
|
+
|
|
796
|
+
Account purpose: ${purpose}
|
|
797
|
+
|
|
798
|
+
## Scope before action
|
|
799
|
+
|
|
800
|
+
${teamId
|
|
801
|
+
? `This connection defaults to team \`${teamId}${skill.fragments.guide_0}`
|
|
802
|
+
: "This connection defaults to the token owner's personal account. Use hosted MCP `list_teams`, then pass `teamId`, for team-owned resources."}${skill.fragments.guide_1}${accountInstructions ? `\n## ${skill.instructionsHeading}\n\n${accountInstructions}\n` : ""}`;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Reviewed in #705's provider audit against https://vercel.com/docs/mcp/vercel-mcp/tools.
|
|
806
|
+
* Retains the release-reviewed inventory, including names absent from today's
|
|
807
|
+
* public reference. Live annotations were not reverified without credentials.
|
|
808
|
+
* No schema digest is asserted without a captured schema to review.
|
|
809
|
+
*/
|
|
810
|
+
const VERCEL_MCP_CLASSIFICATION = {
|
|
811
|
+
tools: {
|
|
812
|
+
"request_promote": {
|
|
813
|
+
verdict: "destructive",
|
|
814
|
+
reason: "The rolling-releases MCP reference promotes an existing deployment to production; replaces the authored REST promotion write.",
|
|
815
|
+
},
|
|
816
|
+
"list_project_domains": {
|
|
817
|
+
verdict: "read",
|
|
818
|
+
reason: "The projects MCP reference lists project-domain metadata; formerly the named REST read.",
|
|
819
|
+
},
|
|
820
|
+
"add_project_domain": {
|
|
821
|
+
verdict: "destructive",
|
|
822
|
+
reason: "Attaches a domain to a project; preserves the former REST write classification.",
|
|
823
|
+
},
|
|
824
|
+
"cancel_deployment": {
|
|
825
|
+
verdict: "destructive",
|
|
826
|
+
reason: "Cancels an existing deployment, even if the vendor claims read-only access.",
|
|
827
|
+
},
|
|
828
|
+
"upload_file": { verdict: "destructive", reason: "Uploads deployment file bytes and creates vendor state." },
|
|
829
|
+
"create_deployment": {
|
|
830
|
+
verdict: "destructive",
|
|
831
|
+
reason: "Creates a preview or production deployment from Git or files.",
|
|
832
|
+
},
|
|
833
|
+
"list_deployment_events": {
|
|
834
|
+
verdict: "read",
|
|
835
|
+
reason: "Reads build events using the documented deployments catalog.",
|
|
836
|
+
},
|
|
837
|
+
"search_vercel_documentation": {
|
|
838
|
+
"verdict": "read",
|
|
839
|
+
"reason": "Retrieves Vercel vercel documentation information without changing vendor state.",
|
|
840
|
+
},
|
|
841
|
+
"list_teams": { "verdict": "read", "reason": "Retrieves Vercel teams information without changing vendor state." },
|
|
842
|
+
"list_projects": {
|
|
843
|
+
"verdict": "read",
|
|
844
|
+
"reason": "Retrieves Vercel projects information without changing vendor state.",
|
|
845
|
+
},
|
|
846
|
+
"get_project": {
|
|
847
|
+
"verdict": "read",
|
|
848
|
+
"reason": "Retrieves Vercel project information without changing vendor state.",
|
|
849
|
+
},
|
|
850
|
+
"list_deployments": {
|
|
851
|
+
"verdict": "read",
|
|
852
|
+
"reason": "Retrieves Vercel deployments information without changing vendor state.",
|
|
853
|
+
},
|
|
854
|
+
"get_deployment": {
|
|
855
|
+
"verdict": "read",
|
|
856
|
+
"reason": "Retrieves Vercel deployment information without changing vendor state.",
|
|
857
|
+
},
|
|
858
|
+
"get_deployment_build_logs": {
|
|
859
|
+
"verdict": "read",
|
|
860
|
+
"reason": "Retrieves Vercel deployment build logs information without changing vendor state.",
|
|
861
|
+
},
|
|
862
|
+
"get_runtime_logs": {
|
|
863
|
+
"verdict": "read",
|
|
864
|
+
"reason": "Retrieves Vercel runtime logs information without changing vendor state.",
|
|
865
|
+
},
|
|
866
|
+
"get_runtime_errors": {
|
|
867
|
+
"verdict": "read",
|
|
868
|
+
"reason": "Retrieves Vercel runtime errors information without changing vendor state.",
|
|
869
|
+
},
|
|
870
|
+
"get_web_analytics": {
|
|
871
|
+
"verdict": "read",
|
|
872
|
+
"reason": "Retrieves Vercel web analytics information without changing vendor state.",
|
|
873
|
+
},
|
|
874
|
+
"list_agent_run_projects": {
|
|
875
|
+
"verdict": "read",
|
|
876
|
+
"reason": "Retrieves Vercel agent run projects information without changing vendor state.",
|
|
877
|
+
},
|
|
878
|
+
"list_agent_runs": {
|
|
879
|
+
"verdict": "read",
|
|
880
|
+
"reason": "Retrieves Vercel agent runs information without changing vendor state.",
|
|
881
|
+
},
|
|
882
|
+
"get_agent_run": {
|
|
883
|
+
"verdict": "read",
|
|
884
|
+
"reason": "Retrieves Vercel agent run information without changing vendor state.",
|
|
885
|
+
},
|
|
886
|
+
"get_agent_run_trace": {
|
|
887
|
+
"verdict": "read",
|
|
888
|
+
"reason": "Retrieves Vercel agent run trace information without changing vendor state.",
|
|
889
|
+
},
|
|
890
|
+
"check_domain_availability_and_price": {
|
|
891
|
+
"verdict": "read",
|
|
892
|
+
"reason": "Retrieves Vercel check domain availability and price information without changing vendor state.",
|
|
893
|
+
},
|
|
894
|
+
"get_purchase_quote": {
|
|
895
|
+
"verdict": "read",
|
|
896
|
+
"reason": "Reads a purchase quote; billing changes require a separate purchase tool.",
|
|
897
|
+
},
|
|
898
|
+
"get_domain_order": {
|
|
899
|
+
"verdict": "read",
|
|
900
|
+
"reason": "Retrieves Vercel domain order information without changing vendor state.",
|
|
901
|
+
},
|
|
902
|
+
"list_toolbar_threads": {
|
|
903
|
+
"verdict": "read",
|
|
904
|
+
"reason": "Retrieves Vercel toolbar threads information without changing vendor state.",
|
|
905
|
+
},
|
|
906
|
+
"get_toolbar_thread": {
|
|
907
|
+
"verdict": "read",
|
|
908
|
+
"reason": "Retrieves Vercel toolbar thread information without changing vendor state.",
|
|
909
|
+
},
|
|
910
|
+
"use_vercel_cli": {
|
|
911
|
+
"verdict": "read",
|
|
912
|
+
"reason": "Returns CLI guidance only; any subsequent CLI execution is outside this tool.",
|
|
913
|
+
},
|
|
914
|
+
"reply_to_toolbar_thread": {
|
|
915
|
+
"verdict": "write",
|
|
916
|
+
"reason": "reply to toolbar thread creates or appends Vercel state; it has side effects.",
|
|
917
|
+
},
|
|
918
|
+
"add_toolbar_reaction": {
|
|
919
|
+
"verdict": "write",
|
|
920
|
+
"reason": "add toolbar reaction creates or appends Vercel state; it has side effects.",
|
|
921
|
+
},
|
|
922
|
+
"deploy_to_vercel": {
|
|
923
|
+
"verdict": "destructive",
|
|
924
|
+
"reason": "Can change a live deployment and the project serving production traffic.",
|
|
925
|
+
},
|
|
926
|
+
"buy_pro": { "verdict": "destructive", "reason": "buy pro changes existing Vercel state or removes it." },
|
|
927
|
+
"buy_credits": { "verdict": "destructive", "reason": "buy credits changes existing Vercel state or removes it." },
|
|
928
|
+
"buy_addon": { "verdict": "destructive", "reason": "buy addon changes existing Vercel state or removes it." },
|
|
929
|
+
"buy_domain": { "verdict": "destructive", "reason": "buy domain changes existing Vercel state or removes it." },
|
|
930
|
+
"get_access_to_vercel_url": {
|
|
931
|
+
"verdict": "destructive",
|
|
932
|
+
"reason": "Creates an access grant; the returned access URL is a credential.",
|
|
933
|
+
},
|
|
934
|
+
"web_fetch_vercel_url": {
|
|
935
|
+
"verdict": "destructive",
|
|
936
|
+
"reason": "Invokes application code, whose side effects cannot be established from HTTP GET alone.",
|
|
937
|
+
},
|
|
938
|
+
"import-claude-design-from-url": {
|
|
939
|
+
"verdict": "destructive",
|
|
940
|
+
"reason": "Imports into a project and can change existing live project state.",
|
|
941
|
+
},
|
|
942
|
+
"change_toolbar_thread_resolve_status": {
|
|
943
|
+
"verdict": "destructive",
|
|
944
|
+
"reason": "change toolbar thread resolve status changes existing Vercel state or removes it.",
|
|
945
|
+
},
|
|
946
|
+
"edit_toolbar_message": {
|
|
947
|
+
"verdict": "destructive",
|
|
948
|
+
"reason": "edit toolbar message changes existing Vercel state or removes it.",
|
|
949
|
+
},
|
|
950
|
+
},
|
|
951
|
+
};
|
|
952
|
+
function mcpUsageGuide(purpose, instructions) {
|
|
953
|
+
const accountInstructions = instructions?.trim();
|
|
954
|
+
return `${skill.fragments.guide_2}${purpose}${skill.fragments.guide_3}${accountInstructions ? `\n## ${skill.instructionsHeading}\n\n${accountInstructions}\n` : ""}`;
|
|
955
|
+
}
|
|
956
|
+
function vercelMcp(id, purpose, options, provider) {
|
|
957
|
+
const connector = remoteMcp(id, {
|
|
958
|
+
url: VERCEL_MCP_ENDPOINT,
|
|
959
|
+
...provider.connectorOptions,
|
|
960
|
+
title: options.title ?? "Vercel (MCP)",
|
|
961
|
+
description: `Vercel's official hosted MCP surface: ${purpose}`,
|
|
962
|
+
auth: { type: "oauth" },
|
|
963
|
+
requireHttps: true,
|
|
964
|
+
classify: provider.classify,
|
|
965
|
+
usageGuide: {
|
|
966
|
+
content: mcpUsageGuide(purpose, options.instructions),
|
|
967
|
+
summary: "Official MCP. Live Vercel schemas, id resolution, deployment diagnosis, purchases, and access grants.",
|
|
968
|
+
required: true,
|
|
969
|
+
},
|
|
970
|
+
});
|
|
971
|
+
return vercelCatalog(connector);
|
|
972
|
+
}
|
|
973
|
+
function vercelApi(id, purpose, options) {
|
|
974
|
+
const defaultPageSize = options.defaultPageSize ?? DEFAULT_PAGE_SIZE;
|
|
975
|
+
if (!Number.isInteger(defaultPageSize) || defaultPageSize < 1 || defaultPageSize > MAX_PAGE_SIZE) {
|
|
976
|
+
throw new Error(`vercel() defaultPageSize must be a whole number between 1 and ${MAX_PAGE_SIZE}.`);
|
|
977
|
+
}
|
|
978
|
+
const teamId = options.teamId?.trim() || undefined;
|
|
979
|
+
const send = vercelTransport(options.baseUrl ?? VERCEL_API_BASE_URL);
|
|
980
|
+
return api(id, {
|
|
981
|
+
...(options.authScope ? { authScope: options.authScope } : {}),
|
|
982
|
+
title: options.title ?? "Vercel",
|
|
983
|
+
description: `Vercel account and deployments: ${purpose}`,
|
|
984
|
+
credential: {
|
|
985
|
+
label: "Vercel access token",
|
|
986
|
+
description: "Access token from Vercel Account Settings → Tokens. Choose the personal account or team scope this deployment needs and set an expiration date. The connector never sends it anywhere except api.vercel.com or the configured baseUrl proxy.",
|
|
987
|
+
placeholder: "Paste Vercel access token",
|
|
988
|
+
},
|
|
989
|
+
testCredential: async (value, ctx) => {
|
|
990
|
+
try {
|
|
991
|
+
const payload = asRecord(await callVercel(send, { method: "GET", path: "/v2/user" }, { ...ctx, credential: { get: async () => value, getAll: async () => ({ value }) } }));
|
|
992
|
+
const user = asRecord(payload["user"] ?? payload);
|
|
993
|
+
const identity = user["username"] ?? user["email"] ?? user["name"] ?? user["id"] ?? "Vercel user";
|
|
994
|
+
return { ok: true, message: `Authenticated as ${identity}.` };
|
|
995
|
+
}
|
|
996
|
+
catch (error) {
|
|
997
|
+
return {
|
|
998
|
+
ok: false,
|
|
999
|
+
message: error instanceof ConnectorCallError ? error.message : "Vercel rejected the token.",
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
usageGuide: {
|
|
1004
|
+
content: apiUsageGuide(purpose, teamId, options.instructions),
|
|
1005
|
+
summary: "Team scoping, deployment diagnosis, value-safe environment variables, REST hatches, and cursor pagination.",
|
|
1006
|
+
required: true,
|
|
1007
|
+
},
|
|
1008
|
+
...(options.callAdmission ? { callAdmission: options.callAdmission } : {}),
|
|
1009
|
+
tools: tools(send, teamId),
|
|
1010
|
+
...(options.maxResultBytes !== undefined ? { maxResultBytes: options.maxResultBytes } : {}),
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
/** The closed options vercel() accepts; see `assertKnownOptions`. */
|
|
1014
|
+
const VERCEL_OPTIONS = variants("surface", {
|
|
1015
|
+
api: optionsOf()({
|
|
1016
|
+
...PROVIDER_COMMON,
|
|
1017
|
+
...keys("surface", "teamId", "baseUrl", "defaultPageSize"),
|
|
1018
|
+
}).shape,
|
|
1019
|
+
mcp: optionsOf()({ ...PROVIDER_COMMON, ...keys("surface") }).shape,
|
|
1020
|
+
}, "mcp");
|
|
1021
|
+
/** A maintained Vercel connection using the selected provider surface. */
|
|
1022
|
+
export const vercel = defineProvider({
|
|
1023
|
+
name: "vercel",
|
|
1024
|
+
title: "Vercel",
|
|
1025
|
+
kind: "composed",
|
|
1026
|
+
readme: "Vercel",
|
|
1027
|
+
bundle: { "baselineGzip": 143649, "maxGzip": 203649 },
|
|
1028
|
+
skill,
|
|
1029
|
+
options: VERCEL_OPTIONS,
|
|
1030
|
+
classify: VERCEL_MCP_CLASSIFICATION,
|
|
1031
|
+
create: vercelConnector,
|
|
1032
|
+
});
|
|
1033
|
+
function vercelConnector(id, options, provider) {
|
|
1034
|
+
const purpose = options.purpose.trim();
|
|
1035
|
+
return options.surface === "api" ? vercelApi(id, purpose, options) : vercelMcp(id, purpose, options, provider);
|
|
1036
|
+
}
|
|
1037
|
+
/** @deprecated Read `vercel.definition.classify` instead. Kept for existing imports. */
|
|
1038
|
+
export const VERCEL_MCP_VETTED_CATALOG = reviewedCatalog(vercel.definition.classify, 'defineProvider("vercel")');
|