wizards 0.1.9
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/LICENSE +110 -0
- package/README.md +170 -0
- package/apps/runtime/dist/agents/architect.js +453 -0
- package/apps/runtime/dist/agents/project-assistant.js +354 -0
- package/apps/runtime/dist/agents/subscription.js +206 -0
- package/apps/runtime/dist/agents/thought.js +21 -0
- package/apps/runtime/dist/agents/website.js +197 -0
- package/apps/runtime/dist/app.js +153 -0
- package/apps/runtime/dist/auth/account.js +121 -0
- package/apps/runtime/dist/auth/index.js +329 -0
- package/apps/runtime/dist/auth/keys.js +55 -0
- package/apps/runtime/dist/auth/local-ticket.js +23 -0
- package/apps/runtime/dist/authoring/example.js +73 -0
- package/apps/runtime/dist/authoring/guide.js +259 -0
- package/apps/runtime/dist/authoring/ops.js +117 -0
- package/apps/runtime/dist/cli/autostart.js +134 -0
- package/apps/runtime/dist/cli/clients.js +44 -0
- package/apps/runtime/dist/cli/environment.js +81 -0
- package/apps/runtime/dist/cli/exec.js +45 -0
- package/apps/runtime/dist/cli/home.js +74 -0
- package/apps/runtime/dist/cli/machine.js +145 -0
- package/apps/runtime/dist/cli/main.js +125 -0
- package/apps/runtime/dist/cli/setup.js +273 -0
- package/apps/runtime/dist/cli/start.js +190 -0
- package/apps/runtime/dist/cli/status.js +120 -0
- package/apps/runtime/dist/cli/ui.js +12 -0
- package/apps/runtime/dist/cli/update.js +58 -0
- package/apps/runtime/dist/connectors/builtin.js +70 -0
- package/apps/runtime/dist/connectors/external.js +400 -0
- package/apps/runtime/dist/connectors/gmail.js +107 -0
- package/apps/runtime/dist/connectors/imap.js +217 -0
- package/apps/runtime/dist/connectors/index.js +266 -0
- package/apps/runtime/dist/connectors/mail.js +72 -0
- package/apps/runtime/dist/connectors/outlook.js +90 -0
- package/apps/runtime/dist/credits/credits.js +103 -0
- package/apps/runtime/dist/credits/estimate.js +132 -0
- package/apps/runtime/dist/db/adopt.js +149 -0
- package/apps/runtime/dist/db/client.js +146 -0
- package/apps/runtime/dist/db/control-schema.js +70 -0
- package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
- package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
- package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
- package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
- package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
- package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
- package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
- package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/schema.js +278 -0
- package/apps/runtime/dist/documents/parse.js +230 -0
- package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
- package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
- package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
- package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
- package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
- package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
- package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
- package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
- package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
- package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
- package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
- package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
- package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
- package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
- package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
- package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
- package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
- package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
- package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
- package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
- package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
- package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
- package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
- package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
- package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
- package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
- package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
- package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
- package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
- package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
- package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
- package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
- package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
- package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
- package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
- package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
- package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
- package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
- package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
- package/apps/runtime/dist/engine/asks.js +60 -0
- package/apps/runtime/dist/engine/events.js +46 -0
- package/apps/runtime/dist/engine/input.js +170 -0
- package/apps/runtime/dist/engine/prepare.js +51 -0
- package/apps/runtime/dist/engine/requirements.js +124 -0
- package/apps/runtime/dist/engine/resources.js +89 -0
- package/apps/runtime/dist/engine/runner.js +560 -0
- package/apps/runtime/dist/engine/steps.js +598 -0
- package/apps/runtime/dist/engine/template.js +183 -0
- package/apps/runtime/dist/engine/types.js +3 -0
- package/apps/runtime/dist/env.js +224 -0
- package/apps/runtime/dist/files/blobs.js +22 -0
- package/apps/runtime/dist/files/objects.js +86 -0
- package/apps/runtime/dist/files/storage.js +95 -0
- package/apps/runtime/dist/geocode.js +58 -0
- package/apps/runtime/dist/harness/claude.js +138 -0
- package/apps/runtime/dist/harness/codex.js +228 -0
- package/apps/runtime/dist/harness/cursor.js +93 -0
- package/apps/runtime/dist/harness/env.js +124 -0
- package/apps/runtime/dist/harness/gemini.js +126 -0
- package/apps/runtime/dist/harness/index.js +99 -0
- package/apps/runtime/dist/harness/model.js +267 -0
- package/apps/runtime/dist/harness/prompt.js +121 -0
- package/apps/runtime/dist/harness/terminal.js +193 -0
- package/apps/runtime/dist/harness/types.js +7 -0
- package/apps/runtime/dist/index.js +76 -0
- package/apps/runtime/dist/limits.js +57 -0
- package/apps/runtime/dist/link-preview.js +59 -0
- package/apps/runtime/dist/manage.js +151 -0
- package/apps/runtime/dist/mcp/auth.js +102 -0
- package/apps/runtime/dist/mcp/bridge.js +59 -0
- package/apps/runtime/dist/mcp/handler.js +42 -0
- package/apps/runtime/dist/mcp/scopes.js +5 -0
- package/apps/runtime/dist/mcp/tools.js +401 -0
- package/apps/runtime/dist/media/ffmpeg.js +121 -0
- package/apps/runtime/dist/media/generate.js +120 -0
- package/apps/runtime/dist/media/marking.js +121 -0
- package/apps/runtime/dist/media/transcribe.js +30 -0
- package/apps/runtime/dist/model-errors.js +3 -0
- package/apps/runtime/dist/models.js +489 -0
- package/apps/runtime/dist/plugin.js +61 -0
- package/apps/runtime/dist/render/chromium.js +85 -0
- package/apps/runtime/dist/render/convert.js +161 -0
- package/apps/runtime/dist/render/downloads.js +277 -0
- package/apps/runtime/dist/render/guard.js +25 -0
- package/apps/runtime/dist/routes/api.js +74 -0
- package/apps/runtime/dist/routes/delivery.js +52 -0
- package/apps/runtime/dist/routes/internal.js +49 -0
- package/apps/runtime/dist/routes/marketplace.js +36 -0
- package/apps/runtime/dist/routes/runs.js +805 -0
- package/apps/runtime/dist/routes/studio.js +702 -0
- package/apps/runtime/dist/routes/wizard-stream.js +30 -0
- package/apps/runtime/dist/running.js +39 -0
- package/apps/runtime/dist/sandbox/agentos.js +191 -0
- package/apps/runtime/dist/sandbox/docker.js +77 -0
- package/apps/runtime/dist/sandbox/index.js +30 -0
- package/apps/runtime/dist/sandbox/types.js +1 -0
- package/apps/runtime/dist/secrets/crypto.js +28 -0
- package/apps/runtime/dist/secrets/signing.js +34 -0
- package/apps/runtime/dist/secrets/vault.js +59 -0
- package/apps/runtime/dist/services/architect.js +92 -0
- package/apps/runtime/dist/services/brand.js +20 -0
- package/apps/runtime/dist/services/cloud.js +52 -0
- package/apps/runtime/dist/services/draft-events.js +27 -0
- package/apps/runtime/dist/services/errors.js +24 -0
- package/apps/runtime/dist/services/files.js +129 -0
- package/apps/runtime/dist/services/links.js +16 -0
- package/apps/runtime/dist/services/marketplace.js +292 -0
- package/apps/runtime/dist/services/package.js +202 -0
- package/apps/runtime/dist/services/profile.js +23 -0
- package/apps/runtime/dist/services/project-files.js +266 -0
- package/apps/runtime/dist/services/project-index.js +109 -0
- package/apps/runtime/dist/services/projects.js +181 -0
- package/apps/runtime/dist/services/runs.js +224 -0
- package/apps/runtime/dist/services/shares.js +129 -0
- package/apps/runtime/dist/services/widgets.js +34 -0
- package/apps/runtime/dist/services/wizards.js +342 -0
- package/apps/runtime/dist/settings.js +16 -0
- package/apps/runtime/dist/store/index.js +279 -0
- package/apps/runtime/dist/tenants/control.js +106 -0
- package/apps/runtime/dist/tenants/tenant.js +26 -0
- package/apps/runtime/dist/tools/browser.js +380 -0
- package/apps/runtime/dist/tools/connector.js +204 -0
- package/apps/runtime/dist/tools/index.js +234 -0
- package/apps/runtime/dist/tools/mail.js +104 -0
- package/apps/runtime/dist/tools/net-guard.js +59 -0
- package/apps/runtime/dist/tools/project.js +53 -0
- package/apps/runtime/dist/tools/shared.js +42 -0
- package/apps/runtime/dist/tools/store.js +238 -0
- package/apps/runtime/dist/update.js +118 -0
- package/apps/runtime/dist/widgets/bundle.js +99 -0
- package/apps/runtime/dist/widgets/render.js +312 -0
- package/apps/runtime/dist/widgets/runtime.js +84 -0
- package/apps/runtime/dist/widgets/script.js +30 -0
- package/apps/runtime/dist/widgets/step.js +192 -0
- package/apps/web/dist/ai-labels/ai-black.svg +20 -0
- package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
- package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
- package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
- package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
- package/apps/web/dist/ai-labels/ai-white.svg +21 -0
- package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
- package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
- package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
- package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
- package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
- package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
- package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
- package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
- package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
- package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
- package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
- package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
- package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
- package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
- package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
- package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
- package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
- package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
- package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
- package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
- package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
- package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
- package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
- package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
- package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
- package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
- package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
- package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
- package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
- package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
- package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
- package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
- package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
- package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
- package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
- package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
- package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
- package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
- package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
- package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
- package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
- package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
- package/apps/web/dist/embed.js +102 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/icons/apple-touch-icon.png +0 -0
- package/apps/web/dist/icons/icon-192.png +0 -0
- package/apps/web/dist/icons/icon-512.png +0 -0
- package/apps/web/dist/icons/icon-maskable-512.png +0 -0
- package/apps/web/dist/index.html +46 -0
- package/apps/web/dist/install.sh +532 -0
- package/apps/web/dist/manifest.webmanifest +20 -0
- package/apps/web/dist/sw.js +15 -0
- package/bin/wizards.mjs +13 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
- package/node_modules/@engenty-wizards/shared/package.json +10 -0
- package/package.json +62 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/wizard.md +8 -0
- package/plugin/skills/build-wizard/SKILL.md +27 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { decryptToken, defineConnector, } from "../shims/connections-sdk.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { headersFromRequired } from "./import-service.js";
|
|
4
|
+
import { executeHttpAction } from "./invoke/http-invoker.js";
|
|
5
|
+
import { mcpCallTool } from "./invoke/mcp-client.js";
|
|
6
|
+
import { connectionsRedirectUri, registerDynamicClient } from "./oauth-dcr.js";
|
|
7
|
+
/**
|
|
8
|
+
* Materialize a stored import record into a live `ConnectorDefinition`. From
|
|
9
|
+
* here on the framework owns everything — per-action operations, policy
|
|
10
|
+
* defaults by group, approvals, accounts, audit — identical to a hand-written
|
|
11
|
+
* provider.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* MCP request headers: the connector's required headers first, then the
|
|
15
|
+
* credential — auth must win a name collision, never the other way round.
|
|
16
|
+
*/
|
|
17
|
+
export function mcpHeaders(record, accessToken) {
|
|
18
|
+
const headers = headersFromRequired(record.required_headers);
|
|
19
|
+
const auth = record.auth_config;
|
|
20
|
+
if (auth.kind === "oauth2") {
|
|
21
|
+
return { ...headers, authorization: `Bearer ${accessToken}` };
|
|
22
|
+
}
|
|
23
|
+
if (auth.kind === "api_key" && auth.placement.in === "header") {
|
|
24
|
+
let credentials = {};
|
|
25
|
+
try {
|
|
26
|
+
credentials = JSON.parse(accessToken);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return headers;
|
|
30
|
+
}
|
|
31
|
+
const value = auth.placement.value_template.replace(/\{\{\s*([a-z0-9_]+)\s*\}\}/giu, (_m, key) => credentials[key] ?? "");
|
|
32
|
+
return { ...headers, [auth.placement.name]: value };
|
|
33
|
+
}
|
|
34
|
+
return headers;
|
|
35
|
+
}
|
|
36
|
+
function inputSchemaToZod(action) {
|
|
37
|
+
try {
|
|
38
|
+
const schema = z.fromJSONSchema(action.input_json_schema);
|
|
39
|
+
return schema instanceof z.ZodObject ? schema : z.object({}).loose();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function buildAction(record, action, live) {
|
|
46
|
+
const inputSchema = inputSchemaToZod(action);
|
|
47
|
+
if (!inputSchema) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const group = action.classification === "read"
|
|
51
|
+
? "read"
|
|
52
|
+
: action.classification === "destructive"
|
|
53
|
+
? "destructive"
|
|
54
|
+
: "write";
|
|
55
|
+
return {
|
|
56
|
+
description: action.description.slice(0, 1024),
|
|
57
|
+
group,
|
|
58
|
+
handler: (input, ctx) => {
|
|
59
|
+
// Read the record through the live resolver: refresh updates base_url/
|
|
60
|
+
// auth without re-binding the already-registered operation closures.
|
|
61
|
+
const current = live();
|
|
62
|
+
const args = (input ?? {});
|
|
63
|
+
if (action.invoke.kind === "mcp") {
|
|
64
|
+
return mcpCallTool({
|
|
65
|
+
args,
|
|
66
|
+
endpoint: current.source_url,
|
|
67
|
+
headers: mcpHeaders(current, ctx.accessToken),
|
|
68
|
+
toolName: action.invoke.tool_name,
|
|
69
|
+
transport: current.mcp_transport,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return executeHttpAction({
|
|
73
|
+
accessToken: ctx.accessToken,
|
|
74
|
+
fetchImpl: ctx.fetchImpl,
|
|
75
|
+
input: args,
|
|
76
|
+
invoke: action.invoke,
|
|
77
|
+
record: current,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
id: action.id,
|
|
81
|
+
inputSchema,
|
|
82
|
+
summary: action.summary.slice(0, 256),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function buildAuth(record, live, persistOAuthClient) {
|
|
86
|
+
const stored = record.auth_config;
|
|
87
|
+
if (stored.kind === "oauth2") {
|
|
88
|
+
const dcr = stored.dcr === true && Boolean(stored.registration_endpoint?.trim());
|
|
89
|
+
return {
|
|
90
|
+
kind: "oauth2",
|
|
91
|
+
oauth2: {
|
|
92
|
+
authUrl: stored.auth_url,
|
|
93
|
+
baseScopes: stored.scopes,
|
|
94
|
+
dynamicClientRegistration: dcr,
|
|
95
|
+
registerClient: dcr && persistOAuthClient
|
|
96
|
+
? async () => {
|
|
97
|
+
const current = live();
|
|
98
|
+
// Public DCR clients store only client_id_enc (empty secret).
|
|
99
|
+
if (current.client_id_enc) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const endpoint = current.auth_config.kind === "oauth2"
|
|
103
|
+
? current.auth_config.registration_endpoint
|
|
104
|
+
: stored.registration_endpoint;
|
|
105
|
+
if (!endpoint) {
|
|
106
|
+
throw new Error(`imported connector ${current.id} has no OAuth registration endpoint`);
|
|
107
|
+
}
|
|
108
|
+
const registered = await registerDynamicClient({
|
|
109
|
+
clientName: current.name,
|
|
110
|
+
redirectUri: connectionsRedirectUri(),
|
|
111
|
+
registrationEndpoint: endpoint,
|
|
112
|
+
scopes: current.auth_config.kind === "oauth2"
|
|
113
|
+
? current.auth_config.scopes
|
|
114
|
+
: stored.scopes,
|
|
115
|
+
});
|
|
116
|
+
await persistOAuthClient(registered);
|
|
117
|
+
}
|
|
118
|
+
: undefined,
|
|
119
|
+
resolveClientCredentials: () => {
|
|
120
|
+
const current = live();
|
|
121
|
+
if (!current.client_id_enc) {
|
|
122
|
+
return Promise.reject(new Error(`imported connector ${current.id} has no OAuth client credentials — set them in the import console`));
|
|
123
|
+
}
|
|
124
|
+
return Promise.resolve({
|
|
125
|
+
clientId: decryptToken(current.client_id_enc),
|
|
126
|
+
clientSecret: current.client_secret_enc
|
|
127
|
+
? decryptToken(current.client_secret_enc)
|
|
128
|
+
: "",
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
scopeSeparator: stored.scope_separator ?? " ",
|
|
132
|
+
tokenUrl: stored.token_url,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (stored.kind === "api_key") {
|
|
137
|
+
return {
|
|
138
|
+
apiKey: {
|
|
139
|
+
fields: stored.fields.map((field) => ({
|
|
140
|
+
key: field.key,
|
|
141
|
+
label: field.label,
|
|
142
|
+
required: field.required ?? true,
|
|
143
|
+
secret: field.secret ?? true,
|
|
144
|
+
})),
|
|
145
|
+
// No live probe on connect: imported specs give no reliable cheap
|
|
146
|
+
// "whoami" endpoint; a bad key surfaces on first action instead.
|
|
147
|
+
verify: (credentials) => {
|
|
148
|
+
for (const field of stored.fields) {
|
|
149
|
+
if ((field.required ?? true) && !credentials[field.key]) {
|
|
150
|
+
return Promise.reject(new Error(`missing credential field "${field.key}"`));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return Promise.resolve({ label: record.domain });
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
kind: "api_key",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// "none": model as api_key with zero fields so connect works without input.
|
|
160
|
+
return {
|
|
161
|
+
apiKey: {
|
|
162
|
+
fields: [],
|
|
163
|
+
verify: () => Promise.resolve({ label: record.domain }),
|
|
164
|
+
},
|
|
165
|
+
kind: "api_key",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export function buildImportedConnector(record, resolveLive, persistOAuthClient) {
|
|
169
|
+
const live = () => resolveLive?.() ?? record;
|
|
170
|
+
const actions = [];
|
|
171
|
+
const skippedActions = [];
|
|
172
|
+
for (const normalized of record.actions) {
|
|
173
|
+
const action = buildAction(record, normalized, live);
|
|
174
|
+
if (action) {
|
|
175
|
+
actions.push(action);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
skippedActions.push(normalized.id);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const connector = defineConnector({
|
|
182
|
+
actions,
|
|
183
|
+
auth: buildAuth(record, live, persistOAuthClient),
|
|
184
|
+
description: `${record.name} — imported from ${record.domain} (${record.source_kind})`,
|
|
185
|
+
icon: "plug",
|
|
186
|
+
id: record.id,
|
|
187
|
+
moduleId: "connections-external",
|
|
188
|
+
name: record.name,
|
|
189
|
+
tenantId: record.tenant_id,
|
|
190
|
+
toolPrefix: record.tool_prefix,
|
|
191
|
+
});
|
|
192
|
+
return { connector, skippedActions };
|
|
193
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import-time rejection. Routes answer 422 for this and 502 for everything
|
|
3
|
+
* else, so anything an admin can fix by choosing a different source, filling a
|
|
4
|
+
* field, or picking another surface must be raised as this error.
|
|
5
|
+
*/
|
|
6
|
+
export class ImportValidationError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ImportValidationError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { encryptToken, getConnectorDefinition } from "../shims/connections-sdk.js";
|
|
3
|
+
import { ImportValidationError } from "./errors.js";
|
|
4
|
+
import { mapAuth } from "./importer/map-auth.js";
|
|
5
|
+
import { normalizeMcpServer } from "./importer/normalize-mcp.js";
|
|
6
|
+
import { MAX_SPEC_BYTES, normalizeOpenApiSpec, } from "./importer/normalize-openapi.js";
|
|
7
|
+
import { isUnauthorizedMcpError } from "./invoke/mcp-client.js";
|
|
8
|
+
import { createGuardedFetch, fetchTextBounded } from "./net/guarded-fetch.js";
|
|
9
|
+
import { resolveMcpTransport, } from "./registry-client.js";
|
|
10
|
+
/**
|
|
11
|
+
* Import/refresh pipeline shared by the admin routes: fetch source → apply the
|
|
12
|
+
* registry's spec overrides → normalize → map auth → assemble the stored
|
|
13
|
+
* record. Pure of DB and registry-transport concerns — the routes/plugin own
|
|
14
|
+
* persistence, registry lookups and (re-)registration.
|
|
15
|
+
*
|
|
16
|
+
* Every registry fact this pipeline reads is either applied or refuses the
|
|
17
|
+
* import. A surface that needs a URL variable, an environment-sourced header,
|
|
18
|
+
* or auth we cannot express never becomes a half-working connector.
|
|
19
|
+
*/
|
|
20
|
+
const SPEC_FETCH_TIMEOUT_MS = 30_000;
|
|
21
|
+
/** Operation/tool ids must satisfy core's strict snake_case tool-id contract. */
|
|
22
|
+
const TOOL_PREFIX_RE = /^[a-z][a-z0-9_]{1,30}$/u;
|
|
23
|
+
export function validateConnectorNaming(params) {
|
|
24
|
+
if (!/^[a-z][a-z0-9-]{1,59}$/u.test(params.id)) {
|
|
25
|
+
throw new ImportValidationError(`connector id "${params.id}" must be kebab-case (a-z, 0-9, -)`);
|
|
26
|
+
}
|
|
27
|
+
if (!TOOL_PREFIX_RE.test(params.toolPrefix)) {
|
|
28
|
+
throw new ImportValidationError(`tool prefix "${params.toolPrefix}" must be snake_case, start with a letter, and stay short`);
|
|
29
|
+
}
|
|
30
|
+
const existing = getConnectorDefinition(params.id, params.tenantId);
|
|
31
|
+
if (existing && existing.moduleId !== "connections-external") {
|
|
32
|
+
throw new ImportValidationError(`connector id "${params.id}" collides with built-in connector "${existing.name}"`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Registry surface → import decisions
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/** Connector id suggested by a registry surface slug ("stripe-mcp-server"). */
|
|
39
|
+
export function connectorIdFromSlug(slug) {
|
|
40
|
+
return slug
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/gu, "-")
|
|
43
|
+
.replace(/^[-0-9]+|-+$/gu, "")
|
|
44
|
+
.slice(0, 60);
|
|
45
|
+
}
|
|
46
|
+
/** Tool prefix suggested by a registry surface slug ("stripe_mcp_server"). */
|
|
47
|
+
export function toolPrefixFromSlug(slug) {
|
|
48
|
+
return slug
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/[^a-z0-9]+/gu, "_")
|
|
51
|
+
.replace(/^[_0-9]+|_+$/gu, "")
|
|
52
|
+
.slice(0, 30);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Static headers the API requires on every request. The registry also sources
|
|
56
|
+
* headers from the environment of the machine that talks to the API; those
|
|
57
|
+
* have no value we could store, so they block the import rather than being
|
|
58
|
+
* dropped on the floor.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveRequiredHeaders(surface) {
|
|
61
|
+
const headers = [];
|
|
62
|
+
for (const header of surface.required_headers) {
|
|
63
|
+
const kind = header.source?.kind ?? "unknown";
|
|
64
|
+
if (kind !== "static") {
|
|
65
|
+
throw new ImportValidationError(`the registry requires header "${header.name}" from a ${kind} source — only static header values can be stored on an imported connector`);
|
|
66
|
+
}
|
|
67
|
+
const value = header.source?.value;
|
|
68
|
+
if (!value) {
|
|
69
|
+
throw new ImportValidationError(`the registry requires header "${header.name}" but publishes no value for it`);
|
|
70
|
+
}
|
|
71
|
+
headers.push({
|
|
72
|
+
description: header.description ?? null,
|
|
73
|
+
name: header.name,
|
|
74
|
+
value,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return headers;
|
|
78
|
+
}
|
|
79
|
+
/** Registry variables have no value at import time — reject, never guess. */
|
|
80
|
+
export function assertNoRegistryVariables(surface) {
|
|
81
|
+
if (surface.variables.length === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const names = surface.variables.map((variable) => variable.name).join(", ");
|
|
85
|
+
throw new ImportValidationError(`this surface is templated on ${names} (${surface.variables
|
|
86
|
+
.map((variable) => variable.resolveFrom ?? variable.name)
|
|
87
|
+
.join("; ")}) — per-tenant URL variables are not supported; paste a resolved spec or endpoint URL instead`);
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Fetch + normalize
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
export async function fetchSpecText(url, fetchImpl = createGuardedFetch()) {
|
|
93
|
+
return await fetchTextBounded({
|
|
94
|
+
fetchImpl,
|
|
95
|
+
headers: { accept: "application/json, application/yaml, text/yaml, */*" },
|
|
96
|
+
maxBytes: MAX_SPEC_BYTES,
|
|
97
|
+
signal: AbortSignal.timeout(SPEC_FETCH_TIMEOUT_MS),
|
|
98
|
+
url,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Registry surface whose MCP/API calls need a credential. */
|
|
102
|
+
export function surfaceRequiresAuth(surface) {
|
|
103
|
+
return surface?.auth.status === "required";
|
|
104
|
+
}
|
|
105
|
+
function emptyMcpNormalize() {
|
|
106
|
+
return {
|
|
107
|
+
actions: [],
|
|
108
|
+
applied_overrides: 0,
|
|
109
|
+
base_url: null,
|
|
110
|
+
description: null,
|
|
111
|
+
dropped_count: 0,
|
|
112
|
+
security_schemes: null,
|
|
113
|
+
skipped: [],
|
|
114
|
+
title: null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export function deferredMcpPrepared(sourceUrl) {
|
|
118
|
+
return {
|
|
119
|
+
deferred_mcp_tools: true,
|
|
120
|
+
normalized: emptyMcpNormalize(),
|
|
121
|
+
spec_hash: createHash("sha256")
|
|
122
|
+
.update(`deferred-mcp:${sourceUrl}`)
|
|
123
|
+
.digest("hex"),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Fetch + normalize one source. openapi → spec text; mcp → live tools/list. */
|
|
127
|
+
export async function prepareSource(params) {
|
|
128
|
+
const fetchImpl = params.fetchImpl ?? createGuardedFetch();
|
|
129
|
+
const overrides = params.specOverrides ?? [];
|
|
130
|
+
if (params.sourceKind === "openapi") {
|
|
131
|
+
const specText = await fetchSpecText(params.sourceUrl, fetchImpl);
|
|
132
|
+
const normalized = await normalizeOpenApiSpec(specText, {
|
|
133
|
+
specOverrides: overrides,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
normalized,
|
|
137
|
+
// Overrides are part of what was imported: a changed patch must show up
|
|
138
|
+
// as a changed spec hash on refresh.
|
|
139
|
+
spec_hash: createHash("sha256")
|
|
140
|
+
.update(specText)
|
|
141
|
+
.update(JSON.stringify(overrides))
|
|
142
|
+
.digest("hex"),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (params.deferMcpTools) {
|
|
146
|
+
return deferredMcpPrepared(params.sourceUrl);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const normalized = await normalizeMcpServer({
|
|
150
|
+
endpoint: params.sourceUrl,
|
|
151
|
+
fetchImpl,
|
|
152
|
+
headers: {
|
|
153
|
+
...headersFromRequired(params.requiredHeaders ?? []),
|
|
154
|
+
...params.mcpHeaders,
|
|
155
|
+
},
|
|
156
|
+
transport: params.transport ?? null,
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
normalized,
|
|
160
|
+
spec_hash: createHash("sha256")
|
|
161
|
+
.update(JSON.stringify(normalized.actions))
|
|
162
|
+
.digest("hex"),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if ((params.deferOnUnauthorized ?? true) && isUnauthorizedMcpError(error)) {
|
|
167
|
+
return deferredMcpPrepared(params.sourceUrl);
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** Stored required headers as a request header map. */
|
|
173
|
+
export function headersFromRequired(headers) {
|
|
174
|
+
const out = {};
|
|
175
|
+
for (const header of headers) {
|
|
176
|
+
out[header.name] = header.value;
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
export function assembleRecord(params) {
|
|
181
|
+
validateConnectorNaming({
|
|
182
|
+
id: params.id,
|
|
183
|
+
tenantId: params.tenantId,
|
|
184
|
+
toolPrefix: params.toolPrefix,
|
|
185
|
+
});
|
|
186
|
+
const { normalized } = params.prepared;
|
|
187
|
+
const warnings = [];
|
|
188
|
+
const surface = params.surface ?? null;
|
|
189
|
+
if (surface) {
|
|
190
|
+
assertNoRegistryVariables(surface);
|
|
191
|
+
}
|
|
192
|
+
const requiredHeaders = surface ? resolveRequiredHeaders(surface) : [];
|
|
193
|
+
const mapped = mapAuth({
|
|
194
|
+
discover: params.discover ?? null,
|
|
195
|
+
securitySchemes: normalized.security_schemes,
|
|
196
|
+
sourceKind: params.sourceKind,
|
|
197
|
+
surface,
|
|
198
|
+
});
|
|
199
|
+
if (!mapped.ok) {
|
|
200
|
+
throw new ImportValidationError(mapped.reason);
|
|
201
|
+
}
|
|
202
|
+
const auth = mapped.auth;
|
|
203
|
+
if (auth.kind === "oauth2" && !params.oauthClient) {
|
|
204
|
+
if (auth.dcr && auth.registration_endpoint) {
|
|
205
|
+
warnings.push("oauth2 client will be registered on first authenticate (dynamic client registration)");
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
warnings.push("oauth2 connector imported without client credentials — connects will fail until they are set");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const filter = params.actionFilter?.length
|
|
212
|
+
? new Set(params.actionFilter)
|
|
213
|
+
: null;
|
|
214
|
+
const actions = filter
|
|
215
|
+
? normalized.actions.filter((action) => filter.has(action.id))
|
|
216
|
+
: normalized.actions;
|
|
217
|
+
const deferredMcp = params.prepared.deferred_mcp_tools === true && params.sourceKind === "mcp";
|
|
218
|
+
if (actions.length === 0 && !deferredMcp) {
|
|
219
|
+
throw new ImportValidationError("no importable actions (empty spec, all filtered out, or all skipped)");
|
|
220
|
+
}
|
|
221
|
+
if (deferredMcp) {
|
|
222
|
+
warnings.push("tools load once an account is connected");
|
|
223
|
+
}
|
|
224
|
+
if (normalized.dropped_count > 0) {
|
|
225
|
+
warnings.push(`${normalized.dropped_count} operation(s) dropped by the per-connector cap`);
|
|
226
|
+
}
|
|
227
|
+
if (normalized.skipped.length > 0) {
|
|
228
|
+
warnings.push(`${normalized.skipped.length} operation(s) skipped: ${normalized.skipped
|
|
229
|
+
.slice(0, 5)
|
|
230
|
+
.map((s) => `${s.id} (${s.reason})`)
|
|
231
|
+
.join(", ")}${normalized.skipped.length > 5 ? ", …" : ""}`);
|
|
232
|
+
}
|
|
233
|
+
if (normalized.applied_overrides > 0) {
|
|
234
|
+
warnings.push(`${normalized.applied_overrides} registry spec override(s) applied before normalization`);
|
|
235
|
+
}
|
|
236
|
+
const baseUrl = params.baseUrlOverride ?? normalized.base_url ?? null;
|
|
237
|
+
if (params.sourceKind === "openapi" && !baseUrl) {
|
|
238
|
+
throw new ImportValidationError("spec declares no server URL — provide base_url explicitly");
|
|
239
|
+
}
|
|
240
|
+
const record = {
|
|
241
|
+
actions,
|
|
242
|
+
auth_config: auth,
|
|
243
|
+
base_url: baseUrl,
|
|
244
|
+
client_id_enc: params.oauthClient
|
|
245
|
+
? encryptToken(params.oauthClient.clientId)
|
|
246
|
+
: null,
|
|
247
|
+
client_secret_enc: params.oauthClient
|
|
248
|
+
? encryptToken(params.oauthClient.clientSecret)
|
|
249
|
+
: null,
|
|
250
|
+
domain: params.domain,
|
|
251
|
+
id: params.id,
|
|
252
|
+
imported_at: new Date().toISOString(),
|
|
253
|
+
imported_by: params.importedBy,
|
|
254
|
+
mcp_transport: params.sourceKind === "mcp"
|
|
255
|
+
? ((surface ? resolveMcpTransport(surface) : null) ?? "streamable-http")
|
|
256
|
+
: null,
|
|
257
|
+
name: params.name ?? normalized.title ?? surface?.name ?? params.domain,
|
|
258
|
+
refreshed_at: null,
|
|
259
|
+
registry_snapshot: params.rawDiscover ?? null,
|
|
260
|
+
registry_surface_slug: surface?.slug ?? null,
|
|
261
|
+
required_headers: requiredHeaders,
|
|
262
|
+
source_kind: params.sourceKind,
|
|
263
|
+
source_url: params.sourceUrl,
|
|
264
|
+
spec_hash: params.prepared.spec_hash,
|
|
265
|
+
status: "enabled",
|
|
266
|
+
tenant_id: params.tenantId,
|
|
267
|
+
tool_prefix: params.toolPrefix,
|
|
268
|
+
};
|
|
269
|
+
return { record, warnings };
|
|
270
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map an HTTP method (plus path hints) onto the connections-framework action
|
|
3
|
+
* groups. Groups drive default policy (read→allow, write/destructive→ask) and
|
|
4
|
+
* the approval contract, so lean conservative: anything ambiguous is `write`,
|
|
5
|
+
* clearly irreversible verbs are `destructive`.
|
|
6
|
+
*/
|
|
7
|
+
const DESTRUCTIVE_PATH_HINT = /\b(delete|remove|destroy|purge|revoke|cancel|terminate)\b/iu;
|
|
8
|
+
export function classifyHttpOperation(method, pathTemplate) {
|
|
9
|
+
const m = method.toLowerCase();
|
|
10
|
+
if (m === "get" || m === "head" || m === "options") {
|
|
11
|
+
return "read";
|
|
12
|
+
}
|
|
13
|
+
if (m === "delete") {
|
|
14
|
+
return "destructive";
|
|
15
|
+
}
|
|
16
|
+
if (DESTRUCTIVE_PATH_HINT.test(pathTemplate)) {
|
|
17
|
+
return "destructive";
|
|
18
|
+
}
|
|
19
|
+
return "write";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* MCP tool annotations → classification. `readOnlyHint` is trusted for reads;
|
|
23
|
+
* `destructiveHint` for destructive; everything else defaults to `write`
|
|
24
|
+
* (server-provided hints are advisory, so no hint never yields `read`).
|
|
25
|
+
*/
|
|
26
|
+
export function classifyMcpTool(annotations) {
|
|
27
|
+
if (annotations.readOnlyHint === true) {
|
|
28
|
+
return "read";
|
|
29
|
+
}
|
|
30
|
+
if (annotations.destructiveHint === true) {
|
|
31
|
+
return "destructive";
|
|
32
|
+
}
|
|
33
|
+
return "write";
|
|
34
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { discoverOAuthFacts, } from "../registry-client.js";
|
|
2
|
+
/** Map an OpenAPI 3.x securitySchemes object (spec wins over registry facts). */
|
|
3
|
+
export function mapAuthFromSecuritySchemes(schemes) {
|
|
4
|
+
const entries = Object.entries(schemes);
|
|
5
|
+
// Prefer a static API key / bearer when the spec also advertises OAuth —
|
|
6
|
+
// personal access tokens work without an admin-registered developer app.
|
|
7
|
+
// Figma REST is the canonical case (PersonalAccessToken + OAuth2).
|
|
8
|
+
const bearer = entries.find(([, s]) => s.type === "http" && s.scheme?.toLowerCase() === "bearer");
|
|
9
|
+
if (bearer) {
|
|
10
|
+
return {
|
|
11
|
+
fields: [
|
|
12
|
+
{ key: "api_token", label: "API token", required: true, secret: true },
|
|
13
|
+
],
|
|
14
|
+
kind: "api_key",
|
|
15
|
+
placement: {
|
|
16
|
+
in: "header",
|
|
17
|
+
name: "Authorization",
|
|
18
|
+
value_template: "Bearer {{api_token}}",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const apiKey = entries.find(([, s]) => s.type === "apiKey" && (s.in === "header" || s.in === "query"));
|
|
23
|
+
if (apiKey) {
|
|
24
|
+
const scheme = apiKey[1];
|
|
25
|
+
return {
|
|
26
|
+
fields: [
|
|
27
|
+
{ key: "api_key", label: "API key", required: true, secret: true },
|
|
28
|
+
],
|
|
29
|
+
kind: "api_key",
|
|
30
|
+
placement: {
|
|
31
|
+
in: scheme.in === "query" ? "query" : "header",
|
|
32
|
+
name: scheme.name ?? "X-API-Key",
|
|
33
|
+
value_template: "{{api_key}}",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const oauth = entries.find(([, s]) => s.type === "oauth2" && s.flows?.authorizationCode);
|
|
38
|
+
if (oauth) {
|
|
39
|
+
const flow = oauth[1].flows?.authorizationCode;
|
|
40
|
+
if (flow?.authorizationUrl && flow.tokenUrl) {
|
|
41
|
+
return {
|
|
42
|
+
auth_url: flow.authorizationUrl,
|
|
43
|
+
kind: "oauth2",
|
|
44
|
+
scopes: Object.keys(flow.scopes ?? {}),
|
|
45
|
+
token_url: flow.tokenUrl,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/** Fallback: map the domain's discovered OAuth facts. */
|
|
52
|
+
export function mapAuthFromRegistry(discover) {
|
|
53
|
+
const oauth = discoverOAuthFacts(discover);
|
|
54
|
+
if (oauth?.authorizationEndpoint &&
|
|
55
|
+
oauth.tokenEndpoint &&
|
|
56
|
+
(oauth.grantTypes.length === 0 ||
|
|
57
|
+
oauth.grantTypes.includes("authorization_code"))) {
|
|
58
|
+
const dcr = oauth.dcr === true && Boolean(oauth.registrationEndpoint?.trim());
|
|
59
|
+
return {
|
|
60
|
+
auth_url: oauth.authorizationEndpoint,
|
|
61
|
+
kind: "oauth2",
|
|
62
|
+
scopes: oauth.scopes,
|
|
63
|
+
token_url: oauth.tokenEndpoint,
|
|
64
|
+
...(dcr
|
|
65
|
+
? {
|
|
66
|
+
dcr: true,
|
|
67
|
+
registration_endpoint: oauth.registrationEndpoint,
|
|
68
|
+
}
|
|
69
|
+
: {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/** A credential id reduced to a stored field key. */
|
|
75
|
+
function fieldKey(credentialId) {
|
|
76
|
+
const key = credentialId
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.replace(/[^a-z0-9]+/gu, "_")
|
|
79
|
+
.replace(/^_+|_+$/gu, "")
|
|
80
|
+
.slice(0, 40);
|
|
81
|
+
return key || "api_key";
|
|
82
|
+
}
|
|
83
|
+
/** Whether a mechanic describes an OAuth flow rather than a static credential. */
|
|
84
|
+
function isOauthMechanics(mechanics) {
|
|
85
|
+
if (mechanics.source === "well-known") {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return (mechanics.source === "spec" &&
|
|
89
|
+
(mechanics.scheme ?? "").toLowerCase().includes("oauth"));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Map one registry auth alternative. Returns null when this alternative is not
|
|
93
|
+
* expressible — the caller tries the next one before giving up.
|
|
94
|
+
*/
|
|
95
|
+
function mapSurfaceAuthEntry(params) {
|
|
96
|
+
const { entry } = params;
|
|
97
|
+
if (entry.use.length === 0) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (entry.use.length === 1 && isOauthMechanics(entry.use[0].mechanics)) {
|
|
101
|
+
return params.discover ? mapAuthFromRegistry(params.discover) : null;
|
|
102
|
+
}
|
|
103
|
+
// A single header/query credential is the only static placement the stored
|
|
104
|
+
// config can express; multi-credential alternatives (client id + secret
|
|
105
|
+
// headers) have no single placement and fall through to the next entry.
|
|
106
|
+
if (entry.use.length !== 1) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const { id, mechanics } = entry.use[0];
|
|
110
|
+
if (mechanics.source !== "http") {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const placementIn = mechanics.in === "query" ? "query" : "header";
|
|
114
|
+
const name = mechanics.headerName ?? (placementIn === "query" ? "api_key" : "X-API-Key");
|
|
115
|
+
if (placementIn === "header" && !mechanics.headerName) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const key = fieldKey(id);
|
|
119
|
+
const scheme = mechanics.scheme?.trim();
|
|
120
|
+
return {
|
|
121
|
+
fields: [
|
|
122
|
+
{
|
|
123
|
+
key,
|
|
124
|
+
label: params.credentialLabel(id),
|
|
125
|
+
required: true,
|
|
126
|
+
secret: true,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
kind: "api_key",
|
|
130
|
+
placement: {
|
|
131
|
+
in: placementIn,
|
|
132
|
+
name,
|
|
133
|
+
value_template: scheme ? `${scheme} {{${key}}}` : `{{${key}}}`,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Human-readable summary of what the registry said, for reject reasons. */
|
|
138
|
+
function describeSurfaceAuth(surface) {
|
|
139
|
+
if (surface.auth.entries.length === 0) {
|
|
140
|
+
return `auth status "${surface.auth.status}" with no credential facts`;
|
|
141
|
+
}
|
|
142
|
+
const shapes = surface.auth.entries.map((entry) => entry.use.map((use) => `${use.id} via ${use.mechanics.source}`).join(" + "));
|
|
143
|
+
return `alternatives: ${shapes.join("; ")}`;
|
|
144
|
+
}
|
|
145
|
+
export function mapAuth(params) {
|
|
146
|
+
const surface = params.surface ?? null;
|
|
147
|
+
if (surface?.auth.status === "none") {
|
|
148
|
+
return { auth: { kind: "none" }, ok: true };
|
|
149
|
+
}
|
|
150
|
+
if (params.securitySchemes) {
|
|
151
|
+
const fromSpec = mapAuthFromSecuritySchemes(params.securitySchemes);
|
|
152
|
+
if (fromSpec) {
|
|
153
|
+
return { auth: fromSpec, ok: true };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (surface) {
|
|
157
|
+
const credentials = params.discover?.credentials ?? {};
|
|
158
|
+
const credentialLabel = (id) => credentials[id]?.label ?? "API credential";
|
|
159
|
+
for (const entry of surface.auth.entries) {
|
|
160
|
+
const mapped = mapSurfaceAuthEntry({
|
|
161
|
+
credentialLabel,
|
|
162
|
+
discover: params.discover ?? null,
|
|
163
|
+
entry,
|
|
164
|
+
});
|
|
165
|
+
if (mapped) {
|
|
166
|
+
return { auth: mapped, ok: true };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (params.discover) {
|
|
171
|
+
const fromRegistry = mapAuthFromRegistry(params.discover);
|
|
172
|
+
if (fromRegistry) {
|
|
173
|
+
return { auth: fromRegistry, ok: true };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const found = surface
|
|
177
|
+
? describeSurfaceAuth(surface)
|
|
178
|
+
: params.sourceKind === "openapi"
|
|
179
|
+
? "no usable securitySchemes in the spec"
|
|
180
|
+
: "no registry auth facts for this endpoint";
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
reason: `cannot map authentication (${found}) — supported: no auth, OAuth 2 authorization_code, or a single header/query API key`,
|
|
184
|
+
};
|
|
185
|
+
}
|