mcp-scraper 0.66.2 → 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 +24 -1
- package/README.md +2 -2
- package/dist/bin/api-server.cjs +281 -21
- 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 +2 -2
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +30 -9
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/{chunk-OZDCTNUV.js → chunk-2XVDLF6H.js} +1 -1
- package/dist/{chunk-ALEBMPOW.js → chunk-EGNT3AY2.js} +1 -1
- package/dist/{chunk-YKL3O7IU.js → chunk-UPN4W4SV.js} +29 -8
- package/dist/{server-Q7VYGSGQ.js → server-YATQ3WHT.js} +251 -13
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ 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
|
+
|
|
18
|
+
## [0.66.3] - 2026-08-25
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Runtime MCP discovery now omits output schemas across hosted OAuth, API-key, npm stdio, and MCPB clients, while the complete generated developer contract continues to supply typed Node and Python SDK outputs.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Restored compatibility for strict MCP clients that could connect successfully but silently register zero tools when the complete output-schema catalog was advertised; tool calls retain both readable content and structured results.
|
|
27
|
+
|
|
7
28
|
## [0.66.2] - 2026-08-24
|
|
8
29
|
|
|
9
30
|
### Changed
|
|
@@ -1088,7 +1109,9 @@ All notable changes to MCP Scraper are documented here. The format is based on [
|
|
|
1088
1109
|
- Write actions remain unavailable until the account owner explicitly enables them.
|
|
1089
1110
|
- Provider-specific connection data is normalized into one agent-facing contract.
|
|
1090
1111
|
|
|
1091
|
-
[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
|
|
1114
|
+
[0.66.3]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.2...v0.66.3
|
|
1092
1115
|
[0.66.2]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.1...v0.66.2
|
|
1093
1116
|
[0.66.1]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.66.0...v0.66.1
|
|
1094
1117
|
[0.66.0]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.65.1...v0.66.0
|
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
|
|
|
@@ -315,7 +315,7 @@ For Google SERP and Maps tools, callers provide the query, two-letter country co
|
|
|
315
315
|
|
|
316
316
|
The `mcp-scraper` server (and the MCPB bundle, which runs it) exposes both sections through one MCP server.
|
|
317
317
|
|
|
318
|
-
All MCP tools
|
|
318
|
+
All MCP tools return `structuredContent` with the IDs, URLs, CSV paths, transcripts, browser session handles, replay paths, artifacts, recipe fields, or blueprint fields needed by the next step, plus readable text content for compatibility. Runtime `tools/list` omits output schemas so strict clients can register the complete catalog; the generated developer manifest retains every canonical output schema for validation and typed SDK generation. All tools carry MCP annotations; file-writing tools such as replay downloads and annotations state their filesystem side effects.
|
|
319
319
|
|
|
320
320
|
The canonical tool inventory is generated at `docs/mcp-tool-manifest.generated.json`. The unified server exposes 259 tools: 158 scraper, browser, workflow, billing, and connected-service tools plus 101 durable-memory tools. The scraper-side inventory includes rendered site-content similarity, governed Local Sourcebook tools, Transparent Commons entity, linkset, and publication tools, direct site-export text and image readers, the searchable News Publisher and Editorial Reading Room template workflow, and 24 X-Ray tools for pixel administration, reporting, attribution journeys, campaign links, exports, and scheduled reports. Successful evidence-compiled Local Sourcebook revisions publish automatically to their canonical `localsourcebook.com` category profile and review URLs; administrator controls handle exceptional rejection or unpublishing. Release verification compares the exact local and hosted tool-name sets, not only the count.
|
|
321
321
|
|
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,
|
|
@@ -50109,14 +50109,30 @@ var init_server_instructions = __esm({
|
|
|
50109
50109
|
});
|
|
50110
50110
|
|
|
50111
50111
|
// src/mcp/output-schema-registry.ts
|
|
50112
|
+
function resolveOutputSchemaMode(env = process.env) {
|
|
50113
|
+
const requested = env.MCP_SCRAPER_OUTPUT_SCHEMA_MODE?.trim().toLowerCase();
|
|
50114
|
+
if (requested) {
|
|
50115
|
+
if (requested === "contract" || requested === "all") return "contract";
|
|
50116
|
+
if (requested === "runtime" || requested === "none" || requested === "essential") return "runtime";
|
|
50117
|
+
throw new Error(`Unsupported MCP_SCRAPER_OUTPUT_SCHEMA_MODE: ${requested}`);
|
|
50118
|
+
}
|
|
50119
|
+
const legacyAdvertisement = env.MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS?.trim().toLowerCase();
|
|
50120
|
+
if (legacyAdvertisement === "true") return "contract";
|
|
50121
|
+
if (legacyAdvertisement === void 0 || legacyAdvertisement === "" || legacyAdvertisement === "false") {
|
|
50122
|
+
return "runtime";
|
|
50123
|
+
}
|
|
50124
|
+
throw new Error(`Unsupported MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS value: ${legacyAdvertisement}`);
|
|
50125
|
+
}
|
|
50112
50126
|
function recordOutputSchema(name, schema) {
|
|
50113
50127
|
OUTPUT_SCHEMAS[name] = schema;
|
|
50114
|
-
return schema;
|
|
50128
|
+
return ADVERTISE_OUTPUT_SCHEMAS ? schema : void 0;
|
|
50115
50129
|
}
|
|
50116
|
-
var OUTPUT_SCHEMAS;
|
|
50130
|
+
var OUTPUT_SCHEMA_MODE, ADVERTISE_OUTPUT_SCHEMAS, OUTPUT_SCHEMAS;
|
|
50117
50131
|
var init_output_schema_registry = __esm({
|
|
50118
50132
|
"src/mcp/output-schema-registry.ts"() {
|
|
50119
50133
|
"use strict";
|
|
50134
|
+
OUTPUT_SCHEMA_MODE = resolveOutputSchemaMode();
|
|
50135
|
+
ADVERTISE_OUTPUT_SCHEMAS = OUTPUT_SCHEMA_MODE === "contract";
|
|
50120
50136
|
OUTPUT_SCHEMAS = {};
|
|
50121
50137
|
}
|
|
50122
50138
|
});
|
|
@@ -53837,7 +53853,12 @@ var init_mcp_tool_schemas = __esm({
|
|
|
53837
53853
|
connectionId: import_zod45.z.string(),
|
|
53838
53854
|
providerConfigKey: import_zod45.z.string(),
|
|
53839
53855
|
provider: import_zod45.z.string().nullable().optional(),
|
|
53840
|
-
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."),
|
|
53841
53862
|
status: import_zod45.z.string(),
|
|
53842
53863
|
lifecycleStatus: import_zod45.z.enum(["pending", "connected", "needs_reauth", "disconnecting", "disconnected"]).optional().describe("Credential lifecycle. This is separate from current provider availability."),
|
|
53843
53864
|
operationalStatus: import_zod45.z.enum(["unknown", "available", "degraded", "unavailable"]).optional().describe("Last observed provider transport availability. Unavailable does not imply reconnect is required."),
|
|
@@ -56341,7 +56362,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
56341
56362
|
}, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
|
|
56342
56363
|
server.registerTool("list_service_connections", {
|
|
56343
56364
|
title: "List Connected Services",
|
|
56344
|
-
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.",
|
|
56345
56366
|
inputSchema: ListServiceConnectionsInputSchema,
|
|
56346
56367
|
outputSchema: recordOutputSchema("list_service_connections", ListServiceConnectionsOutputSchema),
|
|
56347
56368
|
annotations: { title: "List Connected Services", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
@@ -81153,6 +81174,12 @@ function ensureServiceConnectionsSchema() {
|
|
|
81153
81174
|
transport TEXT NOT NULL,
|
|
81154
81175
|
upstream_connection_id TEXT,
|
|
81155
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,
|
|
81156
81183
|
lifecycle_status TEXT NOT NULL DEFAULT 'pending',
|
|
81157
81184
|
operational_status TEXT NOT NULL DEFAULT 'unknown',
|
|
81158
81185
|
reconnect_required INTEGER NOT NULL DEFAULT 0,
|
|
@@ -81174,6 +81201,30 @@ function ensureServiceConnectionsSchema() {
|
|
|
81174
81201
|
UNIQUE(user_id, provider_config_key, upstream_connection_id)
|
|
81175
81202
|
)
|
|
81176
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
|
+
}
|
|
81177
81228
|
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
|
|
81178
81229
|
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
|
|
81179
81230
|
await db.execute(`
|
|
@@ -81234,6 +81285,12 @@ function rowToRecord(row) {
|
|
|
81234
81285
|
transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
|
|
81235
81286
|
upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
|
|
81236
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,
|
|
81237
81294
|
lifecycleStatus: String(row.lifecycle_status),
|
|
81238
81295
|
operationalStatus: String(row.operational_status),
|
|
81239
81296
|
reconnectRequired: Number(row.reconnect_required) === 1,
|
|
@@ -81319,6 +81376,34 @@ async function updateServiceConnectionTools(connectionId, readTools, actionTools
|
|
|
81319
81376
|
args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
|
|
81320
81377
|
});
|
|
81321
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
|
+
}
|
|
81322
81407
|
async function recordServiceConnectionHealth(args) {
|
|
81323
81408
|
await ensureServiceConnectionsSchema();
|
|
81324
81409
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -84395,7 +84480,8 @@ async function nangoRequest(path6, options = {}) {
|
|
|
84395
84480
|
headers: {
|
|
84396
84481
|
authorization: `Bearer ${nangoSecret()}`,
|
|
84397
84482
|
accept: "application/json",
|
|
84398
|
-
...options.body ? { "content-type": "application/json" } : {}
|
|
84483
|
+
...options.body ? { "content-type": "application/json" } : {},
|
|
84484
|
+
...options.headers
|
|
84399
84485
|
},
|
|
84400
84486
|
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
84401
84487
|
signal: AbortSignal.timeout(NANGO_TIMEOUT_MS)
|
|
@@ -84443,22 +84529,126 @@ function discoveredConnection(value) {
|
|
|
84443
84529
|
const upstreamConnectionId = text(row?.connection_id);
|
|
84444
84530
|
const providerConfigKey = text(row?.provider_config_key);
|
|
84445
84531
|
if (!upstreamConnectionId || !providerConfigKey) return null;
|
|
84446
|
-
const tags = asRecord2(row?.tags);
|
|
84447
|
-
const endUser = asRecord2(row?.end_user);
|
|
84448
84532
|
const errors = Array.isArray(row?.errors) ? row.errors : [];
|
|
84449
84533
|
const authError = errors.some((error) => text(asRecord2(error)?.type) === "auth");
|
|
84450
84534
|
return {
|
|
84451
84535
|
upstreamConnectionId,
|
|
84452
84536
|
providerConfigKey,
|
|
84453
84537
|
provider: text(row?.provider) ?? providerConfigKey,
|
|
84454
|
-
|
|
84538
|
+
// Nango's end-user fields identify the MCP Scraper tenant, not the
|
|
84539
|
+
// provider-side principal that authorized this connection.
|
|
84540
|
+
label: null,
|
|
84455
84541
|
lifecycleStatus: authError ? "needs_reauth" : "connected",
|
|
84456
84542
|
reconnectRequired: authError,
|
|
84457
84543
|
createdAt: text(row?.created_at) ?? text(row?.created),
|
|
84458
84544
|
updatedAt: text(row?.updated_at)
|
|
84459
84545
|
};
|
|
84460
84546
|
}
|
|
84461
|
-
|
|
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 = {}) {
|
|
84462
84652
|
const rows = [];
|
|
84463
84653
|
for (let page = 0; page < NANGO_CONNECTION_MAX_PAGES; page += 1) {
|
|
84464
84654
|
const payload = await nangoRequest("/connections", {
|
|
@@ -84469,6 +84659,35 @@ async function discoverOwnedNangoConnections(identity) {
|
|
|
84469
84659
|
if (pageRows.length < NANGO_CONNECTION_PAGE_SIZE) break;
|
|
84470
84660
|
}
|
|
84471
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
|
+
}));
|
|
84472
84691
|
return reconcileDiscoveredNangoConnections(identity, discovered);
|
|
84473
84692
|
}
|
|
84474
84693
|
async function listNangoCatalogDirect() {
|
|
@@ -84731,7 +84950,7 @@ async function describeNangoToolDirect(identity, connectionId, toolName) {
|
|
|
84731
84950
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
84732
84951
|
};
|
|
84733
84952
|
}
|
|
84734
|
-
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;
|
|
84735
84954
|
var init_main_nango_transport = __esm({
|
|
84736
84955
|
"src/api/main-nango-transport.ts"() {
|
|
84737
84956
|
"use strict";
|
|
@@ -84745,6 +84964,7 @@ var init_main_nango_transport = __esm({
|
|
|
84745
84964
|
NANGO_TIMEOUT_MS = 2e4;
|
|
84746
84965
|
NANGO_CONNECTION_PAGE_SIZE = 100;
|
|
84747
84966
|
NANGO_CONNECTION_MAX_PAGES = 20;
|
|
84967
|
+
PROVIDER_IDENTITY_RETRY_MS = 6 * 60 * 60 * 1e3;
|
|
84748
84968
|
MAIN_INTEGRATION_CONTRACT_VERSION = "2026-07-16.1";
|
|
84749
84969
|
MAIN_INTEGRATION_CONTRACT_HASH = "f037859ec80c2ea2fac6e4a67c1a59f3200dd22d3642c97789b9bb3236723d3e";
|
|
84750
84970
|
MainNangoTransportError = class extends Error {
|
|
@@ -84761,6 +84981,22 @@ var init_main_nango_transport = __esm({
|
|
|
84761
84981
|
this.retryAfterMs = retryAfterMs;
|
|
84762
84982
|
}
|
|
84763
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
|
+
]);
|
|
84764
85000
|
}
|
|
84765
85001
|
});
|
|
84766
85002
|
|
|
@@ -85051,7 +85287,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
85051
85287
|
if (mainOwnsIntegrations()) {
|
|
85052
85288
|
let connections;
|
|
85053
85289
|
try {
|
|
85054
|
-
connections = await discoverOwnedNangoConnections(identity);
|
|
85290
|
+
connections = await discoverOwnedNangoConnections(identity, { resolveProviderIdentity: !options.summaryOnly });
|
|
85055
85291
|
} catch (error) {
|
|
85056
85292
|
try {
|
|
85057
85293
|
connections = await listServiceConnections(identity, "nango");
|
|
@@ -85072,11 +85308,12 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
85072
85308
|
connectionId: connection.id,
|
|
85073
85309
|
providerConfigKey: connection.providerConfigKey,
|
|
85074
85310
|
provider: connection.provider,
|
|
85075
|
-
label: connection.label || connection.providerConfigKey,
|
|
85076
|
-
|
|
85077
|
-
|
|
85078
|
-
|
|
85079
|
-
|
|
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",
|
|
85080
85317
|
status: connection.reconnectRequired ? "needs_reauth" : "connected",
|
|
85081
85318
|
lifecycleStatus: connection.lifecycleStatus,
|
|
85082
85319
|
operationalStatus: connection.operationalStatus,
|
|
@@ -85177,6 +85414,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
85177
85414
|
providerConfigKey,
|
|
85178
85415
|
provider,
|
|
85179
85416
|
label,
|
|
85417
|
+
userLabel: null,
|
|
85180
85418
|
providerAccountId,
|
|
85181
85419
|
providerAccountEmail,
|
|
85182
85420
|
providerAccountName,
|
|
@@ -86952,6 +87190,7 @@ var init_server = __esm({
|
|
|
86952
87190
|
init_scheduling_access();
|
|
86953
87191
|
init_memory_db();
|
|
86954
87192
|
init_nango_control();
|
|
87193
|
+
init_service_connections();
|
|
86955
87194
|
init_resend_control();
|
|
86956
87195
|
init_scheduler_integration_auth();
|
|
86957
87196
|
init_connected_account_billing();
|
|
@@ -87543,6 +87782,26 @@ var init_server = __esm({
|
|
|
87543
87782
|
return scheduleConnectionError(c, err, "Unable to load service connections.");
|
|
87544
87783
|
}
|
|
87545
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
|
+
});
|
|
87546
87805
|
app.post("/schedule-connections/billing/reconcile", auth4, requireIntegrationsTier, async (c) => {
|
|
87547
87806
|
const user = c.get("user");
|
|
87548
87807
|
try {
|
|
@@ -88101,6 +88360,7 @@ var init_server = __esm({
|
|
|
88101
88360
|
app.post("/integrations/:id/reconnect-session", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/reconnect-session`));
|
|
88102
88361
|
app.post("/integrations/:id/enable-actions", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}/enable-actions`));
|
|
88103
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`));
|
|
88104
88364
|
app.delete("/integrations/:id", (c) => forwardIntegrationAlias(c, `/schedule-connections/${encodeURIComponent(c.req.param("id"))}`));
|
|
88105
88365
|
app.post("/integrations/actions/read", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/read"));
|
|
88106
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
|
]);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// release-message.json
|
|
5
5
|
var release_message_default = {
|
|
6
|
-
message: "
|
|
6
|
+
message: "Strict MCP clients now discover all 259 tools through a compact runtime catalog, while generated SDKs retain complete output types."
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
// src/install-terminal.ts
|
|
@@ -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;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
renderInstallTerminal
|
|
4
|
-
} from "../chunk-
|
|
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");
|
|
@@ -2237,10 +2237,26 @@ async function annotateReplayVideo(inputFilePath, outputFilePath, options) {
|
|
|
2237
2237
|
}
|
|
2238
2238
|
|
|
2239
2239
|
// src/mcp/output-schema-registry.ts
|
|
2240
|
+
function resolveOutputSchemaMode(env = process.env) {
|
|
2241
|
+
const requested = env.MCP_SCRAPER_OUTPUT_SCHEMA_MODE?.trim().toLowerCase();
|
|
2242
|
+
if (requested) {
|
|
2243
|
+
if (requested === "contract" || requested === "all") return "contract";
|
|
2244
|
+
if (requested === "runtime" || requested === "none" || requested === "essential") return "runtime";
|
|
2245
|
+
throw new Error(`Unsupported MCP_SCRAPER_OUTPUT_SCHEMA_MODE: ${requested}`);
|
|
2246
|
+
}
|
|
2247
|
+
const legacyAdvertisement = env.MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS?.trim().toLowerCase();
|
|
2248
|
+
if (legacyAdvertisement === "true") return "contract";
|
|
2249
|
+
if (legacyAdvertisement === void 0 || legacyAdvertisement === "" || legacyAdvertisement === "false") {
|
|
2250
|
+
return "runtime";
|
|
2251
|
+
}
|
|
2252
|
+
throw new Error(`Unsupported MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS value: ${legacyAdvertisement}`);
|
|
2253
|
+
}
|
|
2254
|
+
var OUTPUT_SCHEMA_MODE = resolveOutputSchemaMode();
|
|
2255
|
+
var ADVERTISE_OUTPUT_SCHEMAS = OUTPUT_SCHEMA_MODE === "contract";
|
|
2240
2256
|
var OUTPUT_SCHEMAS = {};
|
|
2241
2257
|
function recordOutputSchema(name, schema) {
|
|
2242
2258
|
OUTPUT_SCHEMAS[name] = schema;
|
|
2243
|
-
return schema;
|
|
2259
|
+
return ADVERTISE_OUTPUT_SCHEMAS ? schema : void 0;
|
|
2244
2260
|
}
|
|
2245
2261
|
|
|
2246
2262
|
// src/mcp/browser-agent-mcp-server.ts
|
|
@@ -8392,10 +8408,10 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
8392
8408
|
artifact, call \`report_artifact_read\` with the returned artifactId and follow nextOffset until null. This
|
|
8393
8409
|
works through the authenticated MCP connection even when the client cannot open the signed download URL;
|
|
8394
8410
|
do not try curl or web_fetch. Use \`archive_read\` for ZIP archives.
|
|
8395
|
-
- Before using a connected account, call \`list_service_connections
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
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.
|
|
8399
8415
|
- For Google Search Console, use \`export_connected_service_data\` with
|
|
8400
8416
|
\`dataset:"search_console_performance"\` when the person asks for a fresh JSONL download. When they
|
|
8401
8417
|
ask to schedule ingestion or repeatedly filter historical rows, create a \`connection_sync\` schedule,
|
|
@@ -12048,7 +12064,12 @@ var ListServiceConnectionsOutputSchema = {
|
|
|
12048
12064
|
connectionId: import_zod8.z.string(),
|
|
12049
12065
|
providerConfigKey: import_zod8.z.string(),
|
|
12050
12066
|
provider: import_zod8.z.string().nullable().optional(),
|
|
12051
|
-
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."),
|
|
12052
12073
|
status: import_zod8.z.string(),
|
|
12053
12074
|
lifecycleStatus: import_zod8.z.enum(["pending", "connected", "needs_reauth", "disconnecting", "disconnected"]).optional().describe("Credential lifecycle. This is separate from current provider availability."),
|
|
12054
12075
|
operationalStatus: import_zod8.z.enum(["unknown", "available", "degraded", "unavailable"]).optional().describe("Last observed provider transport availability. Unavailable does not imply reconnect is required."),
|
|
@@ -14362,7 +14383,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
14362
14383
|
}, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
|
|
14363
14384
|
server.registerTool("list_service_connections", {
|
|
14364
14385
|
title: "List Connected Services",
|
|
14365
|
-
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.",
|
|
14366
14387
|
inputSchema: ListServiceConnectionsInputSchema,
|
|
14367
14388
|
outputSchema: recordOutputSchema("list_service_connections", ListServiceConnectionsOutputSchema),
|
|
14368
14389
|
annotations: { title: "List Connected Services", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
@@ -18639,7 +18660,7 @@ var HostedMemoryMcpToolExecutor = class {
|
|
|
18639
18660
|
|
|
18640
18661
|
// release-message.json
|
|
18641
18662
|
var release_message_default = {
|
|
18642
|
-
message: "
|
|
18663
|
+
message: "Strict MCP clients now discover all 259 tools through a compact runtime catalog, while generated SDKs retain complete output types."
|
|
18643
18664
|
};
|
|
18644
18665
|
|
|
18645
18666
|
// src/install-terminal.ts
|
|
@@ -13,18 +13,18 @@ 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";
|
|
20
20
|
import {
|
|
21
21
|
renderInstallTerminal
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-2XVDLF6H.js";
|
|
23
23
|
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";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// release-message.json
|
|
2
2
|
var release_message_default = {
|
|
3
|
-
message: "
|
|
3
|
+
message: "Strict MCP clients now discover all 259 tools through a compact runtime catalog, while generated SDKs retain complete output types."
|
|
4
4
|
};
|
|
5
5
|
|
|
6
6
|
// src/install-terminal.ts
|
|
@@ -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,
|
|
@@ -5006,10 +5006,26 @@ ${rows}` : ""
|
|
|
5006
5006
|
}
|
|
5007
5007
|
|
|
5008
5008
|
// src/mcp/output-schema-registry.ts
|
|
5009
|
+
function resolveOutputSchemaMode(env = process.env) {
|
|
5010
|
+
const requested = env.MCP_SCRAPER_OUTPUT_SCHEMA_MODE?.trim().toLowerCase();
|
|
5011
|
+
if (requested) {
|
|
5012
|
+
if (requested === "contract" || requested === "all") return "contract";
|
|
5013
|
+
if (requested === "runtime" || requested === "none" || requested === "essential") return "runtime";
|
|
5014
|
+
throw new Error(`Unsupported MCP_SCRAPER_OUTPUT_SCHEMA_MODE: ${requested}`);
|
|
5015
|
+
}
|
|
5016
|
+
const legacyAdvertisement = env.MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS?.trim().toLowerCase();
|
|
5017
|
+
if (legacyAdvertisement === "true") return "contract";
|
|
5018
|
+
if (legacyAdvertisement === void 0 || legacyAdvertisement === "" || legacyAdvertisement === "false") {
|
|
5019
|
+
return "runtime";
|
|
5020
|
+
}
|
|
5021
|
+
throw new Error(`Unsupported MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS value: ${legacyAdvertisement}`);
|
|
5022
|
+
}
|
|
5023
|
+
var OUTPUT_SCHEMA_MODE = resolveOutputSchemaMode();
|
|
5024
|
+
var ADVERTISE_OUTPUT_SCHEMAS = OUTPUT_SCHEMA_MODE === "contract";
|
|
5009
5025
|
var OUTPUT_SCHEMAS = {};
|
|
5010
5026
|
function recordOutputSchema(name, schema) {
|
|
5011
5027
|
OUTPUT_SCHEMAS[name] = schema;
|
|
5012
|
-
return schema;
|
|
5028
|
+
return ADVERTISE_OUTPUT_SCHEMAS ? schema : void 0;
|
|
5013
5029
|
}
|
|
5014
5030
|
|
|
5015
5031
|
// src/mcp/meta-ad-creative-media.ts
|
|
@@ -9426,7 +9442,12 @@ var ListServiceConnectionsOutputSchema = {
|
|
|
9426
9442
|
connectionId: z6.string(),
|
|
9427
9443
|
providerConfigKey: z6.string(),
|
|
9428
9444
|
provider: z6.string().nullable().optional(),
|
|
9429
|
-
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."),
|
|
9430
9451
|
status: z6.string(),
|
|
9431
9452
|
lifecycleStatus: z6.enum(["pending", "connected", "needs_reauth", "disconnecting", "disconnected"]).optional().describe("Credential lifecycle. This is separate from current provider availability."),
|
|
9432
9453
|
operationalStatus: z6.enum(["unknown", "available", "degraded", "unavailable"]).optional().describe("Last observed provider transport availability. Unavailable does not imply reconnect is required."),
|
|
@@ -11871,7 +11892,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
11871
11892
|
}, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
|
|
11872
11893
|
server.registerTool("list_service_connections", {
|
|
11873
11894
|
title: "List Connected Services",
|
|
11874
|
-
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.",
|
|
11875
11896
|
inputSchema: ListServiceConnectionsInputSchema,
|
|
11876
11897
|
outputSchema: recordOutputSchema("list_service_connections", ListServiceConnectionsOutputSchema),
|
|
11877
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"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-scraper",
|
|
3
|
-
"version": "0.66.
|
|
3
|
+
"version": "0.66.4",
|
|
4
4
|
"description": "MCP server for MCP Scraper web intelligence tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"build": "tsup",
|
|
38
38
|
"build:md": "node scripts/build-md-mirror.mjs",
|
|
39
39
|
"build:mcpb": "npm run build && node scripts/build-mcpb.mjs",
|
|
40
|
+
"build:mcpb:candidate": "npm run build && node scripts/build-mcpb.mjs --candidate",
|
|
40
41
|
"build:rates": "node --import tsx scripts/gen-rates.ts",
|
|
41
42
|
"build:geotargets": "node --import tsx scripts/gen-geotargets.ts",
|
|
42
43
|
"build:blog": "node scripts/run-ts-sandbox-safe.mjs scripts/build-blog.ts",
|
|
@@ -79,7 +80,8 @@
|
|
|
79
80
|
"db:migrate": "node --import tsx scripts/run-core-migration.ts",
|
|
80
81
|
"analytics:migrate": "node --import tsx scripts/run-analytics-migration.ts",
|
|
81
82
|
"analytics:rollups": "node --import tsx scripts/run-analytics-rollups.ts",
|
|
82
|
-
"mcp
|
|
83
|
+
"test:mcp-output-contract": "vitest run --config vitest.mcp-output-contract.config.ts tests/unit/golden-payloads.test.ts tests/unit/mcp-formatter-structured.test.ts tests/unit/mcp-2026-stdio.test.ts",
|
|
84
|
+
"mcp:verify": "npm run build && npm run mcp:manifest && npm run build:rates && npm run mcp:measure-catalog && npm run mcp:lint-tools && npm run test:mcp-output-contract && npm run typecheck && npm test && npm run package:verify-entrypoints",
|
|
83
85
|
"prepack": "npm run mcp:verify",
|
|
84
86
|
"package:verify-entrypoints": "node scripts/verify-package-entrypoints.mjs"
|
|
85
87
|
},
|