mcp-scraper 0.66.3 → 0.66.4
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 +13 -1
- package/README.md +1 -1
- package/dist/bin/api-server.cjs +263 -19
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +12 -7
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/{chunk-JBFLOJGM.js → chunk-EGNT3AY2.js} +1 -1
- package/dist/{chunk-LNLMOBP6.js → chunk-UPN4W4SV.js} +12 -7
- package/dist/{server-D47PNEXR.js → server-YATQ3WHT.js} +251 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to MCP Scraper are documented here. The format is based on [
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.66.4] - 2026-08-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Connected-service discovery now returns a tenant-owned `userLabel` plus verified provider account ID, email, name, and identity status so agents can select the intended account when one integration has multiple connections.
|
|
12
|
+
- Managed OAuth connections now retain non-secret provider identity metadata and support owner-scoped account names through the dashboard API.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Nango tenant tags are no longer presented as the external provider account, preventing the MCP Scraper login from being mistaken for the Google, Slack, GitHub, Zoom, or other authorized identity.
|
|
17
|
+
|
|
7
18
|
## [0.66.3] - 2026-08-25
|
|
8
19
|
|
|
9
20
|
### Changed
|
|
@@ -1098,7 +1109,8 @@ All notable changes to MCP Scraper are documented here. The format is based on [
|
|
|
1098
1109
|
- Write actions remain unavailable until the account owner explicitly enables them.
|
|
1099
1110
|
- Provider-specific connection data is normalized into one agent-facing contract.
|
|
1100
1111
|
|
|
1101
|
-
[Unreleased]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.
|
|
1112
|
+
[Unreleased]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.4...HEAD
|
|
1113
|
+
[0.66.4]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.3...v0.66.4
|
|
1102
1114
|
[0.66.3]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.2...v0.66.3
|
|
1103
1115
|
[0.66.2]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.1...v0.66.2
|
|
1104
1116
|
[0.66.1]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.0...v0.66.1
|
package/README.md
CHANGED
|
@@ -159,7 +159,7 @@ Build the branded one-click bundle:
|
|
|
159
159
|
npm run build:mcpb
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.66.
|
|
162
|
+
The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.66.4`, SHA-256 `53fa34844c0a030c9777cb0e62d547c446ebf6f553f0a850d33bd0aa4185f73c`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, API-key configuration field, and manually curated current-release message from the bundle manifest.
|
|
163
163
|
|
|
164
164
|
The MCPB install exposes every tool — web-intelligence plus all `browser_*` tools — through the one `mcp-scraper` server.
|
|
165
165
|
|
package/dist/bin/api-server.cjs
CHANGED
|
@@ -49679,7 +49679,7 @@ var PACKAGE_VERSION;
|
|
|
49679
49679
|
var init_version = __esm({
|
|
49680
49680
|
"src/version.ts"() {
|
|
49681
49681
|
"use strict";
|
|
49682
|
-
PACKAGE_VERSION = "0.66.
|
|
49682
|
+
PACKAGE_VERSION = "0.66.4";
|
|
49683
49683
|
}
|
|
49684
49684
|
});
|
|
49685
49685
|
|
|
@@ -49940,10 +49940,10 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
49940
49940
|
artifact, call \`report_artifact_read\` with the returned artifactId and follow nextOffset until null. This
|
|
49941
49941
|
works through the authenticated MCP connection even when the client cannot open the signed download URL;
|
|
49942
49942
|
do not try curl or web_fetch. Use \`archive_read\` for ZIP archives.
|
|
49943
|
-
- Before using a connected account, call \`list_service_connections
|
|
49944
|
-
|
|
49945
|
-
|
|
49946
|
-
which
|
|
49943
|
+
- Before using a connected account, call \`list_service_connections\`. Match an explicit \`userLabel\` first,
|
|
49944
|
+
then verified provider-side \`providerAccountEmail\`, \`providerAccountName\`, or \`providerAccountId\`; never
|
|
49945
|
+
treat the MCP Scraper login as external-provider identity. If identity is pending or unavailable and no
|
|
49946
|
+
distinct user label exists, ask the person which connection they mean instead of assuming.
|
|
49947
49947
|
- For Google Search Console, use \`export_connected_service_data\` with
|
|
49948
49948
|
\`dataset:"search_console_performance"\` when the person asks for a fresh JSONL download. When they
|
|
49949
49949
|
ask to schedule ingestion or repeatedly filter historical rows, create a \`connection_sync\` schedule,
|
|
@@ -53853,7 +53853,12 @@ var init_mcp_tool_schemas = __esm({
|
|
|
53853
53853
|
connectionId: import_zod45.z.string(),
|
|
53854
53854
|
providerConfigKey: import_zod45.z.string(),
|
|
53855
53855
|
provider: import_zod45.z.string().nullable().optional(),
|
|
53856
|
-
label: import_zod45.z.string(),
|
|
53856
|
+
label: import_zod45.z.string().describe("Best available human-facing account label: a userLabel when set, otherwise verified provider identity, then a stable fallback."),
|
|
53857
|
+
userLabel: import_zod45.z.string().nullable().optional().describe("Optional tenant-owned name set in the dashboard to distinguish this connection. This is not asserted provider identity."),
|
|
53858
|
+
providerAccountId: import_zod45.z.string().nullable().optional().describe("Stable provider-side account, user, workspace, channel, or tenant identifier when the provider exposes one."),
|
|
53859
|
+
providerAccountEmail: import_zod45.z.string().email().nullable().optional().describe("Verified provider-side email when the provider exposes one. This is never inferred from the MCP Scraper login."),
|
|
53860
|
+
providerAccountName: import_zod45.z.string().nullable().optional().describe("Provider-side account, user, workspace, channel, or tenant name when the provider exposes one."),
|
|
53861
|
+
providerIdentityStatus: import_zod45.z.enum(["pending", "verified", "unavailable"]).optional().describe("Whether provider identity discovery is pending, returned at least one verified provider identifier, or completed without one."),
|
|
53857
53862
|
status: import_zod45.z.string(),
|
|
53858
53863
|
lifecycleStatus: import_zod45.z.enum(["pending", "connected", "needs_reauth", "disconnecting", "disconnected"]).optional().describe("Credential lifecycle. This is separate from current provider availability."),
|
|
53859
53864
|
operationalStatus: import_zod45.z.enum(["unknown", "available", "degraded", "unavailable"]).optional().describe("Last observed provider transport availability. Unavailable does not imply reconnect is required."),
|
|
@@ -56357,7 +56362,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
56357
56362
|
}, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
|
|
56358
56363
|
server.registerTool("list_service_connections", {
|
|
56359
56364
|
title: "List Connected Services",
|
|
56360
|
-
description: "List every service connection this MCP Scraper account has authorized, including Resend, GitHub, Google Analytics, Google Search Console, YouTube, Facebook Pages, LinkedIn, X, Meta Marketing, Slack, Gmail, Calendar, Google Drive, Zoom, Xero, and others. Returns the tenant-scoped connectionId, credential transport
|
|
56365
|
+
description: "List every service connection this MCP Scraper account has authorized, including Resend, GitHub, Google Analytics, Google Search Console, YouTube, Facebook Pages, LinkedIn, X, Meta Marketing, Slack, Gmail, Calendar, Google Drive, Zoom, Xero, and others. Returns the tenant-scoped connectionId; a tenant-owned userLabel; verified provider-side account ID, email, or name when available; credential transport; exact live readTools and gated actionTools; permission-aware toolCapabilities with missing OAuth-grant or provider-app-feature blockers; permanently blocked administrative tools; and schema-discovery metadata. Get a connectionId and exact tool name here before calling describe_service_connection_tool, read_service_connection, or call_service_connection_action. Managed OAuth and official remote MCP connections use the same provider-neutral bridges; mutations still require the account action switch and an exact allowed action. A scheduled Search Console connection_sync creates a typed tenant-owned performance table; after it runs, use the returned tableName with table-describe and table-query instead of repeatedly calling Google for historical filtering.",
|
|
56361
56366
|
inputSchema: ListServiceConnectionsInputSchema,
|
|
56362
56367
|
outputSchema: recordOutputSchema("list_service_connections", ListServiceConnectionsOutputSchema),
|
|
56363
56368
|
annotations: { title: "List Connected Services", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
@@ -81169,6 +81174,12 @@ function ensureServiceConnectionsSchema() {
|
|
|
81169
81174
|
transport TEXT NOT NULL,
|
|
81170
81175
|
upstream_connection_id TEXT,
|
|
81171
81176
|
label TEXT,
|
|
81177
|
+
user_label TEXT,
|
|
81178
|
+
provider_account_id TEXT,
|
|
81179
|
+
provider_account_email TEXT,
|
|
81180
|
+
provider_account_name TEXT,
|
|
81181
|
+
provider_identity_checked_at TEXT,
|
|
81182
|
+
provider_identity_source_updated_at TEXT,
|
|
81172
81183
|
lifecycle_status TEXT NOT NULL DEFAULT 'pending',
|
|
81173
81184
|
operational_status TEXT NOT NULL DEFAULT 'unknown',
|
|
81174
81185
|
reconnect_required INTEGER NOT NULL DEFAULT 0,
|
|
@@ -81190,6 +81201,30 @@ function ensureServiceConnectionsSchema() {
|
|
|
81190
81201
|
UNIQUE(user_id, provider_config_key, upstream_connection_id)
|
|
81191
81202
|
)
|
|
81192
81203
|
`);
|
|
81204
|
+
try {
|
|
81205
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
|
|
81206
|
+
} catch {
|
|
81207
|
+
}
|
|
81208
|
+
try {
|
|
81209
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
|
|
81210
|
+
} catch {
|
|
81211
|
+
}
|
|
81212
|
+
try {
|
|
81213
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
|
|
81214
|
+
} catch {
|
|
81215
|
+
}
|
|
81216
|
+
try {
|
|
81217
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
|
|
81218
|
+
} catch {
|
|
81219
|
+
}
|
|
81220
|
+
try {
|
|
81221
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
|
|
81222
|
+
} catch {
|
|
81223
|
+
}
|
|
81224
|
+
try {
|
|
81225
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
|
|
81226
|
+
} catch {
|
|
81227
|
+
}
|
|
81193
81228
|
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
|
|
81194
81229
|
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
|
|
81195
81230
|
await db.execute(`
|
|
@@ -81250,6 +81285,12 @@ function rowToRecord(row) {
|
|
|
81250
81285
|
transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
|
|
81251
81286
|
upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
|
|
81252
81287
|
label: typeof row.label === "string" ? row.label : null,
|
|
81288
|
+
userLabel: typeof row.user_label === "string" ? row.user_label : null,
|
|
81289
|
+
providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
|
|
81290
|
+
providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
|
|
81291
|
+
providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
|
|
81292
|
+
providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
|
|
81293
|
+
providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
|
|
81253
81294
|
lifecycleStatus: String(row.lifecycle_status),
|
|
81254
81295
|
operationalStatus: String(row.operational_status),
|
|
81255
81296
|
reconnectRequired: Number(row.reconnect_required) === 1,
|
|
@@ -81335,6 +81376,34 @@ async function updateServiceConnectionTools(connectionId, readTools, actionTools
|
|
|
81335
81376
|
args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
|
|
81336
81377
|
});
|
|
81337
81378
|
}
|
|
81379
|
+
async function setServiceConnectionProviderIdentity(args) {
|
|
81380
|
+
await ensureServiceConnectionsSchema();
|
|
81381
|
+
await getDb().execute({
|
|
81382
|
+
sql: `
|
|
81383
|
+
UPDATE service_connections SET
|
|
81384
|
+
provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
|
|
81385
|
+
provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
|
|
81386
|
+
updated_at = datetime('now')
|
|
81387
|
+
WHERE id = ?
|
|
81388
|
+
`,
|
|
81389
|
+
args: [
|
|
81390
|
+
args.providerAccountId ?? null,
|
|
81391
|
+
args.providerAccountEmail?.toLowerCase() ?? null,
|
|
81392
|
+
args.providerAccountName ?? null,
|
|
81393
|
+
args.sourceUpdatedAt,
|
|
81394
|
+
args.connectionId
|
|
81395
|
+
]
|
|
81396
|
+
});
|
|
81397
|
+
}
|
|
81398
|
+
async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
|
|
81399
|
+
const connection = await getOwnedServiceConnection(identity, connectionId);
|
|
81400
|
+
if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
|
|
81401
|
+
await getDb().execute({
|
|
81402
|
+
sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
|
|
81403
|
+
args: [userLabel, connectionId, connection.userId]
|
|
81404
|
+
});
|
|
81405
|
+
return userLabel;
|
|
81406
|
+
}
|
|
81338
81407
|
async function recordServiceConnectionHealth(args) {
|
|
81339
81408
|
await ensureServiceConnectionsSchema();
|
|
81340
81409
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -84411,7 +84480,8 @@ async function nangoRequest(path6, options = {}) {
|
|
|
84411
84480
|
headers: {
|
|
84412
84481
|
authorization: `Bearer ${nangoSecret()}`,
|
|
84413
84482
|
accept: "application/json",
|
|
84414
|
-
...options.body ? { "content-type": "application/json" } : {}
|
|
84483
|
+
...options.body ? { "content-type": "application/json" } : {},
|
|
84484
|
+
...options.headers
|
|
84415
84485
|
},
|
|
84416
84486
|
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
84417
84487
|
signal: AbortSignal.timeout(NANGO_TIMEOUT_MS)
|
|
@@ -84459,22 +84529,126 @@ function discoveredConnection(value) {
|
|
|
84459
84529
|
const upstreamConnectionId = text(row?.connection_id);
|
|
84460
84530
|
const providerConfigKey = text(row?.provider_config_key);
|
|
84461
84531
|
if (!upstreamConnectionId || !providerConfigKey) return null;
|
|
84462
|
-
const tags = asRecord2(row?.tags);
|
|
84463
|
-
const endUser = asRecord2(row?.end_user);
|
|
84464
84532
|
const errors = Array.isArray(row?.errors) ? row.errors : [];
|
|
84465
84533
|
const authError = errors.some((error) => text(asRecord2(error)?.type) === "auth");
|
|
84466
84534
|
return {
|
|
84467
84535
|
upstreamConnectionId,
|
|
84468
84536
|
providerConfigKey,
|
|
84469
84537
|
provider: text(row?.provider) ?? providerConfigKey,
|
|
84470
|
-
|
|
84538
|
+
// Nango's end-user fields identify the MCP Scraper tenant, not the
|
|
84539
|
+
// provider-side principal that authorized this connection.
|
|
84540
|
+
label: null,
|
|
84471
84541
|
lifecycleStatus: authError ? "needs_reauth" : "connected",
|
|
84472
84542
|
reconnectRequired: authError,
|
|
84473
84543
|
createdAt: text(row?.created_at) ?? text(row?.created),
|
|
84474
84544
|
updatedAt: text(row?.updated_at)
|
|
84475
84545
|
};
|
|
84476
84546
|
}
|
|
84477
|
-
|
|
84547
|
+
function scalarId(value) {
|
|
84548
|
+
const valueText = text(value);
|
|
84549
|
+
if (valueText) return valueText;
|
|
84550
|
+
return typeof value === "number" && Number.isFinite(value) ? String(value) : null;
|
|
84551
|
+
}
|
|
84552
|
+
function providerEmail(value) {
|
|
84553
|
+
const email = text(value)?.toLowerCase() ?? null;
|
|
84554
|
+
return email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? email : null;
|
|
84555
|
+
}
|
|
84556
|
+
function compactProviderIdentity(identity) {
|
|
84557
|
+
return identity.id || identity.email || identity.name ? identity : null;
|
|
84558
|
+
}
|
|
84559
|
+
async function providerProxyJson(args) {
|
|
84560
|
+
return nangoRequest(`/proxy${args.path}`, {
|
|
84561
|
+
query: args.query,
|
|
84562
|
+
headers: {
|
|
84563
|
+
"connection-id": args.upstreamConnectionId,
|
|
84564
|
+
"provider-config-key": args.providerConfigKey,
|
|
84565
|
+
...args.baseUrlOverride ? { "base-url-override": args.baseUrlOverride } : {}
|
|
84566
|
+
}
|
|
84567
|
+
});
|
|
84568
|
+
}
|
|
84569
|
+
async function googleProviderAccountIdentity(args) {
|
|
84570
|
+
const request = (path6, query, baseUrlOverride) => providerProxyJson({ ...args, path: path6, query, baseUrlOverride });
|
|
84571
|
+
try {
|
|
84572
|
+
if (args.providerConfigKey === "google-mail") {
|
|
84573
|
+
const payload = asRecord2(await request("/gmail/v1/users/me/profile", void 0, "https://gmail.googleapis.com"));
|
|
84574
|
+
const email = providerEmail(payload?.emailAddress);
|
|
84575
|
+
return compactProviderIdentity({ id: email, email, name: null });
|
|
84576
|
+
}
|
|
84577
|
+
if (args.providerConfigKey === "google-drive") {
|
|
84578
|
+
const payload = asRecord2(await request("/drive/v3/about", { fields: "user(permissionId,displayName,emailAddress)" }, "https://www.googleapis.com"));
|
|
84579
|
+
const user = asRecord2(payload?.user);
|
|
84580
|
+
return compactProviderIdentity({
|
|
84581
|
+
id: text(user?.permissionId),
|
|
84582
|
+
email: providerEmail(user?.emailAddress),
|
|
84583
|
+
name: text(user?.displayName)
|
|
84584
|
+
});
|
|
84585
|
+
}
|
|
84586
|
+
if (args.providerConfigKey === "google-calendar") {
|
|
84587
|
+
const payload = asRecord2(await request("/calendar/v3/calendars/primary", void 0, "https://www.googleapis.com"));
|
|
84588
|
+
const id = text(payload?.id);
|
|
84589
|
+
return compactProviderIdentity({ id, email: providerEmail(id), name: text(payload?.summary) });
|
|
84590
|
+
}
|
|
84591
|
+
if (args.providerConfigKey === "youtube") {
|
|
84592
|
+
const payload = asRecord2(await request("/youtube/v3/channels", { part: "snippet", mine: "true", maxResults: 1 }, "https://www.googleapis.com"));
|
|
84593
|
+
const first = Array.isArray(payload?.items) ? asRecord2(payload.items[0]) : null;
|
|
84594
|
+
return compactProviderIdentity({ id: text(first?.id), email: null, name: text(asRecord2(first?.snippet)?.title) });
|
|
84595
|
+
}
|
|
84596
|
+
} catch {
|
|
84597
|
+
}
|
|
84598
|
+
try {
|
|
84599
|
+
const payload = asRecord2(await request("/v1/userinfo", void 0, "https://openidconnect.googleapis.com"));
|
|
84600
|
+
return compactProviderIdentity({
|
|
84601
|
+
id: text(payload?.sub),
|
|
84602
|
+
email: payload?.email_verified === false ? null : providerEmail(payload?.email),
|
|
84603
|
+
name: text(payload?.name)
|
|
84604
|
+
});
|
|
84605
|
+
} catch {
|
|
84606
|
+
return null;
|
|
84607
|
+
}
|
|
84608
|
+
}
|
|
84609
|
+
async function getNangoProviderAccountIdentity(args) {
|
|
84610
|
+
if (!IDENTITY_SUPPORTED_PROVIDERS.has(args.providerConfigKey)) return null;
|
|
84611
|
+
if (args.providerConfigKey.startsWith("google-") || args.providerConfigKey === "youtube") {
|
|
84612
|
+
return googleProviderAccountIdentity(args);
|
|
84613
|
+
}
|
|
84614
|
+
try {
|
|
84615
|
+
if (args.providerConfigKey === "github-getting-started") {
|
|
84616
|
+
const payload = asRecord2(await providerProxyJson({ ...args, path: "/user" }));
|
|
84617
|
+
return compactProviderIdentity({ id: scalarId(payload?.id), email: providerEmail(payload?.email), name: text(payload?.name) ?? text(payload?.login) });
|
|
84618
|
+
}
|
|
84619
|
+
if (args.providerConfigKey === "slack") {
|
|
84620
|
+
const payload = asRecord2(await providerProxyJson({ ...args, path: "/api/auth.test" }));
|
|
84621
|
+
return compactProviderIdentity({ id: text(payload?.user_id), email: null, name: [text(payload?.user), text(payload?.team)].filter(Boolean).join(" \xB7 ") || null });
|
|
84622
|
+
}
|
|
84623
|
+
if (args.providerConfigKey === "zoom") {
|
|
84624
|
+
const payload = asRecord2(await providerProxyJson({ ...args, path: "/v2/users/me" }));
|
|
84625
|
+
const fullName = [text(payload?.first_name), text(payload?.last_name)].filter(Boolean).join(" ");
|
|
84626
|
+
return compactProviderIdentity({ id: text(payload?.id), email: providerEmail(payload?.email), name: text(payload?.display_name) ?? (fullName || null) });
|
|
84627
|
+
}
|
|
84628
|
+
if (args.providerConfigKey === "linkedin") {
|
|
84629
|
+
const payload = asRecord2(await providerProxyJson({ ...args, path: "/v2/userinfo" }));
|
|
84630
|
+
return compactProviderIdentity({ id: text(payload?.sub), email: payload?.email_verified === false ? null : providerEmail(payload?.email), name: text(payload?.name) });
|
|
84631
|
+
}
|
|
84632
|
+
if (args.providerConfigKey === "meta-marketing-api" || args.providerConfigKey === "facebook") {
|
|
84633
|
+
const payload = asRecord2(await providerProxyJson({ ...args, path: "/me", query: { fields: "id,name,email" } }));
|
|
84634
|
+
return compactProviderIdentity({ id: text(payload?.id), email: providerEmail(payload?.email), name: text(payload?.name) });
|
|
84635
|
+
}
|
|
84636
|
+
if (args.providerConfigKey === "twitter-v2") {
|
|
84637
|
+
const payload = asRecord2(await providerProxyJson({ ...args, path: "/2/users/me", query: { "user.fields": "id,name,username" } }));
|
|
84638
|
+
const data = asRecord2(payload?.data);
|
|
84639
|
+
return compactProviderIdentity({ id: text(data?.id), email: null, name: text(data?.username) ?? text(data?.name) });
|
|
84640
|
+
}
|
|
84641
|
+
if (args.providerConfigKey === "xero") {
|
|
84642
|
+
const payload = await providerProxyJson({ ...args, path: "/connections" });
|
|
84643
|
+
const first = Array.isArray(payload) ? asRecord2(payload[0]) : null;
|
|
84644
|
+
return compactProviderIdentity({ id: text(first?.tenantId), email: null, name: text(first?.tenantName) });
|
|
84645
|
+
}
|
|
84646
|
+
} catch {
|
|
84647
|
+
return null;
|
|
84648
|
+
}
|
|
84649
|
+
return null;
|
|
84650
|
+
}
|
|
84651
|
+
async function discoverOwnedNangoConnections(identity, options = {}) {
|
|
84478
84652
|
const rows = [];
|
|
84479
84653
|
for (let page = 0; page < NANGO_CONNECTION_MAX_PAGES; page += 1) {
|
|
84480
84654
|
const payload = await nangoRequest("/connections", {
|
|
@@ -84485,6 +84659,35 @@ async function discoverOwnedNangoConnections(identity) {
|
|
|
84485
84659
|
if (pageRows.length < NANGO_CONNECTION_PAGE_SIZE) break;
|
|
84486
84660
|
}
|
|
84487
84661
|
const discovered = rows.filter((row) => connectionIdentityMatches(row, identity)).map(discoveredConnection).filter((connection) => connection !== null);
|
|
84662
|
+
const reconciled = await reconcileDiscoveredNangoConnections(identity, discovered);
|
|
84663
|
+
if (options.resolveProviderIdentity === false) return reconciled;
|
|
84664
|
+
const remoteByKey = new Map(discovered.map((connection) => [
|
|
84665
|
+
`${connection.providerConfigKey}\0${connection.upstreamConnectionId}`,
|
|
84666
|
+
connection
|
|
84667
|
+
]));
|
|
84668
|
+
await Promise.all(reconciled.map(async (connection) => {
|
|
84669
|
+
if (connection.lifecycleStatus !== "connected" || !connection.upstreamConnectionId) return;
|
|
84670
|
+
const remote = remoteByKey.get(`${connection.providerConfigKey}\0${connection.upstreamConnectionId}`);
|
|
84671
|
+
if (!remote) return;
|
|
84672
|
+
const sourceUpdatedAt = remote.updatedAt ?? remote.createdAt ?? connection.lifecycleStatus;
|
|
84673
|
+
const hasProviderIdentity = Boolean(
|
|
84674
|
+
connection.providerAccountId || connection.providerAccountEmail || connection.providerAccountName
|
|
84675
|
+
);
|
|
84676
|
+
const checkedAt = connection.providerIdentityCheckedAt ? Date.parse(`${connection.providerIdentityCheckedAt.replace(" ", "T")}Z`) : Number.NaN;
|
|
84677
|
+
const unavailableIdentityIsFresh = !hasProviderIdentity && Number.isFinite(checkedAt) && Date.now() - checkedAt < PROVIDER_IDENTITY_RETRY_MS;
|
|
84678
|
+
if (connection.providerIdentitySourceUpdatedAt === sourceUpdatedAt && (hasProviderIdentity || unavailableIdentityIsFresh)) return;
|
|
84679
|
+
const providerIdentity = await getNangoProviderAccountIdentity({
|
|
84680
|
+
upstreamConnectionId: connection.upstreamConnectionId,
|
|
84681
|
+
providerConfigKey: connection.providerConfigKey
|
|
84682
|
+
});
|
|
84683
|
+
await setServiceConnectionProviderIdentity({
|
|
84684
|
+
connectionId: connection.id,
|
|
84685
|
+
providerAccountId: providerIdentity?.id,
|
|
84686
|
+
providerAccountEmail: providerIdentity?.email,
|
|
84687
|
+
providerAccountName: providerIdentity?.name,
|
|
84688
|
+
sourceUpdatedAt
|
|
84689
|
+
});
|
|
84690
|
+
}));
|
|
84488
84691
|
return reconcileDiscoveredNangoConnections(identity, discovered);
|
|
84489
84692
|
}
|
|
84490
84693
|
async function listNangoCatalogDirect() {
|
|
@@ -84747,7 +84950,7 @@ async function describeNangoToolDirect(identity, connectionId, toolName) {
|
|
|
84747
84950
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
84748
84951
|
};
|
|
84749
84952
|
}
|
|
84750
|
-
var import_node_crypto68, import_client13, DEFAULT_NANGO_MCP_URL, NANGO_TIMEOUT_MS, NANGO_CONNECTION_PAGE_SIZE, NANGO_CONNECTION_MAX_PAGES, MAIN_INTEGRATION_CONTRACT_VERSION, MAIN_INTEGRATION_CONTRACT_HASH, MainNangoTransportError;
|
|
84953
|
+
var import_node_crypto68, import_client13, DEFAULT_NANGO_MCP_URL, NANGO_TIMEOUT_MS, NANGO_CONNECTION_PAGE_SIZE, NANGO_CONNECTION_MAX_PAGES, PROVIDER_IDENTITY_RETRY_MS, MAIN_INTEGRATION_CONTRACT_VERSION, MAIN_INTEGRATION_CONTRACT_HASH, MainNangoTransportError, IDENTITY_SUPPORTED_PROVIDERS;
|
|
84751
84954
|
var init_main_nango_transport = __esm({
|
|
84752
84955
|
"src/api/main-nango-transport.ts"() {
|
|
84753
84956
|
"use strict";
|
|
@@ -84761,6 +84964,7 @@ var init_main_nango_transport = __esm({
|
|
|
84761
84964
|
NANGO_TIMEOUT_MS = 2e4;
|
|
84762
84965
|
NANGO_CONNECTION_PAGE_SIZE = 100;
|
|
84763
84966
|
NANGO_CONNECTION_MAX_PAGES = 20;
|
|
84967
|
+
PROVIDER_IDENTITY_RETRY_MS = 6 * 60 * 60 * 1e3;
|
|
84764
84968
|
MAIN_INTEGRATION_CONTRACT_VERSION = "2026-07-16.1";
|
|
84765
84969
|
MAIN_INTEGRATION_CONTRACT_HASH = "f037859ec80c2ea2fac6e4a67c1a59f3200dd22d3642c97789b9bb3236723d3e";
|
|
84766
84970
|
MainNangoTransportError = class extends Error {
|
|
@@ -84777,6 +84981,22 @@ var init_main_nango_transport = __esm({
|
|
|
84777
84981
|
this.retryAfterMs = retryAfterMs;
|
|
84778
84982
|
}
|
|
84779
84983
|
};
|
|
84984
|
+
IDENTITY_SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set([
|
|
84985
|
+
"facebook",
|
|
84986
|
+
"github-getting-started",
|
|
84987
|
+
"google-analytics",
|
|
84988
|
+
"google-calendar",
|
|
84989
|
+
"google-drive",
|
|
84990
|
+
"google-mail",
|
|
84991
|
+
"google-search-console",
|
|
84992
|
+
"linkedin",
|
|
84993
|
+
"meta-marketing-api",
|
|
84994
|
+
"slack",
|
|
84995
|
+
"twitter-v2",
|
|
84996
|
+
"xero",
|
|
84997
|
+
"youtube",
|
|
84998
|
+
"zoom"
|
|
84999
|
+
]);
|
|
84780
85000
|
}
|
|
84781
85001
|
});
|
|
84782
85002
|
|
|
@@ -85067,7 +85287,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
85067
85287
|
if (mainOwnsIntegrations()) {
|
|
85068
85288
|
let connections;
|
|
85069
85289
|
try {
|
|
85070
|
-
connections = await discoverOwnedNangoConnections(identity);
|
|
85290
|
+
connections = await discoverOwnedNangoConnections(identity, { resolveProviderIdentity: !options.summaryOnly });
|
|
85071
85291
|
} catch (error) {
|
|
85072
85292
|
try {
|
|
85073
85293
|
connections = await listServiceConnections(identity, "nango");
|
|
@@ -85088,11 +85308,12 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
85088
85308
|
connectionId: connection.id,
|
|
85089
85309
|
providerConfigKey: connection.providerConfigKey,
|
|
85090
85310
|
provider: connection.provider,
|
|
85091
|
-
label: connection.label || connection.providerConfigKey,
|
|
85092
|
-
|
|
85093
|
-
|
|
85094
|
-
|
|
85095
|
-
|
|
85311
|
+
label: connection.userLabel || connection.providerAccountEmail || connection.providerAccountName || (connection.label && connection.label.toLowerCase() !== identity.trim().toLowerCase() ? connection.label : null) || connection.providerConfigKey,
|
|
85312
|
+
userLabel: connection.userLabel,
|
|
85313
|
+
providerAccountId: connection.providerAccountId,
|
|
85314
|
+
providerAccountEmail: connection.providerAccountEmail,
|
|
85315
|
+
providerAccountName: connection.providerAccountName,
|
|
85316
|
+
providerIdentityStatus: connection.providerIdentityCheckedAt ? connection.providerAccountId || connection.providerAccountEmail || connection.providerAccountName ? "verified" : "unavailable" : "pending",
|
|
85096
85317
|
status: connection.reconnectRequired ? "needs_reauth" : "connected",
|
|
85097
85318
|
lifecycleStatus: connection.lifecycleStatus,
|
|
85098
85319
|
operationalStatus: connection.operationalStatus,
|
|
@@ -85193,6 +85414,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
85193
85414
|
providerConfigKey,
|
|
85194
85415
|
provider,
|
|
85195
85416
|
label,
|
|
85417
|
+
userLabel: null,
|
|
85196
85418
|
providerAccountId,
|
|
85197
85419
|
providerAccountEmail,
|
|
85198
85420
|
providerAccountName,
|
|
@@ -86968,6 +87190,7 @@ var init_server = __esm({
|
|
|
86968
87190
|
init_scheduling_access();
|
|
86969
87191
|
init_memory_db();
|
|
86970
87192
|
init_nango_control();
|
|
87193
|
+
init_service_connections();
|
|
86971
87194
|
init_resend_control();
|
|
86972
87195
|
init_scheduler_integration_auth();
|
|
86973
87196
|
init_connected_account_billing();
|
|
@@ -87559,6 +87782,26 @@ var init_server = __esm({
|
|
|
87559
87782
|
return scheduleConnectionError(c, err, "Unable to load service connections.");
|
|
87560
87783
|
}
|
|
87561
87784
|
});
|
|
87785
|
+
app.patch("/schedule-connections/:id/label", auth4, async (c) => {
|
|
87786
|
+
const user = c.get("user");
|
|
87787
|
+
const body = await c.req.json().catch(() => ({}));
|
|
87788
|
+
if (body.label !== null && typeof body.label !== "string") {
|
|
87789
|
+
return c.json({ ok: false, error: "label must be a string or null." }, 400);
|
|
87790
|
+
}
|
|
87791
|
+
const label = typeof body.label === "string" ? body.label.normalize("NFKC").trim().replace(/\s+/g, " ") : null;
|
|
87792
|
+
if (label && label.length > 80) {
|
|
87793
|
+
return c.json({ ok: false, error: "label must contain at most 80 characters." }, 400);
|
|
87794
|
+
}
|
|
87795
|
+
try {
|
|
87796
|
+
const userLabel = await setServiceConnectionUserLabel(user.email, c.req.param("id"), label || null);
|
|
87797
|
+
return c.json({ ok: true, userLabel });
|
|
87798
|
+
} catch (err) {
|
|
87799
|
+
if (err instanceof Error && err.message === "service_connection_not_found") {
|
|
87800
|
+
return c.json({ ok: false, error: "Service connection not found." }, 404);
|
|
87801
|
+
}
|
|
87802
|
+
return scheduleConnectionError(c, err, "Unable to rename this service connection.");
|
|
87803
|
+
}
|
|
87804
|
+
});
|
|
87562
87805
|
app.post("/schedule-connections/billing/reconcile", auth4, requireIntegrationsTier, async (c) => {
|
|
87563
87806
|
const user = c.get("user");
|
|
87564
87807
|
try {
|
|
@@ -88117,6 +88360,7 @@ var init_server = __esm({
|
|
|
88117
88360
|
app.post("/integrations/:id/reconnect-session", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/reconnect-session`));
|
|
88118
88361
|
app.post("/integrations/:id/enable-actions", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/enable-actions`));
|
|
88119
88362
|
app.post("/integrations/:id/test", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/test`));
|
|
88363
|
+
app.patch("/integrations/:id/label", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/label`));
|
|
88120
88364
|
app.delete("/integrations/:id", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}`));
|
|
88121
88365
|
app.post("/integrations/actions/read", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/read"));
|
|
88122
88366
|
app.post("/integrations/actions/describe", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/describe"));
|
package/dist/bin/api-server.js
CHANGED
|
@@ -17,7 +17,7 @@ loadDotEnv();
|
|
|
17
17
|
async function main() {
|
|
18
18
|
const [{ serve }, { app }, { startWorker }, { migrate }] = await Promise.all([
|
|
19
19
|
import("@hono/node-server"),
|
|
20
|
-
import("../server-
|
|
20
|
+
import("../server-YATQ3WHT.js"),
|
|
21
21
|
import("../worker-UTBUWEXQ.js"),
|
|
22
22
|
import("../db-4RVZ3NYZ.js")
|
|
23
23
|
]);
|
|
@@ -112,7 +112,7 @@ function renderInstallTerminal(options) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// src/version.ts
|
|
115
|
-
var PACKAGE_VERSION = "0.66.
|
|
115
|
+
var PACKAGE_VERSION = "0.66.4";
|
|
116
116
|
|
|
117
117
|
// bin/mcp-scraper-install.ts
|
|
118
118
|
var noColor = process.argv.includes("--no-color") || process.env.NO_COLOR !== void 0 || process.env.FORCE_COLOR === "0" || !process.stdout.isTTY;
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "../chunk-2XVDLF6H.js";
|
|
5
5
|
import {
|
|
6
6
|
PACKAGE_VERSION
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-EGNT3AY2.js";
|
|
8
8
|
|
|
9
9
|
// bin/mcp-scraper-install.ts
|
|
10
10
|
var noColor = process.argv.includes("--no-color") || process.env.NO_COLOR !== void 0 || process.env.FORCE_COLOR === "0" || !process.stdout.isTTY;
|
|
@@ -1232,7 +1232,7 @@ render();
|
|
|
1232
1232
|
}
|
|
1233
1233
|
|
|
1234
1234
|
// src/version.ts
|
|
1235
|
-
var PACKAGE_VERSION = "0.66.
|
|
1235
|
+
var PACKAGE_VERSION = "0.66.4";
|
|
1236
1236
|
|
|
1237
1237
|
// src/mcp/input-field-descriptions.ts
|
|
1238
1238
|
var import_zod = require("zod");
|
|
@@ -8408,10 +8408,10 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
8408
8408
|
artifact, call \`report_artifact_read\` with the returned artifactId and follow nextOffset until null. This
|
|
8409
8409
|
works through the authenticated MCP connection even when the client cannot open the signed download URL;
|
|
8410
8410
|
do not try curl or web_fetch. Use \`archive_read\` for ZIP archives.
|
|
8411
|
-
- Before using a connected account, call \`list_service_connections
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
which
|
|
8411
|
+
- Before using a connected account, call \`list_service_connections\`. Match an explicit \`userLabel\` first,
|
|
8412
|
+
then verified provider-side \`providerAccountEmail\`, \`providerAccountName\`, or \`providerAccountId\`; never
|
|
8413
|
+
treat the MCP Scraper login as external-provider identity. If identity is pending or unavailable and no
|
|
8414
|
+
distinct user label exists, ask the person which connection they mean instead of assuming.
|
|
8415
8415
|
- For Google Search Console, use \`export_connected_service_data\` with
|
|
8416
8416
|
\`dataset:"search_console_performance"\` when the person asks for a fresh JSONL download. When they
|
|
8417
8417
|
ask to schedule ingestion or repeatedly filter historical rows, create a \`connection_sync\` schedule,
|
|
@@ -12064,7 +12064,12 @@ var ListServiceConnectionsOutputSchema = {
|
|
|
12064
12064
|
connectionId: import_zod8.z.string(),
|
|
12065
12065
|
providerConfigKey: import_zod8.z.string(),
|
|
12066
12066
|
provider: import_zod8.z.string().nullable().optional(),
|
|
12067
|
-
label: import_zod8.z.string(),
|
|
12067
|
+
label: import_zod8.z.string().describe("Best available human-facing account label: a userLabel when set, otherwise verified provider identity, then a stable fallback."),
|
|
12068
|
+
userLabel: import_zod8.z.string().nullable().optional().describe("Optional tenant-owned name set in the dashboard to distinguish this connection. This is not asserted provider identity."),
|
|
12069
|
+
providerAccountId: import_zod8.z.string().nullable().optional().describe("Stable provider-side account, user, workspace, channel, or tenant identifier when the provider exposes one."),
|
|
12070
|
+
providerAccountEmail: import_zod8.z.string().email().nullable().optional().describe("Verified provider-side email when the provider exposes one. This is never inferred from the MCP Scraper login."),
|
|
12071
|
+
providerAccountName: import_zod8.z.string().nullable().optional().describe("Provider-side account, user, workspace, channel, or tenant name when the provider exposes one."),
|
|
12072
|
+
providerIdentityStatus: import_zod8.z.enum(["pending", "verified", "unavailable"]).optional().describe("Whether provider identity discovery is pending, returned at least one verified provider identifier, or completed without one."),
|
|
12068
12073
|
status: import_zod8.z.string(),
|
|
12069
12074
|
lifecycleStatus: import_zod8.z.enum(["pending", "connected", "needs_reauth", "disconnecting", "disconnected"]).optional().describe("Credential lifecycle. This is separate from current provider availability."),
|
|
12070
12075
|
operationalStatus: import_zod8.z.enum(["unknown", "available", "degraded", "unavailable"]).optional().describe("Last observed provider transport availability. Unavailable does not imply reconnect is required."),
|
|
@@ -14378,7 +14383,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
14378
14383
|
}, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
|
|
14379
14384
|
server.registerTool("list_service_connections", {
|
|
14380
14385
|
title: "List Connected Services",
|
|
14381
|
-
description: "List every service connection this MCP Scraper account has authorized, including Resend, GitHub, Google Analytics, Google Search Console, YouTube, Facebook Pages, LinkedIn, X, Meta Marketing, Slack, Gmail, Calendar, Google Drive, Zoom, Xero, and others. Returns the tenant-scoped connectionId, credential transport
|
|
14386
|
+
description: "List every service connection this MCP Scraper account has authorized, including Resend, GitHub, Google Analytics, Google Search Console, YouTube, Facebook Pages, LinkedIn, X, Meta Marketing, Slack, Gmail, Calendar, Google Drive, Zoom, Xero, and others. Returns the tenant-scoped connectionId; a tenant-owned userLabel; verified provider-side account ID, email, or name when available; credential transport; exact live readTools and gated actionTools; permission-aware toolCapabilities with missing OAuth-grant or provider-app-feature blockers; permanently blocked administrative tools; and schema-discovery metadata. Get a connectionId and exact tool name here before calling describe_service_connection_tool, read_service_connection, or call_service_connection_action. Managed OAuth and official remote MCP connections use the same provider-neutral bridges; mutations still require the account action switch and an exact allowed action. A scheduled Search Console connection_sync creates a typed tenant-owned performance table; after it runs, use the returned tableName with table-describe and table-query instead of repeatedly calling Google for historical filtering.",
|
|
14382
14387
|
inputSchema: ListServiceConnectionsInputSchema,
|
|
14383
14388
|
outputSchema: recordOutputSchema("list_service_connections", ListServiceConnectionsOutputSchema),
|
|
14384
14389
|
annotations: { title: "List Connected Services", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
registerScheduledResultsMcpTools,
|
|
14
14
|
registerSerpIntelligenceCaptureTools,
|
|
15
15
|
resolveDeploymentProfile
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-UPN4W4SV.js";
|
|
17
17
|
import "../chunk-PGJQDMC2.js";
|
|
18
18
|
import "../chunk-3EYALCYK.js";
|
|
19
19
|
import "../chunk-3TKKJQB4.js";
|
|
@@ -24,7 +24,7 @@ import "../chunk-VXLU74YZ.js";
|
|
|
24
24
|
import "../chunk-GGZEC22A.js";
|
|
25
25
|
import {
|
|
26
26
|
PACKAGE_VERSION
|
|
27
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-EGNT3AY2.js";
|
|
28
28
|
import "../chunk-DNM65UCK.js";
|
|
29
29
|
import "../chunk-AYJHI2FK.js";
|
|
30
30
|
import "../chunk-55B3T5L2.js";
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "./chunk-VXLU74YZ.js";
|
|
26
26
|
import {
|
|
27
27
|
PACKAGE_VERSION
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-EGNT3AY2.js";
|
|
29
29
|
import {
|
|
30
30
|
createPrivateArtifact,
|
|
31
31
|
privateArtifactOwnerId,
|
|
@@ -580,10 +580,10 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
580
580
|
artifact, call \`report_artifact_read\` with the returned artifactId and follow nextOffset until null. This
|
|
581
581
|
works through the authenticated MCP connection even when the client cannot open the signed download URL;
|
|
582
582
|
do not try curl or web_fetch. Use \`archive_read\` for ZIP archives.
|
|
583
|
-
- Before using a connected account, call \`list_service_connections
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
which
|
|
583
|
+
- Before using a connected account, call \`list_service_connections\`. Match an explicit \`userLabel\` first,
|
|
584
|
+
then verified provider-side \`providerAccountEmail\`, \`providerAccountName\`, or \`providerAccountId\`; never
|
|
585
|
+
treat the MCP Scraper login as external-provider identity. If identity is pending or unavailable and no
|
|
586
|
+
distinct user label exists, ask the person which connection they mean instead of assuming.
|
|
587
587
|
- For Google Search Console, use \`export_connected_service_data\` with
|
|
588
588
|
\`dataset:"search_console_performance"\` when the person asks for a fresh JSONL download. When they
|
|
589
589
|
ask to schedule ingestion or repeatedly filter historical rows, create a \`connection_sync\` schedule,
|
|
@@ -9442,7 +9442,12 @@ var ListServiceConnectionsOutputSchema = {
|
|
|
9442
9442
|
connectionId: z6.string(),
|
|
9443
9443
|
providerConfigKey: z6.string(),
|
|
9444
9444
|
provider: z6.string().nullable().optional(),
|
|
9445
|
-
label: z6.string(),
|
|
9445
|
+
label: z6.string().describe("Best available human-facing account label: a userLabel when set, otherwise verified provider identity, then a stable fallback."),
|
|
9446
|
+
userLabel: z6.string().nullable().optional().describe("Optional tenant-owned name set in the dashboard to distinguish this connection. This is not asserted provider identity."),
|
|
9447
|
+
providerAccountId: z6.string().nullable().optional().describe("Stable provider-side account, user, workspace, channel, or tenant identifier when the provider exposes one."),
|
|
9448
|
+
providerAccountEmail: z6.string().email().nullable().optional().describe("Verified provider-side email when the provider exposes one. This is never inferred from the MCP Scraper login."),
|
|
9449
|
+
providerAccountName: z6.string().nullable().optional().describe("Provider-side account, user, workspace, channel, or tenant name when the provider exposes one."),
|
|
9450
|
+
providerIdentityStatus: z6.enum(["pending", "verified", "unavailable"]).optional().describe("Whether provider identity discovery is pending, returned at least one verified provider identifier, or completed without one."),
|
|
9446
9451
|
status: z6.string(),
|
|
9447
9452
|
lifecycleStatus: z6.enum(["pending", "connected", "needs_reauth", "disconnecting", "disconnected"]).optional().describe("Credential lifecycle. This is separate from current provider availability."),
|
|
9448
9453
|
operationalStatus: z6.enum(["unknown", "available", "degraded", "unavailable"]).optional().describe("Last observed provider transport availability. Unavailable does not imply reconnect is required."),
|
|
@@ -11887,7 +11892,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
11887
11892
|
}, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
|
|
11888
11893
|
server.registerTool("list_service_connections", {
|
|
11889
11894
|
title: "List Connected Services",
|
|
11890
|
-
description: "List every service connection this MCP Scraper account has authorized, including Resend, GitHub, Google Analytics, Google Search Console, YouTube, Facebook Pages, LinkedIn, X, Meta Marketing, Slack, Gmail, Calendar, Google Drive, Zoom, Xero, and others. Returns the tenant-scoped connectionId, credential transport
|
|
11895
|
+
description: "List every service connection this MCP Scraper account has authorized, including Resend, GitHub, Google Analytics, Google Search Console, YouTube, Facebook Pages, LinkedIn, X, Meta Marketing, Slack, Gmail, Calendar, Google Drive, Zoom, Xero, and others. Returns the tenant-scoped connectionId; a tenant-owned userLabel; verified provider-side account ID, email, or name when available; credential transport; exact live readTools and gated actionTools; permission-aware toolCapabilities with missing OAuth-grant or provider-app-feature blockers; permanently blocked administrative tools; and schema-discovery metadata. Get a connectionId and exact tool name here before calling describe_service_connection_tool, read_service_connection, or call_service_connection_action. Managed OAuth and official remote MCP connections use the same provider-neutral bridges; mutations still require the account action switch and an exact allowed action. A scheduled Search Console connection_sync creates a typed tenant-owned performance table; after it runs, use the returned tableName with table-describe and table-query instead of repeatedly calling Google for historical filtering.",
|
|
11891
11896
|
inputSchema: ListServiceConnectionsInputSchema,
|
|
11892
11897
|
outputSchema: recordOutputSchema("list_service_connections", ListServiceConnectionsOutputSchema),
|
|
11893
11898
|
annotations: { title: "List Connected Services", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
@@ -199,7 +199,7 @@ import {
|
|
|
199
199
|
resolveDeploymentProfile,
|
|
200
200
|
resolveLocalSourcebookSchemaType,
|
|
201
201
|
transcribeMediaUrl
|
|
202
|
-
} from "./chunk-
|
|
202
|
+
} from "./chunk-UPN4W4SV.js";
|
|
203
203
|
import {
|
|
204
204
|
auditImageUrls,
|
|
205
205
|
auditImages,
|
|
@@ -256,7 +256,7 @@ import {
|
|
|
256
256
|
} from "./chunk-GGZEC22A.js";
|
|
257
257
|
import {
|
|
258
258
|
PACKAGE_VERSION
|
|
259
|
-
} from "./chunk-
|
|
259
|
+
} from "./chunk-EGNT3AY2.js";
|
|
260
260
|
import {
|
|
261
261
|
SITE_EXTRACT_ARTIFACT_PREFIX,
|
|
262
262
|
abandonExtractSettlement,
|
|
@@ -41109,6 +41109,12 @@ function ensureServiceConnectionsSchema() {
|
|
|
41109
41109
|
transport TEXT NOT NULL,
|
|
41110
41110
|
upstream_connection_id TEXT,
|
|
41111
41111
|
label TEXT,
|
|
41112
|
+
user_label TEXT,
|
|
41113
|
+
provider_account_id TEXT,
|
|
41114
|
+
provider_account_email TEXT,
|
|
41115
|
+
provider_account_name TEXT,
|
|
41116
|
+
provider_identity_checked_at TEXT,
|
|
41117
|
+
provider_identity_source_updated_at TEXT,
|
|
41112
41118
|
lifecycle_status TEXT NOT NULL DEFAULT 'pending',
|
|
41113
41119
|
operational_status TEXT NOT NULL DEFAULT 'unknown',
|
|
41114
41120
|
reconnect_required INTEGER NOT NULL DEFAULT 0,
|
|
@@ -41130,6 +41136,30 @@ function ensureServiceConnectionsSchema() {
|
|
|
41130
41136
|
UNIQUE(user_id, provider_config_key, upstream_connection_id)
|
|
41131
41137
|
)
|
|
41132
41138
|
`);
|
|
41139
|
+
try {
|
|
41140
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
|
|
41141
|
+
} catch {
|
|
41142
|
+
}
|
|
41143
|
+
try {
|
|
41144
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
|
|
41145
|
+
} catch {
|
|
41146
|
+
}
|
|
41147
|
+
try {
|
|
41148
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
|
|
41149
|
+
} catch {
|
|
41150
|
+
}
|
|
41151
|
+
try {
|
|
41152
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
|
|
41153
|
+
} catch {
|
|
41154
|
+
}
|
|
41155
|
+
try {
|
|
41156
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
|
|
41157
|
+
} catch {
|
|
41158
|
+
}
|
|
41159
|
+
try {
|
|
41160
|
+
await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
|
|
41161
|
+
} catch {
|
|
41162
|
+
}
|
|
41133
41163
|
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
|
|
41134
41164
|
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
|
|
41135
41165
|
await db.execute(`
|
|
@@ -41190,6 +41220,12 @@ function rowToRecord(row) {
|
|
|
41190
41220
|
transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
|
|
41191
41221
|
upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
|
|
41192
41222
|
label: typeof row.label === "string" ? row.label : null,
|
|
41223
|
+
userLabel: typeof row.user_label === "string" ? row.user_label : null,
|
|
41224
|
+
providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
|
|
41225
|
+
providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
|
|
41226
|
+
providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
|
|
41227
|
+
providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
|
|
41228
|
+
providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
|
|
41193
41229
|
lifecycleStatus: String(row.lifecycle_status),
|
|
41194
41230
|
operationalStatus: String(row.operational_status),
|
|
41195
41231
|
reconnectRequired: Number(row.reconnect_required) === 1,
|
|
@@ -41275,6 +41311,34 @@ async function updateServiceConnectionTools(connectionId, readTools, actionTools
|
|
|
41275
41311
|
args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
|
|
41276
41312
|
});
|
|
41277
41313
|
}
|
|
41314
|
+
async function setServiceConnectionProviderIdentity(args) {
|
|
41315
|
+
await ensureServiceConnectionsSchema();
|
|
41316
|
+
await getDb().execute({
|
|
41317
|
+
sql: `
|
|
41318
|
+
UPDATE service_connections SET
|
|
41319
|
+
provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
|
|
41320
|
+
provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
|
|
41321
|
+
updated_at = datetime('now')
|
|
41322
|
+
WHERE id = ?
|
|
41323
|
+
`,
|
|
41324
|
+
args: [
|
|
41325
|
+
args.providerAccountId ?? null,
|
|
41326
|
+
args.providerAccountEmail?.toLowerCase() ?? null,
|
|
41327
|
+
args.providerAccountName ?? null,
|
|
41328
|
+
args.sourceUpdatedAt,
|
|
41329
|
+
args.connectionId
|
|
41330
|
+
]
|
|
41331
|
+
});
|
|
41332
|
+
}
|
|
41333
|
+
async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
|
|
41334
|
+
const connection = await getOwnedServiceConnection(identity, connectionId);
|
|
41335
|
+
if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
|
|
41336
|
+
await getDb().execute({
|
|
41337
|
+
sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
|
|
41338
|
+
args: [userLabel, connectionId, connection.userId]
|
|
41339
|
+
});
|
|
41340
|
+
return userLabel;
|
|
41341
|
+
}
|
|
41278
41342
|
async function recordServiceConnectionHealth(args) {
|
|
41279
41343
|
await ensureServiceConnectionsSchema();
|
|
41280
41344
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -44114,6 +44178,7 @@ var DEFAULT_NANGO_MCP_URL = "https://api.nango.dev/mcp";
|
|
|
44114
44178
|
var NANGO_TIMEOUT_MS = 2e4;
|
|
44115
44179
|
var NANGO_CONNECTION_PAGE_SIZE = 100;
|
|
44116
44180
|
var NANGO_CONNECTION_MAX_PAGES = 20;
|
|
44181
|
+
var PROVIDER_IDENTITY_RETRY_MS = 6 * 60 * 60 * 1e3;
|
|
44117
44182
|
var MAIN_INTEGRATION_CONTRACT_VERSION = "2026-07-16.1";
|
|
44118
44183
|
var MAIN_INTEGRATION_CONTRACT_HASH = "f037859ec80c2ea2fac6e4a67c1a59f3200dd22d3642c97789b9bb3236723d3e";
|
|
44119
44184
|
var MainNangoTransportError = class extends Error {
|
|
@@ -44217,7 +44282,8 @@ async function nangoRequest(path5, options = {}) {
|
|
|
44217
44282
|
headers: {
|
|
44218
44283
|
authorization: `Bearer ${nangoSecret()}`,
|
|
44219
44284
|
accept: "application/json",
|
|
44220
|
-
...options.body ? { "content-type": "application/json" } : {}
|
|
44285
|
+
...options.body ? { "content-type": "application/json" } : {},
|
|
44286
|
+
...options.headers
|
|
44221
44287
|
},
|
|
44222
44288
|
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
44223
44289
|
signal: AbortSignal.timeout(NANGO_TIMEOUT_MS)
|
|
@@ -44265,22 +44331,142 @@ function discoveredConnection(value) {
|
|
|
44265
44331
|
const upstreamConnectionId = text(row?.connection_id);
|
|
44266
44332
|
const providerConfigKey = text(row?.provider_config_key);
|
|
44267
44333
|
if (!upstreamConnectionId || !providerConfigKey) return null;
|
|
44268
|
-
const tags = asRecord(row?.tags);
|
|
44269
|
-
const endUser = asRecord(row?.end_user);
|
|
44270
44334
|
const errors = Array.isArray(row?.errors) ? row.errors : [];
|
|
44271
44335
|
const authError = errors.some((error) => text(asRecord(error)?.type) === "auth");
|
|
44272
44336
|
return {
|
|
44273
44337
|
upstreamConnectionId,
|
|
44274
44338
|
providerConfigKey,
|
|
44275
44339
|
provider: text(row?.provider) ?? providerConfigKey,
|
|
44276
|
-
|
|
44340
|
+
// Nango's end-user fields identify the MCP Scraper tenant, not the
|
|
44341
|
+
// provider-side principal that authorized this connection.
|
|
44342
|
+
label: null,
|
|
44277
44343
|
lifecycleStatus: authError ? "needs_reauth" : "connected",
|
|
44278
44344
|
reconnectRequired: authError,
|
|
44279
44345
|
createdAt: text(row?.created_at) ?? text(row?.created),
|
|
44280
44346
|
updatedAt: text(row?.updated_at)
|
|
44281
44347
|
};
|
|
44282
44348
|
}
|
|
44283
|
-
|
|
44349
|
+
var IDENTITY_SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set([
|
|
44350
|
+
"facebook",
|
|
44351
|
+
"github-getting-started",
|
|
44352
|
+
"google-analytics",
|
|
44353
|
+
"google-calendar",
|
|
44354
|
+
"google-drive",
|
|
44355
|
+
"google-mail",
|
|
44356
|
+
"google-search-console",
|
|
44357
|
+
"linkedin",
|
|
44358
|
+
"meta-marketing-api",
|
|
44359
|
+
"slack",
|
|
44360
|
+
"twitter-v2",
|
|
44361
|
+
"xero",
|
|
44362
|
+
"youtube",
|
|
44363
|
+
"zoom"
|
|
44364
|
+
]);
|
|
44365
|
+
function scalarId(value) {
|
|
44366
|
+
const valueText = text(value);
|
|
44367
|
+
if (valueText) return valueText;
|
|
44368
|
+
return typeof value === "number" && Number.isFinite(value) ? String(value) : null;
|
|
44369
|
+
}
|
|
44370
|
+
function providerEmail(value) {
|
|
44371
|
+
const email = text(value)?.toLowerCase() ?? null;
|
|
44372
|
+
return email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? email : null;
|
|
44373
|
+
}
|
|
44374
|
+
function compactProviderIdentity(identity) {
|
|
44375
|
+
return identity.id || identity.email || identity.name ? identity : null;
|
|
44376
|
+
}
|
|
44377
|
+
async function providerProxyJson(args) {
|
|
44378
|
+
return nangoRequest(`/proxy${args.path}`, {
|
|
44379
|
+
query: args.query,
|
|
44380
|
+
headers: {
|
|
44381
|
+
"connection-id": args.upstreamConnectionId,
|
|
44382
|
+
"provider-config-key": args.providerConfigKey,
|
|
44383
|
+
...args.baseUrlOverride ? { "base-url-override": args.baseUrlOverride } : {}
|
|
44384
|
+
}
|
|
44385
|
+
});
|
|
44386
|
+
}
|
|
44387
|
+
async function googleProviderAccountIdentity(args) {
|
|
44388
|
+
const request = (path5, query, baseUrlOverride) => providerProxyJson({ ...args, path: path5, query, baseUrlOverride });
|
|
44389
|
+
try {
|
|
44390
|
+
if (args.providerConfigKey === "google-mail") {
|
|
44391
|
+
const payload = asRecord(await request("/gmail/v1/users/me/profile", void 0, "https://gmail.googleapis.com"));
|
|
44392
|
+
const email = providerEmail(payload?.emailAddress);
|
|
44393
|
+
return compactProviderIdentity({ id: email, email, name: null });
|
|
44394
|
+
}
|
|
44395
|
+
if (args.providerConfigKey === "google-drive") {
|
|
44396
|
+
const payload = asRecord(await request("/drive/v3/about", { fields: "user(permissionId,displayName,emailAddress)" }, "https://www.googleapis.com"));
|
|
44397
|
+
const user = asRecord(payload?.user);
|
|
44398
|
+
return compactProviderIdentity({
|
|
44399
|
+
id: text(user?.permissionId),
|
|
44400
|
+
email: providerEmail(user?.emailAddress),
|
|
44401
|
+
name: text(user?.displayName)
|
|
44402
|
+
});
|
|
44403
|
+
}
|
|
44404
|
+
if (args.providerConfigKey === "google-calendar") {
|
|
44405
|
+
const payload = asRecord(await request("/calendar/v3/calendars/primary", void 0, "https://www.googleapis.com"));
|
|
44406
|
+
const id = text(payload?.id);
|
|
44407
|
+
return compactProviderIdentity({ id, email: providerEmail(id), name: text(payload?.summary) });
|
|
44408
|
+
}
|
|
44409
|
+
if (args.providerConfigKey === "youtube") {
|
|
44410
|
+
const payload = asRecord(await request("/youtube/v3/channels", { part: "snippet", mine: "true", maxResults: 1 }, "https://www.googleapis.com"));
|
|
44411
|
+
const first = Array.isArray(payload?.items) ? asRecord(payload.items[0]) : null;
|
|
44412
|
+
return compactProviderIdentity({ id: text(first?.id), email: null, name: text(asRecord(first?.snippet)?.title) });
|
|
44413
|
+
}
|
|
44414
|
+
} catch {
|
|
44415
|
+
}
|
|
44416
|
+
try {
|
|
44417
|
+
const payload = asRecord(await request("/v1/userinfo", void 0, "https://openidconnect.googleapis.com"));
|
|
44418
|
+
return compactProviderIdentity({
|
|
44419
|
+
id: text(payload?.sub),
|
|
44420
|
+
email: payload?.email_verified === false ? null : providerEmail(payload?.email),
|
|
44421
|
+
name: text(payload?.name)
|
|
44422
|
+
});
|
|
44423
|
+
} catch {
|
|
44424
|
+
return null;
|
|
44425
|
+
}
|
|
44426
|
+
}
|
|
44427
|
+
async function getNangoProviderAccountIdentity(args) {
|
|
44428
|
+
if (!IDENTITY_SUPPORTED_PROVIDERS.has(args.providerConfigKey)) return null;
|
|
44429
|
+
if (args.providerConfigKey.startsWith("google-") || args.providerConfigKey === "youtube") {
|
|
44430
|
+
return googleProviderAccountIdentity(args);
|
|
44431
|
+
}
|
|
44432
|
+
try {
|
|
44433
|
+
if (args.providerConfigKey === "github-getting-started") {
|
|
44434
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/user" }));
|
|
44435
|
+
return compactProviderIdentity({ id: scalarId(payload?.id), email: providerEmail(payload?.email), name: text(payload?.name) ?? text(payload?.login) });
|
|
44436
|
+
}
|
|
44437
|
+
if (args.providerConfigKey === "slack") {
|
|
44438
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/api/auth.test" }));
|
|
44439
|
+
return compactProviderIdentity({ id: text(payload?.user_id), email: null, name: [text(payload?.user), text(payload?.team)].filter(Boolean).join(" \xB7 ") || null });
|
|
44440
|
+
}
|
|
44441
|
+
if (args.providerConfigKey === "zoom") {
|
|
44442
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/v2/users/me" }));
|
|
44443
|
+
const fullName = [text(payload?.first_name), text(payload?.last_name)].filter(Boolean).join(" ");
|
|
44444
|
+
return compactProviderIdentity({ id: text(payload?.id), email: providerEmail(payload?.email), name: text(payload?.display_name) ?? (fullName || null) });
|
|
44445
|
+
}
|
|
44446
|
+
if (args.providerConfigKey === "linkedin") {
|
|
44447
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/v2/userinfo" }));
|
|
44448
|
+
return compactProviderIdentity({ id: text(payload?.sub), email: payload?.email_verified === false ? null : providerEmail(payload?.email), name: text(payload?.name) });
|
|
44449
|
+
}
|
|
44450
|
+
if (args.providerConfigKey === "meta-marketing-api" || args.providerConfigKey === "facebook") {
|
|
44451
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/me", query: { fields: "id,name,email" } }));
|
|
44452
|
+
return compactProviderIdentity({ id: text(payload?.id), email: providerEmail(payload?.email), name: text(payload?.name) });
|
|
44453
|
+
}
|
|
44454
|
+
if (args.providerConfigKey === "twitter-v2") {
|
|
44455
|
+
const payload = asRecord(await providerProxyJson({ ...args, path: "/2/users/me", query: { "user.fields": "id,name,username" } }));
|
|
44456
|
+
const data = asRecord(payload?.data);
|
|
44457
|
+
return compactProviderIdentity({ id: text(data?.id), email: null, name: text(data?.username) ?? text(data?.name) });
|
|
44458
|
+
}
|
|
44459
|
+
if (args.providerConfigKey === "xero") {
|
|
44460
|
+
const payload = await providerProxyJson({ ...args, path: "/connections" });
|
|
44461
|
+
const first = Array.isArray(payload) ? asRecord(payload[0]) : null;
|
|
44462
|
+
return compactProviderIdentity({ id: text(first?.tenantId), email: null, name: text(first?.tenantName) });
|
|
44463
|
+
}
|
|
44464
|
+
} catch {
|
|
44465
|
+
return null;
|
|
44466
|
+
}
|
|
44467
|
+
return null;
|
|
44468
|
+
}
|
|
44469
|
+
async function discoverOwnedNangoConnections(identity, options = {}) {
|
|
44284
44470
|
const rows = [];
|
|
44285
44471
|
for (let page = 0; page < NANGO_CONNECTION_MAX_PAGES; page += 1) {
|
|
44286
44472
|
const payload = await nangoRequest("/connections", {
|
|
@@ -44291,6 +44477,35 @@ async function discoverOwnedNangoConnections(identity) {
|
|
|
44291
44477
|
if (pageRows.length < NANGO_CONNECTION_PAGE_SIZE) break;
|
|
44292
44478
|
}
|
|
44293
44479
|
const discovered = rows.filter((row) => connectionIdentityMatches(row, identity)).map(discoveredConnection).filter((connection) => connection !== null);
|
|
44480
|
+
const reconciled = await reconcileDiscoveredNangoConnections(identity, discovered);
|
|
44481
|
+
if (options.resolveProviderIdentity === false) return reconciled;
|
|
44482
|
+
const remoteByKey = new Map(discovered.map((connection) => [
|
|
44483
|
+
`${connection.providerConfigKey}\0${connection.upstreamConnectionId}`,
|
|
44484
|
+
connection
|
|
44485
|
+
]));
|
|
44486
|
+
await Promise.all(reconciled.map(async (connection) => {
|
|
44487
|
+
if (connection.lifecycleStatus !== "connected" || !connection.upstreamConnectionId) return;
|
|
44488
|
+
const remote = remoteByKey.get(`${connection.providerConfigKey}\0${connection.upstreamConnectionId}`);
|
|
44489
|
+
if (!remote) return;
|
|
44490
|
+
const sourceUpdatedAt = remote.updatedAt ?? remote.createdAt ?? connection.lifecycleStatus;
|
|
44491
|
+
const hasProviderIdentity = Boolean(
|
|
44492
|
+
connection.providerAccountId || connection.providerAccountEmail || connection.providerAccountName
|
|
44493
|
+
);
|
|
44494
|
+
const checkedAt = connection.providerIdentityCheckedAt ? Date.parse(`${connection.providerIdentityCheckedAt.replace(" ", "T")}Z`) : Number.NaN;
|
|
44495
|
+
const unavailableIdentityIsFresh = !hasProviderIdentity && Number.isFinite(checkedAt) && Date.now() - checkedAt < PROVIDER_IDENTITY_RETRY_MS;
|
|
44496
|
+
if (connection.providerIdentitySourceUpdatedAt === sourceUpdatedAt && (hasProviderIdentity || unavailableIdentityIsFresh)) return;
|
|
44497
|
+
const providerIdentity = await getNangoProviderAccountIdentity({
|
|
44498
|
+
upstreamConnectionId: connection.upstreamConnectionId,
|
|
44499
|
+
providerConfigKey: connection.providerConfigKey
|
|
44500
|
+
});
|
|
44501
|
+
await setServiceConnectionProviderIdentity({
|
|
44502
|
+
connectionId: connection.id,
|
|
44503
|
+
providerAccountId: providerIdentity?.id,
|
|
44504
|
+
providerAccountEmail: providerIdentity?.email,
|
|
44505
|
+
providerAccountName: providerIdentity?.name,
|
|
44506
|
+
sourceUpdatedAt
|
|
44507
|
+
});
|
|
44508
|
+
}));
|
|
44294
44509
|
return reconcileDiscoveredNangoConnections(identity, discovered);
|
|
44295
44510
|
}
|
|
44296
44511
|
async function listNangoCatalogDirect() {
|
|
@@ -44965,7 +45180,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
44965
45180
|
if (mainOwnsIntegrations()) {
|
|
44966
45181
|
let connections;
|
|
44967
45182
|
try {
|
|
44968
|
-
connections = await discoverOwnedNangoConnections(identity);
|
|
45183
|
+
connections = await discoverOwnedNangoConnections(identity, { resolveProviderIdentity: !options.summaryOnly });
|
|
44969
45184
|
} catch (error) {
|
|
44970
45185
|
try {
|
|
44971
45186
|
connections = await listServiceConnections(identity, "nango");
|
|
@@ -44986,11 +45201,12 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
44986
45201
|
connectionId: connection.id,
|
|
44987
45202
|
providerConfigKey: connection.providerConfigKey,
|
|
44988
45203
|
provider: connection.provider,
|
|
44989
|
-
label: connection.label || connection.providerConfigKey,
|
|
44990
|
-
|
|
44991
|
-
|
|
44992
|
-
|
|
44993
|
-
|
|
45204
|
+
label: connection.userLabel || connection.providerAccountEmail || connection.providerAccountName || (connection.label && connection.label.toLowerCase() !== identity.trim().toLowerCase() ? connection.label : null) || connection.providerConfigKey,
|
|
45205
|
+
userLabel: connection.userLabel,
|
|
45206
|
+
providerAccountId: connection.providerAccountId,
|
|
45207
|
+
providerAccountEmail: connection.providerAccountEmail,
|
|
45208
|
+
providerAccountName: connection.providerAccountName,
|
|
45209
|
+
providerIdentityStatus: connection.providerIdentityCheckedAt ? connection.providerAccountId || connection.providerAccountEmail || connection.providerAccountName ? "verified" : "unavailable" : "pending",
|
|
44994
45210
|
status: connection.reconnectRequired ? "needs_reauth" : "connected",
|
|
44995
45211
|
lifecycleStatus: connection.lifecycleStatus,
|
|
44996
45212
|
operationalStatus: connection.operationalStatus,
|
|
@@ -45091,6 +45307,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
45091
45307
|
providerConfigKey,
|
|
45092
45308
|
provider,
|
|
45093
45309
|
label,
|
|
45310
|
+
userLabel: null,
|
|
45094
45311
|
providerAccountId,
|
|
45095
45312
|
providerAccountEmail,
|
|
45096
45313
|
providerAccountName,
|
|
@@ -46932,6 +47149,26 @@ app.get("/schedule-connections", auth4, async (c) => {
|
|
|
46932
47149
|
return scheduleConnectionError(c, err, "Unable to load service connections.");
|
|
46933
47150
|
}
|
|
46934
47151
|
});
|
|
47152
|
+
app.patch("/schedule-connections/:id/label", auth4, async (c) => {
|
|
47153
|
+
const user = c.get("user");
|
|
47154
|
+
const body = await c.req.json().catch(() => ({}));
|
|
47155
|
+
if (body.label !== null && typeof body.label !== "string") {
|
|
47156
|
+
return c.json({ ok: false, error: "label must be a string or null." }, 400);
|
|
47157
|
+
}
|
|
47158
|
+
const label = typeof body.label === "string" ? body.label.normalize("NFKC").trim().replace(/\s+/g, " ") : null;
|
|
47159
|
+
if (label && label.length > 80) {
|
|
47160
|
+
return c.json({ ok: false, error: "label must contain at most 80 characters." }, 400);
|
|
47161
|
+
}
|
|
47162
|
+
try {
|
|
47163
|
+
const userLabel = await setServiceConnectionUserLabel(user.email, c.req.param("id"), label || null);
|
|
47164
|
+
return c.json({ ok: true, userLabel });
|
|
47165
|
+
} catch (err) {
|
|
47166
|
+
if (err instanceof Error && err.message === "service_connection_not_found") {
|
|
47167
|
+
return c.json({ ok: false, error: "Service connection not found." }, 404);
|
|
47168
|
+
}
|
|
47169
|
+
return scheduleConnectionError(c, err, "Unable to rename this service connection.");
|
|
47170
|
+
}
|
|
47171
|
+
});
|
|
46935
47172
|
app.post("/schedule-connections/billing/reconcile", auth4, requireIntegrationsTier, async (c) => {
|
|
46936
47173
|
const user = c.get("user");
|
|
46937
47174
|
try {
|
|
@@ -47518,6 +47755,7 @@ app.post("/integrations/billing/reconcile", (c) => forwardIntegrationAlias(c, "/
|
|
|
47518
47755
|
app.post("/integrations/:id/reconnect-session", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/reconnect-session`));
|
|
47519
47756
|
app.post("/integrations/:id/enable-actions", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/enable-actions`));
|
|
47520
47757
|
app.post("/integrations/:id/test", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/test`));
|
|
47758
|
+
app.patch("/integrations/:id/label", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/label`));
|
|
47521
47759
|
app.delete("/integrations/:id", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}`));
|
|
47522
47760
|
app.post("/integrations/actions/read", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/read"));
|
|
47523
47761
|
app.post("/integrations/actions/describe", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/describe"));
|