mcp-google-gsc 1.1.0 → 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/auth-cli.js +5 -8
- package/dist/auth-cli.js.map +2 -2
- 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/auth-cli.js
CHANGED
|
@@ -276,16 +276,13 @@ function randomState() {
|
|
|
276
276
|
globalThis.crypto.getRandomValues(bytes);
|
|
277
277
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
278
278
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const classified = classifyError(err);
|
|
283
|
-
process.stderr.write(`
|
|
279
|
+
run().catch((err) => {
|
|
280
|
+
const classified = classifyError(err);
|
|
281
|
+
process.stderr.write(`
|
|
284
282
|
${classified.message}
|
|
285
283
|
`);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
284
|
+
process.exit(1);
|
|
285
|
+
});
|
|
289
286
|
export {
|
|
290
287
|
run
|
|
291
288
|
};
|
package/dist/auth-cli.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/auth-cli.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n// ============================================\n// mcp-gsc-auth -- one-time OAuth + site selection\n// ============================================\n// Flow:\n// 1. Loopback HTTP listener on a free port\n// 2. Open browser to Google OAuth consent screen (webmasters.readonly scope)\n// 3. Exchange code for tokens\n// 4. Enumerate accessible GSC sites\n// 5. User picks which site to use as default\n// 6. Write credentials to ~/.config/mcp-gsc-nodejs/credentials.json\n\nimport { google } from \"googleapis\";\nimport http from \"http\";\nimport promptsImport from \"prompts\";\nimport { URL } from \"url\";\nimport { writeStoredCredentials, credentialsFilePath, CREDENTIALS_FILE_VERSION, type StoredCredentials } from \"./credentials.js\";\nimport { EMBEDDED_CLIENT_ID, EMBEDDED_CLIENT_SECRET } from \"./embedded-secrets.js\";\nimport { classifyError, GscAuthError } from \"./errors.js\";\nimport { findFreeLoopbackPort, openBrowser } from \"./platform.js\";\nimport { logger, withResilience } from \"./resilience.js\";\n\nconst prompts = (promptsImport as unknown as { default?: typeof promptsImport }).default ?? promptsImport;\n\nconst OAUTH_SCOPE = \"https://www.googleapis.com/auth/webmasters.readonly\";\nconst OAUTH_AUTH_URL = \"https://accounts.google.com/o/oauth2/v2/auth\";\nconst OAUTH_TOKEN_URL = \"https://oauth2.googleapis.com/token\";\n\ninterface CliArgs {\n siteUrl?: string;\n help: boolean;\n}\n\nfunction parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = { help: false };\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--help\" || a === \"-h\") args.help = true;\n else if (a === \"--site-url\" && argv[i + 1]) {\n args.siteUrl = argv[++i];\n }\n }\n return args;\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n [\n \"mcp-gsc-auth -- authorize Claude to access your Google Search Console data\",\n \"\",\n \"Usage:\",\n \" npx mcp-gsc-auth\",\n \" npx mcp-gsc-auth --site-url https://example.com/\",\n \"\",\n \"Options:\",\n \" --site-url <url> Skip the site picker and use this property directly\",\n \" -h, --help Show this help\",\n \"\",\n `Credentials are written to: ${credentialsFilePath}`,\n \"\",\n ].join(\"\\n\"),\n );\n}\n\n// ============================================\n// OAUTH: LOOPBACK REDIRECT FLOW\n// ============================================\n\nfunction buildAuthUrl(clientId: string, redirectUri: string, state: string): string {\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: OAUTH_SCOPE,\n access_type: \"offline\",\n prompt: \"consent\",\n state,\n });\n return `${OAUTH_AUTH_URL}?${params.toString()}`;\n}\n\ninterface AuthorizationCode {\n code: string;\n state: string;\n}\n\nasync function waitForAuthorizationCode(\n port: number,\n expectedState: string,\n authUrl: string,\n): Promise<AuthorizationCode> {\n return new Promise<AuthorizationCode>((resolve, reject) => {\n let settled = false;\n const finish = (fn: () => void) => {\n if (settled) return;\n settled = true;\n fn();\n };\n\n const server = http.createServer((req, res) => {\n if (!req.url) {\n res.writeHead(404).end();\n return;\n }\n const parsed = new URL(req.url, `http://127.0.0.1:${port}`);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\");\n const error = parsed.searchParams.get(\"error\");\n\n if (error) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderPage(\"Authorization was denied\", `Google returned: ${escapeHtml(error)}. Close this tab and re-run the command.`));\n finish(() => { server.close(); reject(new GscAuthError(`OAuth denied: ${error}`)); });\n return;\n }\n\n if (!code) {\n res.writeHead(204).end();\n return;\n }\n\n if (state !== expectedState) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderPage(\"Security check failed\", \"The state parameter did not match. Please re-run the command.\"));\n finish(() => { server.close(); reject(new GscAuthError(\"OAuth state mismatch\")); });\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderPage(\"Signed in successfully\", \"You can close this tab and return to the terminal.\"));\n finish(() => {\n setTimeout(() => server.close(), 200);\n resolve({ code, state });\n });\n });\n\n server.on(\"error\", (err) => {\n finish(() => reject(new Error(`Loopback server failed: ${err.message}`)));\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n process.stderr.write(`\\nOpening your browser to sign in with Google...\\n`);\n process.stderr.write(`If it doesn't open automatically, visit:\\n ${authUrl}\\n\\n`);\n openBrowser(authUrl).catch((err) => {\n logger.warn({ err: err.message }, \"openBrowser failed\");\n });\n });\n\n setTimeout(() => {\n finish(() => { server.close(); reject(new Error(\"Timed out waiting for OAuth callback (5 minutes).\")); });\n }, 5 * 60 * 1000);\n });\n}\n\nfunction renderPage(title: string, body: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\"><title>${escapeHtml(title)}</title>\n <style>body{font:15px -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;max-width:480px;margin:80px auto;padding:0 24px;color:#222}h1{font-size:22px;margin-bottom:12px}p{line-height:1.5}</style>\n</head>\n<body><h1>${escapeHtml(title)}</h1><p>${escapeHtml(body)}</p></body>\n</html>`;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[c]!);\n}\n\n// ============================================\n// TOKEN EXCHANGE\n// ============================================\n\ninterface TokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n scope: string;\n token_type: string;\n}\n\nasync function exchangeCodeForTokens(\n code: string,\n clientId: string,\n clientSecret: string,\n redirectUri: string,\n): Promise<TokenResponse> {\n return withResilience(async () => {\n const body = new URLSearchParams({\n code,\n client_id: clientId,\n client_secret: clientSecret,\n redirect_uri: redirectUri,\n grant_type: \"authorization_code\",\n });\n const res = await fetch(OAUTH_TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n const json = (await res.json()) as Record<string, unknown>;\n if (!res.ok || json.error) {\n const err = new Error(\n `Token exchange failed: ${json.error_description || json.error || res.statusText}`,\n );\n (err as any).status = res.status;\n (err as any).code = res.status;\n throw err;\n }\n return json as unknown as TokenResponse;\n }, \"oauth.exchangeCode\");\n}\n\n// ============================================\n// SITE ENUMERATION\n// ============================================\n\ninterface GscSite {\n siteUrl: string;\n permissionLevel: string;\n}\n\nasync function enumerateSites(accessToken: string): Promise<GscSite[]> {\n const oauth2Client = new google.auth.OAuth2();\n oauth2Client.setCredentials({ access_token: accessToken });\n\n const svc = google.searchconsole({ version: \"v1\", auth: oauth2Client });\n const resp = await withResilience(\n () => svc.sites.list(),\n \"auth.listSites\",\n );\n\n const sites = (resp.data.siteEntry || []).map((entry) => ({\n siteUrl: entry.siteUrl || \"\",\n permissionLevel: entry.permissionLevel || \"\",\n }));\n\n if (sites.length === 0) {\n throw new GscAuthError(\n \"No Search Console properties found for this Google account. \" +\n \"Make sure you signed in with an account that has access to at least one property.\",\n );\n }\n\n return sites;\n}\n\n// ============================================\n// PICKER\n// ============================================\n\nasync function pickSite(sites: GscSite[], presetSiteUrl?: string): Promise<GscSite> {\n if (presetSiteUrl) {\n const match = sites.find((s) => s.siteUrl === presetSiteUrl);\n if (!match) {\n throw new Error(\n `--site-url \"${presetSiteUrl}\" was not found among ${sites.length} accessible properties. ` +\n `Remove the flag to pick interactively.`,\n );\n }\n return match;\n }\n\n if (sites.length === 1) {\n process.stderr.write(`\\nOnly one property accessible: ${sites[0].siteUrl}. Auto-selecting.\\n`);\n return sites[0];\n }\n\n const choices = sites.map((site) => ({\n title: `${site.siteUrl} (${site.permissionLevel})`,\n value: site,\n }));\n\n const response = await prompts(\n {\n type: \"select\",\n name: \"site\",\n message: \"Which Search Console property should Claude use by default?\",\n choices,\n initial: 0,\n },\n {\n onCancel: () => { throw new Error(\"Cancelled by user\"); },\n },\n );\n\n if (!response.site) throw new Error(\"No site selected\");\n return response.site as GscSite;\n}\n\n// ============================================\n// MAIN\n// ============================================\n\nexport async function run(argv: string[] = process.argv.slice(2)): Promise<void> {\n const args = parseArgs(argv);\n if (args.help) {\n printHelp();\n return;\n }\n\n const clientId = process.env.GOOGLE_GSC_CLIENT_ID?.trim() || EMBEDDED_CLIENT_ID;\n const clientSecret = process.env.GOOGLE_GSC_CLIENT_SECRET?.trim() || EMBEDDED_CLIENT_SECRET;\n\n if (!clientId || !clientSecret) {\n process.stderr.write(\n \"This build of mcp-gsc was published without embedded OAuth credentials.\\n\" +\n \"Set GOOGLE_GSC_CLIENT_ID and GOOGLE_GSC_CLIENT_SECRET in your environment.\\n\",\n );\n process.exit(2);\n }\n\n const port = await findFreeLoopbackPort();\n const redirectUri = `http://127.0.0.1:${port}`;\n const state = randomState();\n const authUrl = buildAuthUrl(clientId, redirectUri, state);\n\n process.stderr.write(\"\\n=== mcp-gsc authentication ===\\n\");\n\n const { code } = await waitForAuthorizationCode(port, state, authUrl);\n process.stderr.write(\"Authorization code received. Exchanging for tokens...\\n\");\n\n const tokens = await exchangeCodeForTokens(code, clientId, clientSecret, redirectUri);\n if (!tokens.refresh_token) {\n throw new GscAuthError(\n \"Google did not return a refresh token. This can happen if you previously granted consent \" +\n \"to this app. Revoke access at https://myaccount.google.com/permissions and try again.\",\n );\n }\n process.stderr.write(\"Tokens received. Fetching accessible Search Console properties...\\n\");\n\n const sites = await enumerateSites(tokens.access_token);\n const chosen = await pickSite(sites, args.siteUrl);\n\n const stored: StoredCredentials = {\n version: CREDENTIALS_FILE_VERSION,\n refresh_token: tokens.refresh_token,\n site_urls: sites.map((s) => s.siteUrl),\n primary_site_url: chosen.siteUrl,\n obtained_at: new Date().toISOString(),\n scopes: [OAUTH_SCOPE],\n };\n writeStoredCredentials(stored);\n\n process.stderr.write(\n [\n \"\",\n \"Done.\",\n \"\",\n ` Property: ${chosen.siteUrl}`,\n ` Permission: ${chosen.permissionLevel}`,\n ` Saved to: ${credentialsFilePath}`,\n \"\",\n \"Next step: fully quit Claude Desktop (Cmd+Q / File > Exit) and reopen it.\",\n 'Then try: \"List my Search Console properties\"',\n \"\",\n ].join(\"\\n\"),\n );\n}\n\nfunction randomState(): string {\n const bytes = new Uint8Array(16);\n (globalThis.crypto as Crypto).getRandomValues(bytes);\n return Array.from(bytes).map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n// ============================================\n// ENTRY\n// ============================================\n\nconst isMain =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"/auth-cli.js\") ||\n process.argv[1]?.endsWith(\"\\\\auth-cli.js\");\n\nif (isMain) {\n run().catch((err) => {\n const classified = classifyError(err);\n process.stderr.write(`\\n${classified.message}\\n`);\n process.exit(1);\n });\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n// ============================================\n// mcp-gsc-auth -- one-time OAuth + site selection\n// ============================================\n// Flow:\n// 1. Loopback HTTP listener on a free port\n// 2. Open browser to Google OAuth consent screen (webmasters.readonly scope)\n// 3. Exchange code for tokens\n// 4. Enumerate accessible GSC sites\n// 5. User picks which site to use as default\n// 6. Write credentials to ~/.config/mcp-gsc-nodejs/credentials.json\n\nimport { google } from \"googleapis\";\nimport http from \"http\";\nimport promptsImport from \"prompts\";\nimport { URL } from \"url\";\nimport { writeStoredCredentials, credentialsFilePath, CREDENTIALS_FILE_VERSION, type StoredCredentials } from \"./credentials.js\";\nimport { EMBEDDED_CLIENT_ID, EMBEDDED_CLIENT_SECRET } from \"./embedded-secrets.js\";\nimport { classifyError, GscAuthError } from \"./errors.js\";\nimport { findFreeLoopbackPort, openBrowser } from \"./platform.js\";\nimport { logger, withResilience } from \"./resilience.js\";\n\nconst prompts = (promptsImport as unknown as { default?: typeof promptsImport }).default ?? promptsImport;\n\nconst OAUTH_SCOPE = \"https://www.googleapis.com/auth/webmasters.readonly\";\nconst OAUTH_AUTH_URL = \"https://accounts.google.com/o/oauth2/v2/auth\";\nconst OAUTH_TOKEN_URL = \"https://oauth2.googleapis.com/token\";\n\ninterface CliArgs {\n siteUrl?: string;\n help: boolean;\n}\n\nfunction parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = { help: false };\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--help\" || a === \"-h\") args.help = true;\n else if (a === \"--site-url\" && argv[i + 1]) {\n args.siteUrl = argv[++i];\n }\n }\n return args;\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n [\n \"mcp-gsc-auth -- authorize Claude to access your Google Search Console data\",\n \"\",\n \"Usage:\",\n \" npx mcp-gsc-auth\",\n \" npx mcp-gsc-auth --site-url https://example.com/\",\n \"\",\n \"Options:\",\n \" --site-url <url> Skip the site picker and use this property directly\",\n \" -h, --help Show this help\",\n \"\",\n `Credentials are written to: ${credentialsFilePath}`,\n \"\",\n ].join(\"\\n\"),\n );\n}\n\n// ============================================\n// OAUTH: LOOPBACK REDIRECT FLOW\n// ============================================\n\nfunction buildAuthUrl(clientId: string, redirectUri: string, state: string): string {\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: OAUTH_SCOPE,\n access_type: \"offline\",\n prompt: \"consent\",\n state,\n });\n return `${OAUTH_AUTH_URL}?${params.toString()}`;\n}\n\ninterface AuthorizationCode {\n code: string;\n state: string;\n}\n\nasync function waitForAuthorizationCode(\n port: number,\n expectedState: string,\n authUrl: string,\n): Promise<AuthorizationCode> {\n return new Promise<AuthorizationCode>((resolve, reject) => {\n let settled = false;\n const finish = (fn: () => void) => {\n if (settled) return;\n settled = true;\n fn();\n };\n\n const server = http.createServer((req, res) => {\n if (!req.url) {\n res.writeHead(404).end();\n return;\n }\n const parsed = new URL(req.url, `http://127.0.0.1:${port}`);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\");\n const error = parsed.searchParams.get(\"error\");\n\n if (error) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderPage(\"Authorization was denied\", `Google returned: ${escapeHtml(error)}. Close this tab and re-run the command.`));\n finish(() => { server.close(); reject(new GscAuthError(`OAuth denied: ${error}`)); });\n return;\n }\n\n if (!code) {\n res.writeHead(204).end();\n return;\n }\n\n if (state !== expectedState) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderPage(\"Security check failed\", \"The state parameter did not match. Please re-run the command.\"));\n finish(() => { server.close(); reject(new GscAuthError(\"OAuth state mismatch\")); });\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderPage(\"Signed in successfully\", \"You can close this tab and return to the terminal.\"));\n finish(() => {\n setTimeout(() => server.close(), 200);\n resolve({ code, state });\n });\n });\n\n server.on(\"error\", (err) => {\n finish(() => reject(new Error(`Loopback server failed: ${err.message}`)));\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n process.stderr.write(`\\nOpening your browser to sign in with Google...\\n`);\n process.stderr.write(`If it doesn't open automatically, visit:\\n ${authUrl}\\n\\n`);\n openBrowser(authUrl).catch((err) => {\n logger.warn({ err: err.message }, \"openBrowser failed\");\n });\n });\n\n setTimeout(() => {\n finish(() => { server.close(); reject(new Error(\"Timed out waiting for OAuth callback (5 minutes).\")); });\n }, 5 * 60 * 1000);\n });\n}\n\nfunction renderPage(title: string, body: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\"><title>${escapeHtml(title)}</title>\n <style>body{font:15px -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;max-width:480px;margin:80px auto;padding:0 24px;color:#222}h1{font-size:22px;margin-bottom:12px}p{line-height:1.5}</style>\n</head>\n<body><h1>${escapeHtml(title)}</h1><p>${escapeHtml(body)}</p></body>\n</html>`;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[c]!);\n}\n\n// ============================================\n// TOKEN EXCHANGE\n// ============================================\n\ninterface TokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n scope: string;\n token_type: string;\n}\n\nasync function exchangeCodeForTokens(\n code: string,\n clientId: string,\n clientSecret: string,\n redirectUri: string,\n): Promise<TokenResponse> {\n return withResilience(async () => {\n const body = new URLSearchParams({\n code,\n client_id: clientId,\n client_secret: clientSecret,\n redirect_uri: redirectUri,\n grant_type: \"authorization_code\",\n });\n const res = await fetch(OAUTH_TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n const json = (await res.json()) as Record<string, unknown>;\n if (!res.ok || json.error) {\n const err = new Error(\n `Token exchange failed: ${json.error_description || json.error || res.statusText}`,\n );\n (err as any).status = res.status;\n (err as any).code = res.status;\n throw err;\n }\n return json as unknown as TokenResponse;\n }, \"oauth.exchangeCode\");\n}\n\n// ============================================\n// SITE ENUMERATION\n// ============================================\n\ninterface GscSite {\n siteUrl: string;\n permissionLevel: string;\n}\n\nasync function enumerateSites(accessToken: string): Promise<GscSite[]> {\n const oauth2Client = new google.auth.OAuth2();\n oauth2Client.setCredentials({ access_token: accessToken });\n\n const svc = google.searchconsole({ version: \"v1\", auth: oauth2Client });\n const resp = await withResilience(\n () => svc.sites.list(),\n \"auth.listSites\",\n );\n\n const sites = (resp.data.siteEntry || []).map((entry) => ({\n siteUrl: entry.siteUrl || \"\",\n permissionLevel: entry.permissionLevel || \"\",\n }));\n\n if (sites.length === 0) {\n throw new GscAuthError(\n \"No Search Console properties found for this Google account. \" +\n \"Make sure you signed in with an account that has access to at least one property.\",\n );\n }\n\n return sites;\n}\n\n// ============================================\n// PICKER\n// ============================================\n\nasync function pickSite(sites: GscSite[], presetSiteUrl?: string): Promise<GscSite> {\n if (presetSiteUrl) {\n const match = sites.find((s) => s.siteUrl === presetSiteUrl);\n if (!match) {\n throw new Error(\n `--site-url \"${presetSiteUrl}\" was not found among ${sites.length} accessible properties. ` +\n `Remove the flag to pick interactively.`,\n );\n }\n return match;\n }\n\n if (sites.length === 1) {\n process.stderr.write(`\\nOnly one property accessible: ${sites[0].siteUrl}. Auto-selecting.\\n`);\n return sites[0];\n }\n\n const choices = sites.map((site) => ({\n title: `${site.siteUrl} (${site.permissionLevel})`,\n value: site,\n }));\n\n const response = await prompts(\n {\n type: \"select\",\n name: \"site\",\n message: \"Which Search Console property should Claude use by default?\",\n choices,\n initial: 0,\n },\n {\n onCancel: () => { throw new Error(\"Cancelled by user\"); },\n },\n );\n\n if (!response.site) throw new Error(\"No site selected\");\n return response.site as GscSite;\n}\n\n// ============================================\n// MAIN\n// ============================================\n\nexport async function run(argv: string[] = process.argv.slice(2)): Promise<void> {\n const args = parseArgs(argv);\n if (args.help) {\n printHelp();\n return;\n }\n\n const clientId = process.env.GOOGLE_GSC_CLIENT_ID?.trim() || EMBEDDED_CLIENT_ID;\n const clientSecret = process.env.GOOGLE_GSC_CLIENT_SECRET?.trim() || EMBEDDED_CLIENT_SECRET;\n\n if (!clientId || !clientSecret) {\n process.stderr.write(\n \"This build of mcp-gsc was published without embedded OAuth credentials.\\n\" +\n \"Set GOOGLE_GSC_CLIENT_ID and GOOGLE_GSC_CLIENT_SECRET in your environment.\\n\",\n );\n process.exit(2);\n }\n\n const port = await findFreeLoopbackPort();\n const redirectUri = `http://127.0.0.1:${port}`;\n const state = randomState();\n const authUrl = buildAuthUrl(clientId, redirectUri, state);\n\n process.stderr.write(\"\\n=== mcp-gsc authentication ===\\n\");\n\n const { code } = await waitForAuthorizationCode(port, state, authUrl);\n process.stderr.write(\"Authorization code received. Exchanging for tokens...\\n\");\n\n const tokens = await exchangeCodeForTokens(code, clientId, clientSecret, redirectUri);\n if (!tokens.refresh_token) {\n throw new GscAuthError(\n \"Google did not return a refresh token. This can happen if you previously granted consent \" +\n \"to this app. Revoke access at https://myaccount.google.com/permissions and try again.\",\n );\n }\n process.stderr.write(\"Tokens received. Fetching accessible Search Console properties...\\n\");\n\n const sites = await enumerateSites(tokens.access_token);\n const chosen = await pickSite(sites, args.siteUrl);\n\n const stored: StoredCredentials = {\n version: CREDENTIALS_FILE_VERSION,\n refresh_token: tokens.refresh_token,\n site_urls: sites.map((s) => s.siteUrl),\n primary_site_url: chosen.siteUrl,\n obtained_at: new Date().toISOString(),\n scopes: [OAUTH_SCOPE],\n };\n writeStoredCredentials(stored);\n\n process.stderr.write(\n [\n \"\",\n \"Done.\",\n \"\",\n ` Property: ${chosen.siteUrl}`,\n ` Permission: ${chosen.permissionLevel}`,\n ` Saved to: ${credentialsFilePath}`,\n \"\",\n \"Next step: fully quit Claude Desktop (Cmd+Q / File > Exit) and reopen it.\",\n 'Then try: \"List my Search Console properties\"',\n \"\",\n ].join(\"\\n\"),\n );\n}\n\nfunction randomState(): string {\n const bytes = new Uint8Array(16);\n (globalThis.crypto as Crypto).getRandomValues(bytes);\n return Array.from(bytes).map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n// ============================================\n// ENTRY\n// ============================================\n\n// Always run when loaded as an entry point. The bin symlink name (mcp-gsc-auth)\n// differs from the file name (auth-cli.js), so import.meta.url checks don't\n// work reliably under npx. Since this module has no side effects when imported\n// as a library (run() must be called explicitly), unconditional execution is safe.\nrun().catch((err) => {\n const classified = classifyError(err);\n process.stderr.write(`\\n${classified.message}\\n`);\n process.exit(1);\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
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": "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",
|
|
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",
|