mcp-google-gsc 1.1.1 → 1.1.2
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/dist/build-info.json +2 -2
- package/dist/index.js +3 -0
- package/dist/index.js.map +2 -2
- package/dist/resilience.js +1 -2
- package/dist/resilience.js.map +2 -2
- package/dist/updateNotifier.d.ts +7 -0
- package/dist/updateNotifier.js +53 -0
- package/dist/updateNotifier.js.map +7 -0
- package/package.json +2 -1
package/dist/build-info.json
CHANGED
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { resolveOAuthCredentials } from "./credentials.js";
|
|
19
19
|
import { tools } from "./tools.js";
|
|
20
20
|
import { withResilience, safeResponse, logger } from "./resilience.js";
|
|
21
|
+
import { checkForUpdate } from "./updateNotifier.js";
|
|
21
22
|
import v8 from "v8";
|
|
22
23
|
const __cliPkg = JSON.parse(readFileSync(join(dirname(new URL(import.meta.url).pathname), "..", "package.json"), "utf-8"));
|
|
23
24
|
try {
|
|
@@ -39,6 +40,8 @@ const __semverLt = (a, b) => {
|
|
|
39
40
|
if (__semverLt(__cliPkg.version, __minimumSafeVersion)) {
|
|
40
41
|
console.error(`[WARNING] Running deprecated version ${__cliPkg.version}. Minimum safe version is ${__minimumSafeVersion}. Please upgrade.`);
|
|
41
42
|
}
|
|
43
|
+
void checkForUpdate(__cliPkg.name, __cliPkg.version).catch(() => {
|
|
44
|
+
});
|
|
42
45
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
43
46
|
console.error(`${__cliPkg.name} v${__cliPkg.version}
|
|
44
47
|
`);
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { readFileSync, existsSync } from \"fs\";\nimport { join, dirname, resolve, isAbsolute } from \"path\";\nimport { google, searchconsole_v1 } from \"googleapis\";\nimport { GoogleAuth, OAuth2Client } from \"googleapis-common\";\nimport {\n GscAuthError,\n GscRateLimitError,\n GscServiceError,\n classifyError,\n validateCredentials,\n} from \"./errors.js\";\nimport { resolveOAuthCredentials } from \"./credentials.js\";\nimport { EMBEDDED_CLIENT_ID, EMBEDDED_CLIENT_SECRET } from \"./embedded-secrets.js\";\nimport { tools } from \"./tools.js\";\nimport { withResilience, safeResponse, logger } from \"./resilience.js\";\nimport v8 from \"v8\";\n\n// CLI package info\nconst __cliPkg = JSON.parse(readFileSync(join(dirname(new URL(import.meta.url).pathname), \"..\", \"package.json\"), \"utf-8\"));\n\n// Log build fingerprint at startup\ntry {\n const __buildInfoDir = dirname(new URL(import.meta.url).pathname);\n const buildInfo = JSON.parse(readFileSync(join(__buildInfoDir, \"build-info.json\"), \"utf-8\"));\n console.error(`[build] SHA: ${buildInfo.sha} (${buildInfo.builtAt})`);\n} catch {\n console.error(`[build] ${__cliPkg.name}@${__cliPkg.version} (dev mode)`);\n}\n\n// Version safety: warn if running a deprecated or dangerously old version\nconst __minimumSafeVersion = \"1.0.5\"; // minimum version with input sanitization\nconst __semverLt = (a: string, b: string) => { const pa = a.split(\".\").map(Number), pb = b.split(\".\").map(Number); for (let i = 0; i < 3; i++) { if ((pa[i] || 0) < (pb[i] || 0)) return true; if ((pa[i] || 0) > (pb[i] || 0)) return false; } return false; };\nif (__semverLt(__cliPkg.version, __minimumSafeVersion)) {\n console.error(`[WARNING] Running deprecated version ${__cliPkg.version}. Minimum safe version is ${__minimumSafeVersion}. Please upgrade.`);\n}\n\n// CLI flags\nif (process.argv.includes(\"--help\") || process.argv.includes(\"-h\")) {\n console.error(`${__cliPkg.name} v${__cliPkg.version}\\n`);\n console.error(`Usage: ${__cliPkg.name} [options]\\n`);\n console.error(\"MCP server communicating via stdio. Configure in your .mcp.json.\\n\");\n console.error(\"Options:\");\n console.error(\" --help, -h Show this help message\");\n console.error(\" --version, -v Show version number\");\n console.error(`\\nDocumentation: https://github.com/mharnett/mcp-search-console`);\n process.exit(0);\n}\nif (process.argv.includes(\"--version\") || process.argv.includes(\"-v\")) {\n console.error(__cliPkg.version);\n process.exit(0);\n}\n\n// Startup: detect npx vs direct node\nif (process.argv[1]?.includes('.npm/_npx')) {\n console.error(\"[startup] Running via npx -- first run may be slow due to package resolution\");\n}\n\n// Startup: check heap size\nconst heapLimit = v8.getHeapStatistics().heap_size_limit;\nif (heapLimit < 256 * 1024 * 1024) {\n console.error(`[startup] WARNING: Heap limit is ${Math.round(heapLimit / 1024 / 1024)}MB`);\n}\n\n// ============================================\n// ENV VAR TRIMMING\n// ============================================\n\nconst envTrimmed = (key: string): string => (process.env[key] || \"\").trim().replace(/^[\"']|[\"']$/g, \"\");\n\n// ============================================\n// CONFIGURATION\n// ============================================\n\ninterface ClientConfig {\n name: string;\n folder: string;\n site_url: string;\n}\n\ninterface Config {\n credentials_file: string;\n clients: Record<string, ClientConfig>;\n}\n\nfunction loadConfig(): Config {\n // Try config.json (for multi-client setups)\n const configPath = join(dirname(new URL(import.meta.url).pathname), \"..\", \"config.json\");\n if (existsSync(configPath)) {\n const raw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n const rawCf = raw.credentials_file || envTrimmed(\"GOOGLE_APPLICATION_CREDENTIALS\");\n return {\n credentials_file: rawCf && !isAbsolute(rawCf) ? resolve(rawCf) : rawCf,\n clients: raw.clients || {},\n };\n }\n\n // Fall back to env vars for service account\n const rawCredsFile = envTrimmed(\"GOOGLE_APPLICATION_CREDENTIALS\");\n const credsFile = rawCredsFile && !isAbsolute(rawCredsFile) ? resolve(rawCredsFile) : rawCredsFile;\n if (credsFile) {\n return {\n credentials_file: credsFile,\n clients: {},\n };\n }\n\n // Fall back to OAuth credentials (from mcp-gsc-auth or env vars)\n // Return empty credentials_file to signal OAuth mode\n return {\n credentials_file: \"\",\n clients: {},\n };\n}\n\nfunction getClientFromWorkingDir(config: Config, cwd: string): ClientConfig | null {\n for (const [key, client] of Object.entries(config.clients)) {\n if (cwd.startsWith(client.folder) || cwd.includes(key)) {\n return client;\n }\n }\n return null;\n}\n\nfunction getDefaultSiteUrl(config: Config): string | null {\n const clients = Object.values(config.clients);\n return clients.length > 0 ? clients[0].site_url : null;\n}\n\n// ============================================\n// DATE HELPERS\n// ============================================\n\n// Note: resolveDate uses UTC dates via toISOString(). GSC data is in the property's timezone.\n// At 11PM PT, \"today\" resolves to tomorrow in UTC. Users should be aware of this timezone behavior.\nfunction resolveDate(dateStr: string): string {\n const today = new Date();\n if (dateStr === \"today\") {\n return today.toISOString().slice(0, 10);\n }\n const match = dateStr.match(/^(\\d+)daysAgo$/);\n if (match) {\n const days = parseInt(match[1], 10);\n const d = new Date(today);\n d.setDate(d.getDate() - days);\n return d.toISOString().slice(0, 10);\n }\n return dateStr; // assume YYYY-MM-DD\n}\n\n// ============================================\n// DIMENSION FILTER PARSING\n// ============================================\n\ninterface DimensionFilter {\n dimension: string;\n operator: string;\n expression: string;\n}\n\nfunction parseDimensionFilter(filterStr: string): DimensionFilter | null {\n if (!filterStr) return null;\n\n const operators = [\n \"includingRegex\", \"excludingRegex\",\n \"notContains\", \"notEquals\",\n \"contains\", \"equals\",\n ];\n\n for (const op of operators) {\n const parts = filterStr.split(` ${op} `, 2);\n if (parts.length === 2) {\n return {\n dimension: parts[0].trim(),\n operator: op,\n expression: parts[1].trim(),\n };\n }\n }\n\n return null;\n}\n\n// ============================================\n// GOOGLE SEARCH CONSOLE API CLIENT\n// ============================================\n\nclass GscManager {\n private config: Config;\n private service: searchconsole_v1.Searchconsole | null = null;\n private authMode: \"service_account\" | \"oauth\" = \"service_account\";\n\n constructor(config: Config) {\n this.config = config;\n\n if (config.credentials_file) {\n // Service account mode\n const creds = validateCredentials(config.credentials_file);\n if (!creds.valid) {\n const msg = `[STARTUP ERROR] Missing required credentials: ${creds.missing.join(\", \")}. MCP will not function.`;\n console.error(msg);\n throw new GscAuthError(msg);\n }\n this.authMode = \"service_account\";\n } else {\n // OAuth mode -- resolve will throw with helpful message if no credentials found\n resolveOAuthCredentials(); // validates credentials exist\n this.authMode = \"oauth\";\n }\n }\n\n private getService(): searchconsole_v1.Searchconsole {\n if (!this.service) {\n if (this.authMode === \"service_account\") {\n const auth = new google.auth.GoogleAuth({\n keyFile: this.config.credentials_file,\n scopes: [\"https://www.googleapis.com/auth/webmasters.readonly\"],\n });\n this.service = google.searchconsole({ version: \"v1\", auth });\n console.error(`[startup] Service account loaded from: ${this.config.credentials_file}`);\n } else {\n const resolved = resolveOAuthCredentials();\n const oauth2Client = new google.auth.OAuth2(\n resolved.client_id,\n resolved.client_secret,\n );\n oauth2Client.setCredentials({ refresh_token: resolved.refresh_token });\n this.service = google.searchconsole({ version: \"v1\", auth: oauth2Client });\n console.error(`[startup] OAuth credentials loaded (source: ${resolved.source})`);\n }\n }\n return this.service;\n }\n\n async listSites(): Promise<any> {\n const svc = this.getService();\n return withResilience(async () => {\n const resp = await svc.sites.list();\n const sites = (resp.data.siteEntry || []).map((entry) => ({\n site_url: entry.siteUrl || \"\",\n permission_level: entry.permissionLevel || \"\",\n }));\n return { sites, count: sites.length };\n }, \"gsc_list_sites\");\n }\n\n async searchAnalytics(options: {\n startDate: string;\n endDate: string;\n dimensions: string[];\n searchType: string;\n dimensionFilter: string;\n rowLimit: number;\n aggregationType: string;\n siteUrl: string;\n }): Promise<any> {\n const svc = this.getService();\n const siteUrl = options.siteUrl || getDefaultSiteUrl(this.config);\n if (!siteUrl) {\n return { error: \"No site_url provided and none found in config\" };\n }\n\n const rowLimit = Math.min(Math.max(1, options.rowLimit), 25000);\n const startDate = resolveDate(options.startDate);\n const endDate = resolveDate(options.endDate);\n\n // Future date validation (skip relative dates like \"90daysAgo\")\n const today_gsc = new Date().toISOString().slice(0, 10);\n if (startDate && !options.startDate.includes(\"daysAgo\") && !options.startDate.includes(\"yesterday\") && !options.startDate.includes(\"today\") && startDate > today_gsc) {\n return { error: `start_date \"${startDate}\" is in the future. Reports only cover historical data.` };\n }\n\n const requestBody: any = {\n startDate,\n endDate,\n dimensions: options.dimensions,\n type: options.searchType,\n rowLimit,\n aggregationType: options.aggregationType,\n };\n\n const parsed = parseDimensionFilter(options.dimensionFilter);\n if (parsed) {\n requestBody.dimensionFilterGroups = [{\n filters: [{\n dimension: parsed.dimension,\n operator: parsed.operator,\n expression: parsed.expression,\n }],\n }];\n }\n\n return withResilience(async () => {\n const resp = await svc.searchanalytics.query({\n siteUrl,\n requestBody,\n });\n\n const rows = (resp.data.rows || []).map((row) => {\n const r: Record<string, any> = {};\n for (let i = 0; i < options.dimensions.length; i++) {\n if (row.keys && i < row.keys.length) {\n r[options.dimensions[i]] = row.keys[i];\n }\n }\n r.clicks = row.clicks || 0;\n r.impressions = row.impressions || 0;\n r.ctr = Math.round((row.ctr || 0) * 10000) / 10000;\n r.position = Math.round((row.position || 0) * 10) / 10;\n return r;\n });\n\n return {\n rows,\n row_count: rows.length,\n date_range: `${startDate} to ${endDate}`,\n site_url: siteUrl,\n };\n }, \"gsc_search_analytics\");\n }\n\n async inspection(url: string, siteUrl: string): Promise<any> {\n const svc = this.getService();\n const resolvedSiteUrl = siteUrl || getDefaultSiteUrl(this.config);\n if (!resolvedSiteUrl) {\n return { error: \"No site_url provided and none found in config\" };\n }\n\n return withResilience(async () => {\n try {\n const resp = await svc.urlInspection.index.inspect({\n requestBody: {\n inspectionUrl: url,\n siteUrl: resolvedSiteUrl,\n },\n });\n\n const result = resp.data.inspectionResult || {};\n const indexStatus = result.indexStatusResult || {};\n const mobile = result.mobileUsabilityResult || {};\n const rich = result.richResultsResult || {};\n\n return {\n url,\n site_url: resolvedSiteUrl,\n index_status: {\n verdict: indexStatus.verdict || \"UNKNOWN\",\n coverage_state: indexStatus.coverageState || \"\",\n indexing_state: indexStatus.indexingState || \"\",\n last_crawl_time: indexStatus.lastCrawlTime || \"\",\n page_fetch_state: indexStatus.pageFetchState || \"\",\n robots_txt_state: indexStatus.robotsTxtState || \"\",\n crawled_as: indexStatus.crawledAs || \"\",\n referring_urls: indexStatus.referringUrls || [],\n },\n mobile_usability: {\n verdict: mobile.verdict || \"UNKNOWN\",\n issues: (mobile.issues || []).map((i: any) => i.issueType || \"\"),\n },\n rich_results: {\n verdict: rich.verdict || \"UNKNOWN\",\n },\n };\n } catch (err) {\n return { error: String(err), url, site_url: resolvedSiteUrl };\n }\n }, \"gsc_inspection\");\n }\n}\n\n// ============================================\n// MCP SERVER\n// ============================================\n\nconst config = loadConfig();\nconst gscManager = new GscManager(config);\n\nconst server = new Server(\n {\n name: __cliPkg.name,\n version: __cliPkg.version,\n },\n {\n capabilities: {\n tools: {},\n },\n }\n);\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools };\n});\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case \"gsc_get_client_context\": {\n const cwd = args?.working_directory as string;\n const client = getClientFromWorkingDir(config, cwd);\n if (!client) {\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify({\n error: \"No client found for working directory\",\n working_directory: cwd,\n available_clients: Object.entries(config.clients).map(([k, v]) => ({\n key: k,\n name: v.name,\n folder: v.folder,\n })),\n }, null, 2),\n }],\n };\n }\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify({\n client_name: client.name,\n site_url: client.site_url,\n folder: client.folder,\n }, null, 2),\n }],\n };\n }\n\n case \"gsc_list_sites\": {\n const result = await gscManager.listSites();\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify(safeResponse(result, \"listSites\"), null, 2),\n }],\n };\n }\n\n case \"gsc_search_analytics\": {\n const dimensions = ((args?.dimensions as string) || \"query\")\n .split(\",\")\n .map((d: string) => d.trim())\n .filter(Boolean);\n\n const result = await gscManager.searchAnalytics({\n startDate: (args?.start_date as string) || \"90daysAgo\",\n endDate: (args?.end_date as string) || \"today\",\n dimensions,\n searchType: (args?.search_type as string) || \"web\",\n dimensionFilter: (args?.dimension_filter as string) || \"\",\n rowLimit: (args?.row_limit as number) || 100,\n aggregationType: (args?.aggregation_type as string) || \"auto\",\n siteUrl: (args?.site_url as string) || \"\",\n });\n\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify(safeResponse(result, \"searchAnalytics\"), null, 2),\n }],\n };\n }\n\n case \"gsc_inspection\": {\n const result = await gscManager.inspection(\n args?.url as string,\n (args?.site_url as string) || \"\",\n );\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify(result, null, 2),\n }],\n };\n }\n\n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n } catch (rawError: any) {\n const error = classifyError(rawError);\n logger.error({ error_type: error.name, message: error.message }, \"Tool call failed\");\n\n const response: Record<string, unknown> = {\n error: true,\n error_type: error.name,\n message: error.message,\n server: __cliPkg.name,\n };\n\n if (error instanceof GscAuthError) {\n response.action_required = \"Check credentials (service account or OAuth) and Search Console permissions. If using OAuth, re-run: npx mcp-gsc-auth\";\n } else if (error instanceof GscRateLimitError) {\n response.retry_after_ms = error.retryAfterMs;\n response.action_required = `Rate limited. Retry after ${Math.ceil(error.retryAfterMs / 1000)} seconds.`;\n } else if (error instanceof GscServiceError) {\n response.action_required = \"Google Search Console API server error. This is transient - retry in a few minutes.\";\n } else {\n response.details = rawError.stack;\n }\n\n // Size-limit error responses through safeResponse to prevent oversized payloads\n const safeErrorResponse = safeResponse(response, \"error\");\n return {\n isError: true,\n content: [{\n type: \"text\",\n text: JSON.stringify(safeErrorResponse, null, 2),\n }],\n };\n }\n});\n\n// Start server\nasync function main() {\n try {\n await gscManager.listSites();\n console.error(\"[startup] Auth verified: GSC API call succeeded\");\n } catch (err: any) {\n console.error(`[STARTUP WARNING] Auth check FAILED: ${err.message}`);\n console.error(`[STARTUP WARNING] MCP will start but API calls may fail until auth is fixed.`);\n }\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(\"[startup] MCP GSC server running\");\n}\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[shutdown] SIGTERM received, exiting\");\n process.exit(0);\n});\n\nprocess.on(\"SIGINT\", () => {\n console.error(\"[shutdown] SIGINT received, exiting\");\n process.exit(0);\n});\n\nprocess.on(\"SIGPIPE\", () => {\n // Client disconnected -- expected during shutdown\n});\n\nprocess.on(\"unhandledRejection\", (reason) => {\n console.error(\"[error] Unhandled promise rejection:\", reason);\n});\n\nmain().catch(console.error);\n"],
|
|
5
|
-
"mappings": ";AAEA,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc,kBAAkB;AACzC,SAAS,MAAM,SAAS,SAAS,kBAAkB;AACnD,SAAS,cAAgC;AAEzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,+BAA+B;AAExC,SAAS,aAAa;AACtB,SAAS,gBAAgB,cAAc,cAAc;AACrD,OAAO,QAAQ;AAGf,MAAM,WAAW,KAAK,MAAM,aAAa,KAAK,QAAQ,IAAI,IAAI,YAAY,GAAG,EAAE,QAAQ,GAAG,MAAM,cAAc,GAAG,OAAO,CAAC;AAGzH,IAAI;AACF,QAAM,iBAAiB,QAAQ,IAAI,IAAI,YAAY,GAAG,EAAE,QAAQ;AAChE,QAAM,YAAY,KAAK,MAAM,aAAa,KAAK,gBAAgB,iBAAiB,GAAG,OAAO,CAAC;AAC3F,UAAQ,MAAM,gBAAgB,UAAU,GAAG,KAAK,UAAU,OAAO,GAAG;AACtE,QAAQ;AACN,UAAQ,MAAM,WAAW,SAAS,IAAI,IAAI,SAAS,OAAO,aAAa;AACzE;AAGA,MAAM,uBAAuB;AAC7B,MAAM,aAAa,CAAC,GAAW,MAAc;AAAE,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM,GAAG,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAAG,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAAE,SAAK,GAAG,CAAC,KAAK,MAAM,GAAG,CAAC,KAAK,GAAI,QAAO;AAAM,SAAK,GAAG,CAAC,KAAK,MAAM,GAAG,CAAC,KAAK,GAAI,QAAO;AAAA,EAAO;AAAE,SAAO;AAAO;AAC9P,IAAI,WAAW,SAAS,SAAS,oBAAoB,GAAG;AACtD,UAAQ,MAAM,wCAAwC,SAAS,OAAO,6BAA6B,oBAAoB,mBAAmB;AAC5I;AAGA,IAAI,QAAQ,KAAK,SAAS,QAAQ,KAAK,QAAQ,KAAK,SAAS,IAAI,GAAG;AAClE,UAAQ,MAAM,GAAG,SAAS,IAAI,KAAK,SAAS,OAAO;AAAA,CAAI;AACvD,UAAQ,MAAM,UAAU,SAAS,IAAI;AAAA,CAAc;AACnD,UAAQ,MAAM,oEAAoE;AAClF,UAAQ,MAAM,UAAU;AACxB,UAAQ,MAAM,2CAA2C;AACzD,UAAQ,MAAM,wCAAwC;AACtD,UAAQ,MAAM;AAAA,8DAAiE;AAC/E,UAAQ,KAAK,CAAC;AAChB;AACA,IAAI,QAAQ,KAAK,SAAS,WAAW,KAAK,QAAQ,KAAK,SAAS,IAAI,GAAG;AACrE,UAAQ,MAAM,SAAS,OAAO;AAC9B,UAAQ,KAAK,CAAC;AAChB;AAGA,IAAI,QAAQ,KAAK,CAAC,GAAG,SAAS,WAAW,GAAG;AAC1C,UAAQ,MAAM,8EAA8E;AAC9F;AAGA,MAAM,YAAY,GAAG,kBAAkB,EAAE;AACzC,IAAI,YAAY,MAAM,OAAO,MAAM;AACjC,UAAQ,MAAM,oCAAoC,KAAK,MAAM,YAAY,OAAO,IAAI,CAAC,IAAI;AAC3F;AAMA,MAAM,aAAa,CAAC,SAAyB,QAAQ,IAAI,GAAG,KAAK,IAAI,KAAK,EAAE,QAAQ,gBAAgB,EAAE;AAiBtG,SAAS,aAAqB;AAE5B,QAAM,aAAa,KAAK,QAAQ,IAAI,IAAI,YAAY,GAAG,EAAE,QAAQ,GAAG,MAAM,aAAa;AACvF,MAAI,WAAW,UAAU,GAAG;AAC1B,UAAM,MAAM,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AACxD,UAAM,QAAQ,IAAI,oBAAoB,WAAW,gCAAgC;AACjF,WAAO;AAAA,MACL,kBAAkB,SAAS,CAAC,WAAW,KAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,MACjE,SAAS,IAAI,WAAW,CAAC;AAAA,IAC3B;AAAA,EACF;AAGA,QAAM,eAAe,WAAW,gCAAgC;AAChE,QAAM,YAAY,gBAAgB,CAAC,WAAW,YAAY,IAAI,QAAQ,YAAY,IAAI;AACtF,MAAI,WAAW;AACb,WAAO;AAAA,MACL,kBAAkB;AAAA,MAClB,SAAS,CAAC;AAAA,IACZ;AAAA,EACF;AAIA,SAAO;AAAA,IACL,kBAAkB;AAAA,IAClB,SAAS,CAAC;AAAA,EACZ;AACF;AAEA,SAAS,wBAAwBA,SAAgB,KAAkC;AACjF,aAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQA,QAAO,OAAO,GAAG;AAC1D,QAAI,IAAI,WAAW,OAAO,MAAM,KAAK,IAAI,SAAS,GAAG,GAAG;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAAkBA,SAA+B;AACxD,QAAM,UAAU,OAAO,OAAOA,QAAO,OAAO;AAC5C,SAAO,QAAQ,SAAS,IAAI,QAAQ,CAAC,EAAE,WAAW;AACpD;AAQA,SAAS,YAAY,SAAyB;AAC5C,QAAM,QAAQ,oBAAI,KAAK;AACvB,MAAI,YAAY,SAAS;AACvB,WAAO,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EACxC;AACA,QAAM,QAAQ,QAAQ,MAAM,gBAAgB;AAC5C,MAAI,OAAO;AACT,UAAM,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAClC,UAAM,IAAI,IAAI,KAAK,KAAK;AACxB,MAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI;AAC5B,WAAO,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EACpC;AACA,SAAO;AACT;AAYA,SAAS,qBAAqB,WAA2C;AACvE,MAAI,CAAC,UAAW,QAAO;AAEvB,QAAM,YAAY;AAAA,IAChB;AAAA,IAAkB;AAAA,IAClB;AAAA,IAAe;AAAA,IACf;AAAA,IAAY;AAAA,EACd;AAEA,aAAW,MAAM,WAAW;AAC1B,UAAM,QAAQ,UAAU,MAAM,IAAI,EAAE,KAAK,CAAC;AAC1C,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO;AAAA,QACL,WAAW,MAAM,CAAC,EAAE,KAAK;AAAA,QACzB,UAAU;AAAA,QACV,YAAY,MAAM,CAAC,EAAE,KAAK;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMA,MAAM,WAAW;AAAA,EACP;AAAA,EACA,UAAiD;AAAA,EACjD,WAAwC;AAAA,EAEhD,YAAYA,SAAgB;AAC1B,SAAK,SAASA;AAEd,QAAIA,QAAO,kBAAkB;AAE3B,YAAM,QAAQ,oBAAoBA,QAAO,gBAAgB;AACzD,UAAI,CAAC,MAAM,OAAO;AAChB,cAAM,MAAM,iDAAiD,MAAM,QAAQ,KAAK,IAAI,CAAC;AACrF,gBAAQ,MAAM,GAAG;AACjB,cAAM,IAAI,aAAa,GAAG;AAAA,MAC5B;AACA,WAAK,WAAW;AAAA,IAClB,OAAO;AAEL,8BAAwB;AACxB,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEQ,aAA6C;AACnD,QAAI,CAAC,KAAK,SAAS;AACjB,UAAI,KAAK,aAAa,mBAAmB;AACvC,cAAM,OAAO,IAAI,OAAO,KAAK,WAAW;AAAA,UACtC,SAAS,KAAK,OAAO;AAAA,UACrB,QAAQ,CAAC,qDAAqD;AAAA,QAChE,CAAC;AACD,aAAK,UAAU,OAAO,cAAc,EAAE,SAAS,MAAM,KAAK,CAAC;AAC3D,gBAAQ,MAAM,0CAA0C,KAAK,OAAO,gBAAgB,EAAE;AAAA,MACxF,OAAO;AACL,cAAM,WAAW,wBAAwB;AACzC,cAAM,eAAe,IAAI,OAAO,KAAK;AAAA,UACnC,SAAS;AAAA,UACT,SAAS;AAAA,QACX;AACA,qBAAa,eAAe,EAAE,eAAe,SAAS,cAAc,CAAC;AACrE,aAAK,UAAU,OAAO,cAAc,EAAE,SAAS,MAAM,MAAM,aAAa,CAAC;AACzE,gBAAQ,MAAM,+CAA+C,SAAS,MAAM,GAAG;AAAA,MACjF;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,YAA0B;AAC9B,UAAM,MAAM,KAAK,WAAW;AAC5B,WAAO,eAAe,YAAY;AAChC,YAAM,OAAO,MAAM,IAAI,MAAM,KAAK;AAClC,YAAM,SAAS,KAAK,KAAK,aAAa,CAAC,GAAG,IAAI,CAAC,WAAW;AAAA,QACxD,UAAU,MAAM,WAAW;AAAA,QAC3B,kBAAkB,MAAM,mBAAmB;AAAA,MAC7C,EAAE;AACF,aAAO,EAAE,OAAO,OAAO,MAAM,OAAO;AAAA,IACtC,GAAG,gBAAgB;AAAA,EACrB;AAAA,EAEA,MAAM,gBAAgB,SASL;AACf,UAAM,MAAM,KAAK,WAAW;AAC5B,UAAM,UAAU,QAAQ,WAAW,kBAAkB,KAAK,MAAM;AAChE,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,OAAO,gDAAgD;AAAA,IAClE;AAEA,UAAM,WAAW,KAAK,IAAI,KAAK,IAAI,GAAG,QAAQ,QAAQ,GAAG,IAAK;AAC9D,UAAM,YAAY,YAAY,QAAQ,SAAS;AAC/C,UAAM,UAAU,YAAY,QAAQ,OAAO;AAG3C,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACtD,QAAI,aAAa,CAAC,QAAQ,UAAU,SAAS,SAAS,KAAK,CAAC,QAAQ,UAAU,SAAS,WAAW,KAAK,CAAC,QAAQ,UAAU,SAAS,OAAO,KAAK,YAAY,WAAW;AACpK,aAAO,EAAE,OAAO,eAAe,SAAS,0DAA0D;AAAA,IACpG;AAEA,UAAM,cAAmB;AAAA,MACvB;AAAA,MACA;AAAA,MACA,YAAY,QAAQ;AAAA,MACpB,MAAM,QAAQ;AAAA,MACd;AAAA,MACA,iBAAiB,QAAQ;AAAA,IAC3B;AAEA,UAAM,SAAS,qBAAqB,QAAQ,eAAe;AAC3D,QAAI,QAAQ;AACV,kBAAY,wBAAwB,CAAC;AAAA,QACnC,SAAS,CAAC;AAAA,UACR,WAAW,OAAO;AAAA,UAClB,UAAU,OAAO;AAAA,UACjB,YAAY,OAAO;AAAA,QACrB,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,WAAO,eAAe,YAAY;AAChC,YAAM,OAAO,MAAM,IAAI,gBAAgB,MAAM;AAAA,QAC3C;AAAA,QACA;AAAA,MACF,CAAC;AAED,YAAM,QAAQ,KAAK,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ;AAC/C,cAAM,IAAyB,CAAC;AAChC,iBAAS,IAAI,GAAG,IAAI,QAAQ,WAAW,QAAQ,KAAK;AAClD,cAAI,IAAI,QAAQ,IAAI,IAAI,KAAK,QAAQ;AACnC,cAAE,QAAQ,WAAW,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC;AAAA,UACvC;AAAA,QACF;AACA,UAAE,SAAS,IAAI,UAAU;AACzB,UAAE,cAAc,IAAI,eAAe;AACnC,UAAE,MAAM,KAAK,OAAO,IAAI,OAAO,KAAK,GAAK,IAAI;AAC7C,UAAE,WAAW,KAAK,OAAO,IAAI,YAAY,KAAK,EAAE,IAAI;AACpD,eAAO;AAAA,MACT,CAAC;AAED,aAAO;AAAA,QACL;AAAA,QACA,WAAW,KAAK;AAAA,QAChB,YAAY,GAAG,SAAS,OAAO,OAAO;AAAA,QACtC,UAAU;AAAA,MACZ;AAAA,IACF,GAAG,sBAAsB;AAAA,EAC3B;AAAA,EAEA,MAAM,WAAW,KAAa,SAA+B;AAC3D,UAAM,MAAM,KAAK,WAAW;AAC5B,UAAM,kBAAkB,WAAW,kBAAkB,KAAK,MAAM;AAChE,QAAI,CAAC,iBAAiB;AACpB,aAAO,EAAE,OAAO,gDAAgD;AAAA,IAClE;AAEA,WAAO,eAAe,YAAY;AAChC,UAAI;AACF,cAAM,OAAO,MAAM,IAAI,cAAc,MAAM,QAAQ;AAAA,UACjD,aAAa;AAAA,YACX,eAAe;AAAA,YACf,SAAS;AAAA,UACX;AAAA,QACF,CAAC;AAED,cAAM,SAAS,KAAK,KAAK,oBAAoB,CAAC;AAC9C,cAAM,cAAc,OAAO,qBAAqB,CAAC;AACjD,cAAM,SAAS,OAAO,yBAAyB,CAAC;AAChD,cAAM,OAAO,OAAO,qBAAqB,CAAC;AAE1C,eAAO;AAAA,UACL;AAAA,UACA,UAAU;AAAA,UACV,cAAc;AAAA,YACZ,SAAS,YAAY,WAAW;AAAA,YAChC,gBAAgB,YAAY,iBAAiB;AAAA,YAC7C,gBAAgB,YAAY,iBAAiB;AAAA,YAC7C,iBAAiB,YAAY,iBAAiB;AAAA,YAC9C,kBAAkB,YAAY,kBAAkB;AAAA,YAChD,kBAAkB,YAAY,kBAAkB;AAAA,YAChD,YAAY,YAAY,aAAa;AAAA,YACrC,gBAAgB,YAAY,iBAAiB,CAAC;AAAA,UAChD;AAAA,UACA,kBAAkB;AAAA,YAChB,SAAS,OAAO,WAAW;AAAA,YAC3B,SAAS,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC,MAAW,EAAE,aAAa,EAAE;AAAA,UACjE;AAAA,UACA,cAAc;AAAA,YACZ,SAAS,KAAK,WAAW;AAAA,UAC3B;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,eAAO,EAAE,OAAO,OAAO,GAAG,GAAG,KAAK,UAAU,gBAAgB;AAAA,MAC9D;AAAA,IACF,GAAG,gBAAgB;AAAA,EACrB;AACF;AAMA,MAAM,SAAS,WAAW;AAC1B,MAAM,aAAa,IAAI,WAAW,MAAM;AAExC,MAAM,SAAS,IAAI;AAAA,EACjB;AAAA,IACE,MAAM,SAAS;AAAA,IACf,SAAS,SAAS;AAAA,EACpB;AAAA,EACA;AAAA,IACE,cAAc;AAAA,MACZ,OAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;AAEA,OAAO,kBAAkB,wBAAwB,YAAY;AAC3D,SAAO,EAAE,MAAM;AACjB,CAAC;AAED,OAAO,kBAAkB,uBAAuB,OAAO,YAAY;AACjE,QAAM,EAAE,MAAM,WAAW,KAAK,IAAI,QAAQ;AAE1C,MAAI;AACF,YAAQ,MAAM;AAAA,MACZ,KAAK,0BAA0B;AAC7B,cAAM,MAAM,MAAM;AAClB,cAAM,SAAS,wBAAwB,QAAQ,GAAG;AAClD,YAAI,CAAC,QAAQ;AACX,iBAAO;AAAA,YACL,SAAS,CAAC;AAAA,cACR,MAAM;AAAA,cACN,MAAM,KAAK,UAAU;AAAA,gBACnB,OAAO;AAAA,gBACP,mBAAmB;AAAA,gBACnB,mBAAmB,OAAO,QAAQ,OAAO,OAAO,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO;AAAA,kBACjE,KAAK;AAAA,kBACL,MAAM,EAAE;AAAA,kBACR,QAAQ,EAAE;AAAA,gBACZ,EAAE;AAAA,cACJ,GAAG,MAAM,CAAC;AAAA,YACZ,CAAC;AAAA,UACH;AAAA,QACF;AACA,eAAO;AAAA,UACL,SAAS,CAAC;AAAA,YACR,MAAM;AAAA,YACN,MAAM,KAAK,UAAU;AAAA,cACnB,aAAa,OAAO;AAAA,cACpB,UAAU,OAAO;AAAA,cACjB,QAAQ,OAAO;AAAA,YACjB,GAAG,MAAM,CAAC;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MAEA,KAAK,kBAAkB;AACrB,cAAM,SAAS,MAAM,WAAW,UAAU;AAC1C,eAAO;AAAA,UACL,SAAS,CAAC;AAAA,YACR,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,aAAa,QAAQ,WAAW,GAAG,MAAM,CAAC;AAAA,UACjE,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MAEA,KAAK,wBAAwB;AAC3B,cAAM,cAAe,MAAM,cAAyB,SACjD,MAAM,GAAG,EACT,IAAI,CAAC,MAAc,EAAE,KAAK,CAAC,EAC3B,OAAO,OAAO;AAEjB,cAAM,SAAS,MAAM,WAAW,gBAAgB;AAAA,UAC9C,WAAY,MAAM,cAAyB;AAAA,UAC3C,SAAU,MAAM,YAAuB;AAAA,UACvC;AAAA,UACA,YAAa,MAAM,eAA0B;AAAA,UAC7C,iBAAkB,MAAM,oBAA+B;AAAA,UACvD,UAAW,MAAM,aAAwB;AAAA,UACzC,iBAAkB,MAAM,oBAA+B;AAAA,UACvD,SAAU,MAAM,YAAuB;AAAA,QACzC,CAAC;AAED,eAAO;AAAA,UACL,SAAS,CAAC;AAAA,YACR,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,aAAa,QAAQ,iBAAiB,GAAG,MAAM,CAAC;AAAA,UACvE,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MAEA,KAAK,kBAAkB;AACrB,cAAM,SAAS,MAAM,WAAW;AAAA,UAC9B,MAAM;AAAA,UACL,MAAM,YAAuB;AAAA,QAChC;AACA,eAAO;AAAA,UACL,SAAS,CAAC;AAAA,YACR,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC;AAAA,UACtC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MAEA;AACE,cAAM,IAAI,MAAM,iBAAiB,IAAI,EAAE;AAAA,IAC3C;AAAA,EACF,SAAS,UAAe;AACtB,UAAM,QAAQ,cAAc,QAAQ;AACpC,WAAO,MAAM,EAAE,YAAY,MAAM,MAAM,SAAS,MAAM,QAAQ,GAAG,kBAAkB;AAEnF,UAAM,WAAoC;AAAA,MACxC,OAAO;AAAA,MACP,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM;AAAA,MACf,QAAQ,SAAS;AAAA,IACnB;AAEA,QAAI,iBAAiB,cAAc;AACjC,eAAS,kBAAkB;AAAA,IAC7B,WAAW,iBAAiB,mBAAmB;AAC7C,eAAS,iBAAiB,MAAM;AAChC,eAAS,kBAAkB,6BAA6B,KAAK,KAAK,MAAM,eAAe,GAAI,CAAC;AAAA,IAC9F,WAAW,iBAAiB,iBAAiB;AAC3C,eAAS,kBAAkB;AAAA,IAC7B,OAAO;AACL,eAAS,UAAU,SAAS;AAAA,IAC9B;AAGA,UAAM,oBAAoB,aAAa,UAAU,OAAO;AACxD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS,CAAC;AAAA,QACR,MAAM;AAAA,QACN,MAAM,KAAK,UAAU,mBAAmB,MAAM,CAAC;AAAA,MACjD,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;AAGD,eAAe,OAAO;AACpB,MAAI;AACF,UAAM,WAAW,UAAU;AAC3B,YAAQ,MAAM,iDAAiD;AAAA,EACjE,SAAS,KAAU;AACjB,YAAQ,MAAM,wCAAwC,IAAI,OAAO,EAAE;AACnE,YAAQ,MAAM,8EAA8E;AAAA,EAC9F;AAEA,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC9B,UAAQ,MAAM,kCAAkC;AAClD;AAEA,QAAQ,GAAG,WAAW,MAAM;AAC1B,UAAQ,MAAM,sCAAsC;AACpD,UAAQ,KAAK,CAAC;AAChB,CAAC;AAED,QAAQ,GAAG,UAAU,MAAM;AACzB,UAAQ,MAAM,qCAAqC;AACnD,UAAQ,KAAK,CAAC;AAChB,CAAC;AAED,QAAQ,GAAG,WAAW,MAAM;AAE5B,CAAC;AAED,QAAQ,GAAG,sBAAsB,CAAC,WAAW;AAC3C,UAAQ,MAAM,wCAAwC,MAAM;AAC9D,CAAC;AAED,KAAK,EAAE,MAAM,QAAQ,KAAK;",
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { readFileSync, existsSync } from \"fs\";\nimport { join, dirname, resolve, isAbsolute } from \"path\";\nimport { google, searchconsole_v1 } from \"googleapis\";\nimport { GoogleAuth, OAuth2Client } from \"googleapis-common\";\nimport {\n GscAuthError,\n GscRateLimitError,\n GscServiceError,\n classifyError,\n validateCredentials,\n} from \"./errors.js\";\nimport { resolveOAuthCredentials } from \"./credentials.js\";\nimport { EMBEDDED_CLIENT_ID, EMBEDDED_CLIENT_SECRET } from \"./embedded-secrets.js\";\nimport { tools } from \"./tools.js\";\nimport { withResilience, safeResponse, logger } from \"./resilience.js\";\nimport { checkForUpdate } from \"./updateNotifier.js\";\nimport v8 from \"v8\";\n\n// CLI package info\nconst __cliPkg = JSON.parse(readFileSync(join(dirname(new URL(import.meta.url).pathname), \"..\", \"package.json\"), \"utf-8\"));\n\n// Log build fingerprint at startup\ntry {\n const __buildInfoDir = dirname(new URL(import.meta.url).pathname);\n const buildInfo = JSON.parse(readFileSync(join(__buildInfoDir, \"build-info.json\"), \"utf-8\"));\n console.error(`[build] SHA: ${buildInfo.sha} (${buildInfo.builtAt})`);\n} catch {\n console.error(`[build] ${__cliPkg.name}@${__cliPkg.version} (dev mode)`);\n}\n\n// Version safety: warn if running a deprecated or dangerously old version\nconst __minimumSafeVersion = \"1.0.5\"; // minimum version with input sanitization\nconst __semverLt = (a: string, b: string) => { const pa = a.split(\".\").map(Number), pb = b.split(\".\").map(Number); for (let i = 0; i < 3; i++) { if ((pa[i] || 0) < (pb[i] || 0)) return true; if ((pa[i] || 0) > (pb[i] || 0)) return false; } return false; };\nif (__semverLt(__cliPkg.version, __minimumSafeVersion)) {\n console.error(`[WARNING] Running deprecated version ${__cliPkg.version}. Minimum safe version is ${__minimumSafeVersion}. Please upgrade.`);\n}\n\n// Fire-and-forget npm outdated check. Non-blocking; any error is swallowed.\nvoid checkForUpdate(__cliPkg.name, __cliPkg.version).catch(() => {});\n\n// CLI flags\nif (process.argv.includes(\"--help\") || process.argv.includes(\"-h\")) {\n console.error(`${__cliPkg.name} v${__cliPkg.version}\\n`);\n console.error(`Usage: ${__cliPkg.name} [options]\\n`);\n console.error(\"MCP server communicating via stdio. Configure in your .mcp.json.\\n\");\n console.error(\"Options:\");\n console.error(\" --help, -h Show this help message\");\n console.error(\" --version, -v Show version number\");\n console.error(`\\nDocumentation: https://github.com/mharnett/mcp-search-console`);\n process.exit(0);\n}\nif (process.argv.includes(\"--version\") || process.argv.includes(\"-v\")) {\n console.error(__cliPkg.version);\n process.exit(0);\n}\n\n// Startup: detect npx vs direct node\nif (process.argv[1]?.includes('.npm/_npx')) {\n console.error(\"[startup] Running via npx -- first run may be slow due to package resolution\");\n}\n\n// Startup: check heap size\nconst heapLimit = v8.getHeapStatistics().heap_size_limit;\nif (heapLimit < 256 * 1024 * 1024) {\n console.error(`[startup] WARNING: Heap limit is ${Math.round(heapLimit / 1024 / 1024)}MB`);\n}\n\n// ============================================\n// ENV VAR TRIMMING\n// ============================================\n\nconst envTrimmed = (key: string): string => (process.env[key] || \"\").trim().replace(/^[\"']|[\"']$/g, \"\");\n\n// ============================================\n// CONFIGURATION\n// ============================================\n\ninterface ClientConfig {\n name: string;\n folder: string;\n site_url: string;\n}\n\ninterface Config {\n credentials_file: string;\n clients: Record<string, ClientConfig>;\n}\n\nfunction loadConfig(): Config {\n // Try config.json (for multi-client setups)\n const configPath = join(dirname(new URL(import.meta.url).pathname), \"..\", \"config.json\");\n if (existsSync(configPath)) {\n const raw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n const rawCf = raw.credentials_file || envTrimmed(\"GOOGLE_APPLICATION_CREDENTIALS\");\n return {\n credentials_file: rawCf && !isAbsolute(rawCf) ? resolve(rawCf) : rawCf,\n clients: raw.clients || {},\n };\n }\n\n // Fall back to env vars for service account\n const rawCredsFile = envTrimmed(\"GOOGLE_APPLICATION_CREDENTIALS\");\n const credsFile = rawCredsFile && !isAbsolute(rawCredsFile) ? resolve(rawCredsFile) : rawCredsFile;\n if (credsFile) {\n return {\n credentials_file: credsFile,\n clients: {},\n };\n }\n\n // Fall back to OAuth credentials (from mcp-gsc-auth or env vars)\n // Return empty credentials_file to signal OAuth mode\n return {\n credentials_file: \"\",\n clients: {},\n };\n}\n\nfunction getClientFromWorkingDir(config: Config, cwd: string): ClientConfig | null {\n for (const [key, client] of Object.entries(config.clients)) {\n if (cwd.startsWith(client.folder) || cwd.includes(key)) {\n return client;\n }\n }\n return null;\n}\n\nfunction getDefaultSiteUrl(config: Config): string | null {\n const clients = Object.values(config.clients);\n return clients.length > 0 ? clients[0].site_url : null;\n}\n\n// ============================================\n// DATE HELPERS\n// ============================================\n\n// Note: resolveDate uses UTC dates via toISOString(). GSC data is in the property's timezone.\n// At 11PM PT, \"today\" resolves to tomorrow in UTC. Users should be aware of this timezone behavior.\nfunction resolveDate(dateStr: string): string {\n const today = new Date();\n if (dateStr === \"today\") {\n return today.toISOString().slice(0, 10);\n }\n const match = dateStr.match(/^(\\d+)daysAgo$/);\n if (match) {\n const days = parseInt(match[1], 10);\n const d = new Date(today);\n d.setDate(d.getDate() - days);\n return d.toISOString().slice(0, 10);\n }\n return dateStr; // assume YYYY-MM-DD\n}\n\n// ============================================\n// DIMENSION FILTER PARSING\n// ============================================\n\ninterface DimensionFilter {\n dimension: string;\n operator: string;\n expression: string;\n}\n\nfunction parseDimensionFilter(filterStr: string): DimensionFilter | null {\n if (!filterStr) return null;\n\n const operators = [\n \"includingRegex\", \"excludingRegex\",\n \"notContains\", \"notEquals\",\n \"contains\", \"equals\",\n ];\n\n for (const op of operators) {\n const parts = filterStr.split(` ${op} `, 2);\n if (parts.length === 2) {\n return {\n dimension: parts[0].trim(),\n operator: op,\n expression: parts[1].trim(),\n };\n }\n }\n\n return null;\n}\n\n// ============================================\n// GOOGLE SEARCH CONSOLE API CLIENT\n// ============================================\n\nclass GscManager {\n private config: Config;\n private service: searchconsole_v1.Searchconsole | null = null;\n private authMode: \"service_account\" | \"oauth\" = \"service_account\";\n\n constructor(config: Config) {\n this.config = config;\n\n if (config.credentials_file) {\n // Service account mode\n const creds = validateCredentials(config.credentials_file);\n if (!creds.valid) {\n const msg = `[STARTUP ERROR] Missing required credentials: ${creds.missing.join(\", \")}. MCP will not function.`;\n console.error(msg);\n throw new GscAuthError(msg);\n }\n this.authMode = \"service_account\";\n } else {\n // OAuth mode -- resolve will throw with helpful message if no credentials found\n resolveOAuthCredentials(); // validates credentials exist\n this.authMode = \"oauth\";\n }\n }\n\n private getService(): searchconsole_v1.Searchconsole {\n if (!this.service) {\n if (this.authMode === \"service_account\") {\n const auth = new google.auth.GoogleAuth({\n keyFile: this.config.credentials_file,\n scopes: [\"https://www.googleapis.com/auth/webmasters.readonly\"],\n });\n this.service = google.searchconsole({ version: \"v1\", auth });\n console.error(`[startup] Service account loaded from: ${this.config.credentials_file}`);\n } else {\n const resolved = resolveOAuthCredentials();\n const oauth2Client = new google.auth.OAuth2(\n resolved.client_id,\n resolved.client_secret,\n );\n oauth2Client.setCredentials({ refresh_token: resolved.refresh_token });\n this.service = google.searchconsole({ version: \"v1\", auth: oauth2Client });\n console.error(`[startup] OAuth credentials loaded (source: ${resolved.source})`);\n }\n }\n return this.service;\n }\n\n async listSites(): Promise<any> {\n const svc = this.getService();\n return withResilience(async () => {\n const resp = await svc.sites.list();\n const sites = (resp.data.siteEntry || []).map((entry) => ({\n site_url: entry.siteUrl || \"\",\n permission_level: entry.permissionLevel || \"\",\n }));\n return { sites, count: sites.length };\n }, \"gsc_list_sites\");\n }\n\n async searchAnalytics(options: {\n startDate: string;\n endDate: string;\n dimensions: string[];\n searchType: string;\n dimensionFilter: string;\n rowLimit: number;\n aggregationType: string;\n siteUrl: string;\n }): Promise<any> {\n const svc = this.getService();\n const siteUrl = options.siteUrl || getDefaultSiteUrl(this.config);\n if (!siteUrl) {\n return { error: \"No site_url provided and none found in config\" };\n }\n\n const rowLimit = Math.min(Math.max(1, options.rowLimit), 25000);\n const startDate = resolveDate(options.startDate);\n const endDate = resolveDate(options.endDate);\n\n // Future date validation (skip relative dates like \"90daysAgo\")\n const today_gsc = new Date().toISOString().slice(0, 10);\n if (startDate && !options.startDate.includes(\"daysAgo\") && !options.startDate.includes(\"yesterday\") && !options.startDate.includes(\"today\") && startDate > today_gsc) {\n return { error: `start_date \"${startDate}\" is in the future. Reports only cover historical data.` };\n }\n\n const requestBody: any = {\n startDate,\n endDate,\n dimensions: options.dimensions,\n type: options.searchType,\n rowLimit,\n aggregationType: options.aggregationType,\n };\n\n const parsed = parseDimensionFilter(options.dimensionFilter);\n if (parsed) {\n requestBody.dimensionFilterGroups = [{\n filters: [{\n dimension: parsed.dimension,\n operator: parsed.operator,\n expression: parsed.expression,\n }],\n }];\n }\n\n return withResilience(async () => {\n const resp = await svc.searchanalytics.query({\n siteUrl,\n requestBody,\n });\n\n const rows = (resp.data.rows || []).map((row) => {\n const r: Record<string, any> = {};\n for (let i = 0; i < options.dimensions.length; i++) {\n if (row.keys && i < row.keys.length) {\n r[options.dimensions[i]] = row.keys[i];\n }\n }\n r.clicks = row.clicks || 0;\n r.impressions = row.impressions || 0;\n r.ctr = Math.round((row.ctr || 0) * 10000) / 10000;\n r.position = Math.round((row.position || 0) * 10) / 10;\n return r;\n });\n\n return {\n rows,\n row_count: rows.length,\n date_range: `${startDate} to ${endDate}`,\n site_url: siteUrl,\n };\n }, \"gsc_search_analytics\");\n }\n\n async inspection(url: string, siteUrl: string): Promise<any> {\n const svc = this.getService();\n const resolvedSiteUrl = siteUrl || getDefaultSiteUrl(this.config);\n if (!resolvedSiteUrl) {\n return { error: \"No site_url provided and none found in config\" };\n }\n\n return withResilience(async () => {\n try {\n const resp = await svc.urlInspection.index.inspect({\n requestBody: {\n inspectionUrl: url,\n siteUrl: resolvedSiteUrl,\n },\n });\n\n const result = resp.data.inspectionResult || {};\n const indexStatus = result.indexStatusResult || {};\n const mobile = result.mobileUsabilityResult || {};\n const rich = result.richResultsResult || {};\n\n return {\n url,\n site_url: resolvedSiteUrl,\n index_status: {\n verdict: indexStatus.verdict || \"UNKNOWN\",\n coverage_state: indexStatus.coverageState || \"\",\n indexing_state: indexStatus.indexingState || \"\",\n last_crawl_time: indexStatus.lastCrawlTime || \"\",\n page_fetch_state: indexStatus.pageFetchState || \"\",\n robots_txt_state: indexStatus.robotsTxtState || \"\",\n crawled_as: indexStatus.crawledAs || \"\",\n referring_urls: indexStatus.referringUrls || [],\n },\n mobile_usability: {\n verdict: mobile.verdict || \"UNKNOWN\",\n issues: (mobile.issues || []).map((i: any) => i.issueType || \"\"),\n },\n rich_results: {\n verdict: rich.verdict || \"UNKNOWN\",\n },\n };\n } catch (err) {\n return { error: String(err), url, site_url: resolvedSiteUrl };\n }\n }, \"gsc_inspection\");\n }\n}\n\n// ============================================\n// MCP SERVER\n// ============================================\n\nconst config = loadConfig();\nconst gscManager = new GscManager(config);\n\nconst server = new Server(\n {\n name: __cliPkg.name,\n version: __cliPkg.version,\n },\n {\n capabilities: {\n tools: {},\n },\n }\n);\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools };\n});\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case \"gsc_get_client_context\": {\n const cwd = args?.working_directory as string;\n const client = getClientFromWorkingDir(config, cwd);\n if (!client) {\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify({\n error: \"No client found for working directory\",\n working_directory: cwd,\n available_clients: Object.entries(config.clients).map(([k, v]) => ({\n key: k,\n name: v.name,\n folder: v.folder,\n })),\n }, null, 2),\n }],\n };\n }\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify({\n client_name: client.name,\n site_url: client.site_url,\n folder: client.folder,\n }, null, 2),\n }],\n };\n }\n\n case \"gsc_list_sites\": {\n const result = await gscManager.listSites();\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify(safeResponse(result, \"listSites\"), null, 2),\n }],\n };\n }\n\n case \"gsc_search_analytics\": {\n const dimensions = ((args?.dimensions as string) || \"query\")\n .split(\",\")\n .map((d: string) => d.trim())\n .filter(Boolean);\n\n const result = await gscManager.searchAnalytics({\n startDate: (args?.start_date as string) || \"90daysAgo\",\n endDate: (args?.end_date as string) || \"today\",\n dimensions,\n searchType: (args?.search_type as string) || \"web\",\n dimensionFilter: (args?.dimension_filter as string) || \"\",\n rowLimit: (args?.row_limit as number) || 100,\n aggregationType: (args?.aggregation_type as string) || \"auto\",\n siteUrl: (args?.site_url as string) || \"\",\n });\n\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify(safeResponse(result, \"searchAnalytics\"), null, 2),\n }],\n };\n }\n\n case \"gsc_inspection\": {\n const result = await gscManager.inspection(\n args?.url as string,\n (args?.site_url as string) || \"\",\n );\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify(result, null, 2),\n }],\n };\n }\n\n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n } catch (rawError: any) {\n const error = classifyError(rawError);\n logger.error({ error_type: error.name, message: error.message }, \"Tool call failed\");\n\n const response: Record<string, unknown> = {\n error: true,\n error_type: error.name,\n message: error.message,\n server: __cliPkg.name,\n };\n\n if (error instanceof GscAuthError) {\n response.action_required = \"Check credentials (service account or OAuth) and Search Console permissions. If using OAuth, re-run: npx mcp-gsc-auth\";\n } else if (error instanceof GscRateLimitError) {\n response.retry_after_ms = error.retryAfterMs;\n response.action_required = `Rate limited. Retry after ${Math.ceil(error.retryAfterMs / 1000)} seconds.`;\n } else if (error instanceof GscServiceError) {\n response.action_required = \"Google Search Console API server error. This is transient - retry in a few minutes.\";\n } else {\n response.details = rawError.stack;\n }\n\n // Size-limit error responses through safeResponse to prevent oversized payloads\n const safeErrorResponse = safeResponse(response, \"error\");\n return {\n isError: true,\n content: [{\n type: \"text\",\n text: JSON.stringify(safeErrorResponse, null, 2),\n }],\n };\n }\n});\n\n// Start server\nasync function main() {\n try {\n await gscManager.listSites();\n console.error(\"[startup] Auth verified: GSC API call succeeded\");\n } catch (err: any) {\n console.error(`[STARTUP WARNING] Auth check FAILED: ${err.message}`);\n console.error(`[STARTUP WARNING] MCP will start but API calls may fail until auth is fixed.`);\n }\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(\"[startup] MCP GSC server running\");\n}\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[shutdown] SIGTERM received, exiting\");\n process.exit(0);\n});\n\nprocess.on(\"SIGINT\", () => {\n console.error(\"[shutdown] SIGINT received, exiting\");\n process.exit(0);\n});\n\nprocess.on(\"SIGPIPE\", () => {\n // Client disconnected -- expected during shutdown\n});\n\nprocess.on(\"unhandledRejection\", (reason) => {\n console.error(\"[error] Unhandled promise rejection:\", reason);\n});\n\nmain().catch(console.error);\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["config"]
|
|
7
7
|
}
|
package/dist/resilience.js
CHANGED
|
@@ -26,8 +26,7 @@ const logger = pino(
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
|
-
|
|
30
|
-
process.env.NODE_ENV === "test" ? pino.destination(2) : void 0
|
|
29
|
+
pino.destination(2)
|
|
31
30
|
);
|
|
32
31
|
const MAX_RESPONSE_SIZE = 2e5;
|
|
33
32
|
function safeResponse(data, context) {
|
package/dist/resilience.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/resilience.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n retry,\n circuitBreaker,\n wrap,\n handleWhen,\n timeout,\n TimeoutStrategy,\n ExponentialBackoff,\n ConsecutiveBreaker,\n} from \"cockatiel\";\nimport pino from \"pino\";\n\n// ============================================\n// LOGGER\n// ============================================\n\nexport const logger = pino(\n {\n level: process.env.LOG_LEVEL || \"info\",\n redact: [\"access_token\", \"refresh_token\", \"client_secret\", \"*.access_token\", \"*.refresh_token\", \"*.client_secret\"],\n ...(process.env.NODE_ENV !== \"test\" && process.stderr.isTTY && {\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n singleLine: true,\n translateTime: \"SYS:standard\",\n destination: 2, // stderr -- stdout is reserved for MCP JSON-RPC\n },\n },\n }),\n },\n
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,UAAU;AAMV,MAAM,SAAS;AAAA,EACpB;AAAA,IACE,OAAO,QAAQ,IAAI,aAAa;AAAA,IAChC,QAAQ,CAAC,gBAAgB,iBAAiB,iBAAiB,kBAAkB,mBAAmB,iBAAiB;AAAA,IACjH,GAAI,QAAQ,IAAI,aAAa,UAAU,QAAQ,OAAO,SAAS;AAAA,MAC7D,WAAW;AAAA,QACT,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,eAAe;AAAA,UACf,aAAa;AAAA;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA
|
|
4
|
+
"sourcesContent": ["import {\n retry,\n circuitBreaker,\n wrap,\n handleWhen,\n timeout,\n TimeoutStrategy,\n ExponentialBackoff,\n ConsecutiveBreaker,\n} from \"cockatiel\";\nimport pino from \"pino\";\n\n// ============================================\n// LOGGER\n// ============================================\n\nexport const logger = pino(\n {\n level: process.env.LOG_LEVEL || \"info\",\n redact: [\"access_token\", \"refresh_token\", \"client_secret\", \"*.access_token\", \"*.refresh_token\", \"*.client_secret\"],\n ...(process.env.NODE_ENV !== \"test\" && process.stderr.isTTY && {\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n singleLine: true,\n translateTime: \"SYS:standard\",\n destination: 2, // stderr -- stdout is reserved for MCP JSON-RPC\n },\n },\n }),\n },\n pino.destination(2),\n);\n\n// ============================================\n// SAFE RESPONSE (Response Size Limiting)\n// ============================================\n\nconst MAX_RESPONSE_SIZE = 200_000; // 200KB\n\nexport function safeResponse<T>(data: T, context: string): T {\n let current = data;\n for (let pass = 0; pass < 10; pass++) {\n const jsonStr = JSON.stringify(current);\n const sizeBytes = Buffer.byteLength(jsonStr, \"utf-8\");\n if (sizeBytes <= MAX_RESPONSE_SIZE) return current;\n\n // Deep clone on first truncation pass to avoid mutating the original object\n if (pass === 0 && typeof current === \"object\" && current !== null) {\n current = JSON.parse(JSON.stringify(current)) as T;\n }\n\n logger.warn({ sizeBytes, maxSize: MAX_RESPONSE_SIZE, context, pass }, \"Response exceeds size limit, truncating\");\n\n if (Array.isArray(current)) {\n current = (current as any[]).slice(0, Math.max(1, Math.floor((current as any[]).length * 0.5))) as T;\n continue;\n }\n\n if (typeof current === \"object\" && current !== null) {\n const obj = current as Record<string, any>;\n let truncated = false;\n for (const key of [\"items\", \"results\", \"data\", \"rows\", \"tags\", \"triggers\", \"variables\"]) {\n if (Array.isArray(obj[key]) && obj[key].length > 1) {\n obj[key] = obj[key].slice(0, Math.max(1, Math.floor(obj[key].length * 0.5)));\n if (\"count\" in obj) obj.count = obj[key].length;\n if (\"row_count\" in obj) obj.row_count = obj[key].length;\n obj.truncated = true;\n truncated = true;\n break;\n }\n }\n if (truncated) continue;\n }\n\n break;\n }\n return current;\n}\n\n// ============================================\n// RETRY + CIRCUIT BREAKER + TIMEOUT\n// ============================================\n\nconst backoff = new ExponentialBackoff({\n initialDelay: 100,\n maxDelay: 5_000,\n});\n\nconst isTransient = handleWhen((err) => {\n const msg = (err?.message || \"\").toLowerCase();\n const code = (err as any)?.code || (err as any)?.status;\n if (code === 401 || code === 403 || code === 7 || code === 16) return false;\n if (msg.includes(\"unauthenticated\") || msg.includes(\"permission_denied\") || msg.includes(\"invalid_grant\")) return false;\n if (code === 429 || msg.includes(\"rate\")) return true;\n if (code >= 400 && code < 500) return false;\n return true;\n});\n\nconst retryPolicy = retry(isTransient, {\n maxAttempts: 3,\n backoff,\n});\n\nconst circuitBreakerPolicy = circuitBreaker(isTransient, {\n halfOpenAfter: 60_000,\n breaker: new ConsecutiveBreaker(5),\n});\n\nconst timeoutPolicy = timeout(30_000, TimeoutStrategy.Cooperative);\n\nconst policy = wrap(timeoutPolicy, circuitBreakerPolicy, retryPolicy);\n\n// ============================================\n// WRAPPED API CALL WITH LOGGING\n// ============================================\n\nexport async function withResilience<T>(\n fn: () => Promise<T>,\n operationName: string\n): Promise<T> {\n try {\n logger.debug({ operation: operationName }, \"Starting API call\");\n\n const result = await policy.execute(() => fn());\n\n logger.debug({ operation: operationName }, \"API call succeeded\");\n return result;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n logger.error(\n { operation: operationName, error: error.message, stack: error.stack },\n \"API call failed after retries\"\n );\n throw error;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type FetchLatestVersion = () => Promise<string>;
|
|
2
|
+
export interface UpdateNotifierDeps {
|
|
3
|
+
fetchLatestVersion?: FetchLatestVersion;
|
|
4
|
+
log?: (msg: string) => void;
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
}
|
|
7
|
+
export declare function checkForUpdate(pkgName: string, currentVersion: string, deps?: UpdateNotifierDeps): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
async function checkForUpdate(pkgName, currentVersion, deps = {}) {
|
|
2
|
+
const env = deps.env ?? process.env;
|
|
3
|
+
if (env.MCP_DISABLE_UPDATE_CHECK === "1" || env.NODE_ENV === "test") {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const log = deps.log ?? ((msg) => process.stderr.write(msg + "\n"));
|
|
7
|
+
const fetcher = deps.fetchLatestVersion ?? (() => defaultFetch(pkgName));
|
|
8
|
+
let latest;
|
|
9
|
+
try {
|
|
10
|
+
latest = await fetcher();
|
|
11
|
+
} catch {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!latest || !semverLt(currentVersion, latest)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
log(
|
|
18
|
+
`[update] ${pkgName}@${latest} is available (running ${currentVersion}). Upgrade: npx -y ${pkgName}@latest (and relaunch Claude Desktop).`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
async function defaultFetch(pkgName) {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timer = setTimeout(() => controller.abort(), 2e3);
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
headers: { Accept: "application/json" }
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
throw new Error(`HTTP ${res.status}`);
|
|
31
|
+
}
|
|
32
|
+
const body = await res.json();
|
|
33
|
+
if (typeof body.version !== "string") {
|
|
34
|
+
throw new Error("registry response missing version field");
|
|
35
|
+
}
|
|
36
|
+
return body.version;
|
|
37
|
+
} finally {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function semverLt(a, b) {
|
|
42
|
+
const pa = a.split(".").map((x) => parseInt(x, 10) || 0);
|
|
43
|
+
const pb = b.split(".").map((x) => parseInt(x, 10) || 0);
|
|
44
|
+
for (let i = 0; i < 3; i++) {
|
|
45
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return true;
|
|
46
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return false;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
checkForUpdate
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=updateNotifier.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/updateNotifier.ts"],
|
|
4
|
+
"sourcesContent": ["// Fire-and-forget npm registry check at startup. Logs to stderr when a\n// newer version is available. stdout is reserved for MCP JSON-RPC, so the\n// message never goes there. Silent on network error, timeout, or when the\n// installed version is equal to or ahead of the registry latest.\n//\n// Opt out by setting MCP_DISABLE_UPDATE_CHECK=1 (CI, offline, air-gapped).\n// Also skipped when NODE_ENV=test to keep vitest runs silent.\n\nexport type FetchLatestVersion = () => Promise<string>;\n\nexport interface UpdateNotifierDeps {\n fetchLatestVersion?: FetchLatestVersion;\n log?: (msg: string) => void;\n env?: NodeJS.ProcessEnv;\n}\n\nexport async function checkForUpdate(\n pkgName: string,\n currentVersion: string,\n deps: UpdateNotifierDeps = {},\n): Promise<void> {\n const env = deps.env ?? process.env;\n if (env.MCP_DISABLE_UPDATE_CHECK === \"1\" || env.NODE_ENV === \"test\") {\n return;\n }\n const log = deps.log ?? ((msg) => process.stderr.write(msg + \"\\n\"));\n const fetcher = deps.fetchLatestVersion ?? (() => defaultFetch(pkgName));\n let latest: string;\n try {\n latest = await fetcher();\n } catch {\n return;\n }\n if (!latest || !semverLt(currentVersion, latest)) {\n return;\n }\n log(\n `[update] ${pkgName}@${latest} is available (running ${currentVersion}). ` +\n `Upgrade: npx -y ${pkgName}@latest (and relaunch Claude Desktop).`,\n );\n}\n\nasync function defaultFetch(pkgName: string): Promise<string> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 2_000);\n try {\n const res = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {\n signal: controller.signal,\n headers: { Accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n const body = (await res.json()) as { version?: unknown };\n if (typeof body.version !== \"string\") {\n throw new Error(\"registry response missing version field\");\n }\n return body.version;\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split(\".\").map((x) => parseInt(x, 10) || 0);\n const pb = b.split(\".\").map((x) => parseInt(x, 10) || 0);\n for (let i = 0; i < 3; i++) {\n if ((pa[i] ?? 0) < (pb[i] ?? 0)) return true;\n if ((pa[i] ?? 0) > (pb[i] ?? 0)) return false;\n }\n return false;\n}\n"],
|
|
5
|
+
"mappings": "AAgBA,eAAsB,eACpB,SACA,gBACA,OAA2B,CAAC,GACb;AACf,QAAM,MAAM,KAAK,OAAO,QAAQ;AAChC,MAAI,IAAI,6BAA6B,OAAO,IAAI,aAAa,QAAQ;AACnE;AAAA,EACF;AACA,QAAM,MAAM,KAAK,QAAQ,CAAC,QAAQ,QAAQ,OAAO,MAAM,MAAM,IAAI;AACjE,QAAM,UAAU,KAAK,uBAAuB,MAAM,aAAa,OAAO;AACtE,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,QAAQ;AAAA,EACzB,QAAQ;AACN;AAAA,EACF;AACA,MAAI,CAAC,UAAU,CAAC,SAAS,gBAAgB,MAAM,GAAG;AAChD;AAAA,EACF;AACA;AAAA,IACE,YAAY,OAAO,IAAI,MAAM,0BAA0B,cAAc,sBAChD,OAAO;AAAA,EAC9B;AACF;AAEA,eAAe,aAAa,SAAkC;AAC5D,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,GAAK;AACxD,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,8BAA8B,OAAO,WAAW;AAAA,MACtE,QAAQ,WAAW;AAAA,MACnB,SAAS,EAAE,QAAQ,mBAAmB;AAAA,IACxC,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,QAAQ,IAAI,MAAM,EAAE;AAAA,IACtC;AACA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAI,OAAO,KAAK,YAAY,UAAU;AACpC,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AACA,WAAO,KAAK;AAAA,EACd,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;AAEA,SAAS,SAAS,GAAW,GAAoB;AAC/C,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,KAAK,CAAC;AACvD,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,KAAK,CAAC;AACvD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,SAAK,GAAG,CAAC,KAAK,MAAM,GAAG,CAAC,KAAK,GAAI,QAAO;AACxC,SAAK,GAAG,CAAC,KAAK,MAAM,GAAG,CAAC,KAAK,GAAI,QAAO;AAAA,EAC1C;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-gsc",
|
|
3
3
|
"mcpName": "io.github.mharnett/google-gsc",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.2",
|
|
5
5
|
"description": "MCP server for Google Search Console API with multi-client support, search analytics, and URL inspection.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"prompts": "^2.4.2"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
+
"@drak/mcp-test-harness": "file:../mcp-test-harness",
|
|
49
50
|
"@types/node": "^20.10.0",
|
|
50
51
|
"@types/prompts": "^2.4.9",
|
|
51
52
|
"esbuild": "^0.25.0",
|