@zackbart/connecta 0.37.1 → 0.39.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 +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// remoteMcp() options and validation: the closed option set, resource and
|
|
2
|
+
// credential header rules, and every construction-time refusal, in order.
|
|
3
|
+
import { authorizationParamsFor } from "../../auth/oauth-authorization-params.js";
|
|
4
|
+
import { assertRemoteOAuthClient } from "../../auth/downstream-client-metadata.js";
|
|
5
|
+
import { assertOAuthScope } from "../../auth/downstream-oauth.js";
|
|
6
|
+
import { shortSecretWarning } from "../../sent-secrets.js";
|
|
7
|
+
import { assertKnownOptions, keys, optionsOf, strings } from "../../config-schema.js";
|
|
8
|
+
import { READ_ONLY, CALL_ADMISSION, REMOTE_MCP_AUTH, USAGE_GUIDE } from "../option-shapes.js";
|
|
9
|
+
/** The closed options remoteMcp() accepts; see `assertKnownOptions`. */
|
|
10
|
+
const REMOTE_MCP_OPTIONS = optionsOf()({
|
|
11
|
+
...keys("url", "title", "description", "authScope", "maxResultBytes", "versionNegotiation", "redirects", "requireHttps", "logger", "fetch", "_transportFactory", "skills"),
|
|
12
|
+
readOnly: READ_ONLY,
|
|
13
|
+
callAdmission: CALL_ADMISSION,
|
|
14
|
+
usageGuide: USAGE_GUIDE,
|
|
15
|
+
auth: REMOTE_MCP_AUTH,
|
|
16
|
+
headers: strings(),
|
|
17
|
+
});
|
|
18
|
+
/** Extra headers are immutable code configuration, not another auth channel. */
|
|
19
|
+
function extraResourceHeaders(id, opts) {
|
|
20
|
+
if (opts.headers === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
const refuse = () => new Error(`remoteMcp(${JSON.stringify(id)}).headers requires non-secret resource headers without reserved or auth-owned names; use auth for credentials.`);
|
|
23
|
+
if (opts.auth?.type === "headers")
|
|
24
|
+
throw refuse();
|
|
25
|
+
const authNames = new Set([
|
|
26
|
+
"authorization",
|
|
27
|
+
...(opts.auth?.type === "credential" ? [opts.auth.header?.trim().toLowerCase() || "authorization"] : []),
|
|
28
|
+
...(opts.auth?.type === "request" ? Object.keys(opts.auth.headers ?? {}).map((name) => name.toLowerCase()) : []),
|
|
29
|
+
]);
|
|
30
|
+
const normalized = Object.create(null);
|
|
31
|
+
for (const [name, value] of Object.entries(opts.headers)) {
|
|
32
|
+
const lower = name.toLowerCase();
|
|
33
|
+
if (!HEADER_NAME_TOKEN.test(name) ||
|
|
34
|
+
typeof value !== "string" ||
|
|
35
|
+
/[\r\n]/.test(value) ||
|
|
36
|
+
Object.hasOwn(normalized, lower) ||
|
|
37
|
+
authNames.has(lower) ||
|
|
38
|
+
/^(?:accept|cookie|host|origin|connection|keep-alive|proxy-authenticate|proxy-authorization|te|trailer|transfer-encoding|upgrade|last-event-id|dpop|www-authenticate)$/.test(lower) ||
|
|
39
|
+
/^(?:content-|mcp-)/.test(lower) ||
|
|
40
|
+
/(?:api[-_]?key|token|secret|password|credential|authorization|signature|session)/.test(lower))
|
|
41
|
+
throw refuse();
|
|
42
|
+
try {
|
|
43
|
+
normalized[lower] = new Headers({ [lower]: value }).get(lower);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw refuse();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze(Object.fromEntries(Object.entries(normalized).sort(([a], [b]) => a.localeCompare(b))));
|
|
50
|
+
}
|
|
51
|
+
const encoder = new TextEncoder();
|
|
52
|
+
/** Base64 of a UTF-8 string, Web-API only so the core still runs on workerd. */
|
|
53
|
+
function base64Utf8(value) {
|
|
54
|
+
let binary = "";
|
|
55
|
+
for (const byte of encoder.encode(value)) {
|
|
56
|
+
binary += String.fromCharCode(byte);
|
|
57
|
+
}
|
|
58
|
+
return btoa(binary);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Assemble the one header a credential-auth connector sends.
|
|
62
|
+
*
|
|
63
|
+
* A scheme ending in `Basic` names HTTP Basic credentials wherever a provider
|
|
64
|
+
* nests it, so the `user:secret` it frames is base64-encoded — that is what
|
|
65
|
+
* makes plain `Basic` and nested `Bearer Basic` one rule instead of two.
|
|
66
|
+
*/
|
|
67
|
+
export function credentialHeaderValue(scheme, value) {
|
|
68
|
+
if (scheme === null)
|
|
69
|
+
return value;
|
|
70
|
+
return /(?:^|\s)basic$/i.test(scheme) ? `${scheme} ${base64Utf8(value)}` : `${scheme} ${value}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* True when a stored credential carries a character a header cannot.
|
|
74
|
+
*
|
|
75
|
+
* The fetch specification refuses NUL, CR, and LF outright, and the runtime
|
|
76
|
+
* that refuses them says so in a `TypeError` that quotes the whole offending
|
|
77
|
+
* value — a message that would otherwise travel to the agent. The remaining C0
|
|
78
|
+
* controls and DEL are refused here too: no real API key contains one, and a
|
|
79
|
+
* paste that picked one up is a paste to redo rather than a request to send.
|
|
80
|
+
* Leading and trailing whitespace is already gone by the time this runs.
|
|
81
|
+
*
|
|
82
|
+
* A scan rather than a regular expression, because a character class over the
|
|
83
|
+
* control range is exactly what `no-control-regex` exists to flag, and the
|
|
84
|
+
* suppression would be less readable than the loop it suppressed.
|
|
85
|
+
*/
|
|
86
|
+
export function carriesIllegalHeaderChar(value) {
|
|
87
|
+
for (let index = 0; index < value.length; index++) {
|
|
88
|
+
const code = value.charCodeAt(index);
|
|
89
|
+
if (code <= 0x1f || code === 0x7f)
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
/** RFC 9110 field-name token, checked once at construction. */
|
|
95
|
+
const HEADER_NAME_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
96
|
+
function isLoopbackHost(hostname) {
|
|
97
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
|
|
98
|
+
}
|
|
99
|
+
/** Validate `opts` once, at construction, and derive what every seam reads. */
|
|
100
|
+
export function remoteMcpConfig(id, opts) {
|
|
101
|
+
opts = assertKnownOptions(opts, `remoteMcp(${JSON.stringify(id)})`, REMOTE_MCP_OPTIONS);
|
|
102
|
+
if (opts.readOnly !== undefined)
|
|
103
|
+
READ_ONLY.parse(opts.readOnly, `Connector(${JSON.stringify(id)}).readOnly`);
|
|
104
|
+
if (opts.callAdmission !== undefined)
|
|
105
|
+
CALL_ADMISSION.parse(opts.callAdmission, `remoteMcp(${JSON.stringify(id)}).callAdmission`);
|
|
106
|
+
const extraHeaders = extraResourceHeaders(id, opts);
|
|
107
|
+
const skillsEnabled = opts.skills;
|
|
108
|
+
if (skillsEnabled !== undefined && typeof skillsEnabled !== "boolean") {
|
|
109
|
+
throw new Error(`[connecta] connector "${id}" skills must be a boolean.`);
|
|
110
|
+
}
|
|
111
|
+
const clientMetadataUrl = opts.auth?.type === "oauth" ? opts.auth.clientMetadataUrl : undefined;
|
|
112
|
+
const oauthScope = opts.auth?.type === "oauth" ? opts.auth.scope : undefined;
|
|
113
|
+
assertOAuthScope(id, oauthScope);
|
|
114
|
+
const authorizationParams = authorizationParamsFor(opts.auth?.type === "oauth" ? opts.auth.authorizationParams : undefined, `remoteMcp(${JSON.stringify(id)}).auth.authorizationParams`);
|
|
115
|
+
const staticClient = opts.auth?.type === "oauth" && opts.auth.client ? { ...opts.auth.client } : undefined;
|
|
116
|
+
assertRemoteOAuthClient(id, staticClient);
|
|
117
|
+
if (staticClient && clientMetadataUrl !== undefined)
|
|
118
|
+
throw new Error(`[connecta] connector "${id}" OAuth client and clientMetadataUrl are mutually exclusive.`);
|
|
119
|
+
if (clientMetadataUrl !== undefined) {
|
|
120
|
+
let valid = false;
|
|
121
|
+
try {
|
|
122
|
+
const url = new URL(clientMetadataUrl);
|
|
123
|
+
valid = url.protocol === "https:" && url.pathname !== "/" && !url.username && !url.password && !url.hash;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* Invalid configuration is rejected below without echoing its value. */
|
|
127
|
+
}
|
|
128
|
+
if (!valid)
|
|
129
|
+
throw new Error(`[connecta] connector "${id}" clientMetadataUrl must be an HTTPS URL with a non-root path, without credentials or a fragment.`);
|
|
130
|
+
}
|
|
131
|
+
if (opts.authScope === "personal" && opts.auth?.type === "headers") {
|
|
132
|
+
throw new Error(`[connecta] connector "${id}" cannot combine authScope "personal" ` +
|
|
133
|
+
"with static headers. Use credential or OAuth auth so each principal " +
|
|
134
|
+
"can own a different grant.");
|
|
135
|
+
}
|
|
136
|
+
const isOauth = opts.auth?.type === "oauth";
|
|
137
|
+
const logger = opts.logger ?? console;
|
|
138
|
+
const warnShortSecret = shortSecretWarning();
|
|
139
|
+
warnShortSecret(staticClient?.clientSecret, logger);
|
|
140
|
+
if (opts.auth?.type === "headers") {
|
|
141
|
+
for (const [name, value] of Object.entries(opts.auth.headers)) {
|
|
142
|
+
if (!/key|token|secret|password|auth|signature|session/i.test(name))
|
|
143
|
+
continue;
|
|
144
|
+
const basic = /^Basic\s+(.+)$/i.exec(value);
|
|
145
|
+
if (basic) {
|
|
146
|
+
try {
|
|
147
|
+
const decoded = atob(basic[1]);
|
|
148
|
+
const colon = decoded.indexOf(":");
|
|
149
|
+
if (colon !== -1)
|
|
150
|
+
warnShortSecret(decoded.slice(colon + 1), logger);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
/* Invalid Basic configuration is not a short password. */
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else
|
|
157
|
+
warnShortSecret(value.replace(/^(?:Bearer|token)\s+/i, ""), logger);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const credentialAuth = opts.auth?.type === "credential" ? opts.auth : undefined;
|
|
161
|
+
if (credentialAuth?.credential?.fields?.length) {
|
|
162
|
+
throw new Error(`[connecta] connector "${id}" credential auth declares named fields; ` +
|
|
163
|
+
"this shape sends one secret in one header and reads the reserved " +
|
|
164
|
+
"`value` field only.");
|
|
165
|
+
}
|
|
166
|
+
// Both request shapes, read field by field; a mixture is refused below.
|
|
167
|
+
const requestAuth = opts.auth?.type === "request" ? opts.auth : undefined;
|
|
168
|
+
// Opt-in only: without it the resolver keeps receiving the context alone.
|
|
169
|
+
// `principal` replaces `token`; a malformed or mixed shape has no resolver.
|
|
170
|
+
const forwarding = requestAuth?.principal;
|
|
171
|
+
const requestToken = forwarding
|
|
172
|
+
? requestAuth?.token === undefined && (forwarding.accept === "human" || forwarding.accept === "human-or-agent")
|
|
173
|
+
? forwarding.token
|
|
174
|
+
: undefined
|
|
175
|
+
: requestAuth?.token;
|
|
176
|
+
if (requestAuth && (typeof requestToken !== "function" || opts.authScope === "personal")) {
|
|
177
|
+
throw new Error(`remoteMcp(${JSON.stringify(id)}) requires a request token callback and shared authScope.`);
|
|
178
|
+
}
|
|
179
|
+
const principalPolicy = forwarding?.accept;
|
|
180
|
+
const customFetch = opts.fetch;
|
|
181
|
+
// OAuth discovery, registration and token requests reach authorization
|
|
182
|
+
// servers the downstream names, through the owned credential sender. A
|
|
183
|
+
// custom fetch would split that traffic between two transports.
|
|
184
|
+
if (customFetch !== undefined && (typeof customFetch !== "function" || opts.auth?.type === "oauth")) {
|
|
185
|
+
throw new Error(`remoteMcp(${JSON.stringify(id)}).fetch must be a function, without OAuth auth.`);
|
|
186
|
+
}
|
|
187
|
+
// No delayed dispatch: a write counts as sent once it is handed to the function.
|
|
188
|
+
const customSender = customFetch && { send: customFetch };
|
|
189
|
+
if (requestAuth?.headers) {
|
|
190
|
+
try {
|
|
191
|
+
if (Object.keys(requestAuth.headers).some((name) => name.toLowerCase() === "authorization"))
|
|
192
|
+
throw new Error();
|
|
193
|
+
new Headers(requestAuth.headers);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
throw new Error(`remoteMcp(${JSON.stringify(id)}) requires valid request auth headers without Authorization.`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const credentialConfig = credentialAuth?.credential ?? {
|
|
200
|
+
label: "API key",
|
|
201
|
+
};
|
|
202
|
+
const credentialHeader = credentialAuth?.header?.trim() || "Authorization";
|
|
203
|
+
// A structural mistake in the deployment file, beside the `fields` refusal
|
|
204
|
+
// above: an unsendable header name is not worth discovering on the first
|
|
205
|
+
// request, where only a runtime error can report it.
|
|
206
|
+
if (credentialAuth && !HEADER_NAME_TOKEN.test(credentialHeader)) {
|
|
207
|
+
throw new Error(`[connecta] connector "${id}" credential auth declares header ` +
|
|
208
|
+
`"${credentialHeader}", which is not a valid HTTP field name.`);
|
|
209
|
+
}
|
|
210
|
+
// `undefined` means "not stated" and takes the bearer default; `null` and
|
|
211
|
+
// `""` both mean "send the stored value verbatim", which some providers
|
|
212
|
+
// require for a bare API key.
|
|
213
|
+
const credentialScheme = credentialAuth === undefined || credentialAuth.scheme === undefined
|
|
214
|
+
? "Bearer"
|
|
215
|
+
: (credentialAuth.scheme?.trim() ?? "") || null;
|
|
216
|
+
// Check the destination scheme once at construction: buildTransport (and the
|
|
217
|
+
// SDK's fetch) attach any static credentials to every request, so an http://
|
|
218
|
+
// endpoint sends bearer tokens / API keys in cleartext. Loopback is exempt
|
|
219
|
+
// for local development.
|
|
220
|
+
const destination = new URL(opts.url);
|
|
221
|
+
// Only a web URL reaches a remote MCP server. Another scheme cannot, and a
|
|
222
|
+
// wrapping one (`blob:https://…`) would also carry its inner URL, userinfo
|
|
223
|
+
// and all, into every place the endpoint is reported; the value is not echoed.
|
|
224
|
+
if (destination.protocol !== "https:" && destination.protocol !== "http:") {
|
|
225
|
+
throw new Error(`[connecta] connector "${id}" url must be an http(s) URL.`);
|
|
226
|
+
}
|
|
227
|
+
const insecureDestination = destination.protocol !== "https:" && !isLoopbackHost(destination.hostname);
|
|
228
|
+
if (insecureDestination) {
|
|
229
|
+
if (opts.requireHttps || requestAuth) {
|
|
230
|
+
throw new Error(`[connecta] connector "${id}" url ${opts.url} is not https:// (and not loopback) — refusing to connect (requireHttps).`);
|
|
231
|
+
}
|
|
232
|
+
// Both static shapes send a secret on every request; that an operator
|
|
233
|
+
// typed one into the operator UI rather than a deployment file changes
|
|
234
|
+
// who owns it, not whether the wire carries it in the clear.
|
|
235
|
+
if (opts.auth?.type === "headers" || credentialAuth) {
|
|
236
|
+
logger.warn(`[connecta] connector "${id}" sends static credentials to ${opts.url} over a non-https:// connection — those tokens will be transmitted in cleartext.`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const endpointOrigin = new URL(opts.url).origin;
|
|
240
|
+
return {
|
|
241
|
+
id,
|
|
242
|
+
opts,
|
|
243
|
+
requestAuth,
|
|
244
|
+
forwarding,
|
|
245
|
+
staticClient,
|
|
246
|
+
clientMetadataUrl,
|
|
247
|
+
oauthScope,
|
|
248
|
+
authorizationParams,
|
|
249
|
+
destination,
|
|
250
|
+
isOauth,
|
|
251
|
+
extraHeaders,
|
|
252
|
+
credentialHeader,
|
|
253
|
+
credentialScheme,
|
|
254
|
+
skillsEnabled,
|
|
255
|
+
principalPolicy,
|
|
256
|
+
credentialAuth,
|
|
257
|
+
customFetch,
|
|
258
|
+
customSender,
|
|
259
|
+
requestToken,
|
|
260
|
+
endpointOrigin,
|
|
261
|
+
credentialConfig,
|
|
262
|
+
};
|
|
263
|
+
}
|