@vmandic/searchconsole-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vedran Mandić
4
+ Copyright (c) 2026 Renzo Johnson
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,697 @@
1
+ # Search Console MCP
2
+
3
+ [![CI](https://github.com/vmandic/searchconsole-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/vmandic/searchconsole-mcp/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](package.json)
6
+ [![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-6366f1)](https://modelcontextprotocol.io)
7
+
8
+ **Read-only [Google Search Console](https://search.google.com/search-console) for AI coding agents.**
9
+ Connect Cursor, Claude Desktop, or any MCP client to your GSC properties: search performance, URL inspection, and sitemaps, without leaving the editor.
10
+
11
+ - **Read-only by design** — OAuth scope `webmasters.readonly` only; no writes to Google
12
+ - **Stdio by default** — safe local use with Cursor and Claude
13
+ - **Five focused tools** — no GA4, no Indexing API, no admin clutter
14
+ - **Hardened HTTP mode** — optional streamable HTTP with loopback bind, body limits, and session caps
15
+
16
+ ---
17
+
18
+ ## Table of contents
19
+
20
+ - [Why use this](#why-use-this)
21
+ - [Features](#features)
22
+ - [Quick start](#quick-start)
23
+ - [Agentic install prompt](#agentic-install-prompt)
24
+ - [Manual setup](#manual-setup)
25
+ - [Requirements](#requirements)
26
+ - [Installation](#installation)
27
+ - [Google authentication](#google-authentication)
28
+ - [Connect your MCP client](#connect-your-mcp-client)
29
+ - [Tools reference](#tools-reference)
30
+ - [Example prompts for agents](#example-prompts-for-agents)
31
+ - [Security](#security)
32
+ - [Transports: stdio vs HTTP](#transports-stdio-vs-http)
33
+ - [Configuration](#configuration)
34
+ - [Troubleshooting](#troubleshooting)
35
+ - [Development](#development)
36
+ - [License](#license)
37
+
38
+ ---
39
+
40
+ ## Why use this
41
+
42
+ Search Console data helps agents answer real SEO questions: which queries drive traffic, whether a URL is indexed, or what sitemaps are on file. This server exposes that data through the [Model Context Protocol](https://modelcontextprotocol.io) so tools like Cursor can call Google’s API on your behalf.
43
+
44
+ Use it when you want:
45
+
46
+ - Property lists and search analytics inside an agent session
47
+ - URL inspection results without opening the GSC UI
48
+ - A **small, auditable** surface (five tools) instead of a general Google analytics bundle
49
+
50
+ ---
51
+
52
+ ## Features
53
+
54
+ | Capability | MCP tool |
55
+ |------------|----------|
56
+ | List properties you can access | `gsc_list_sites` |
57
+ | Clicks, impressions, CTR, position (with dimensions) | `gsc_search_analytics` |
58
+ | Indexing / crawl / rich-result inspection for a URL | `gsc_inspect_url` |
59
+ | Sitemaps submitted for a property | `gsc_list_sitemaps` |
60
+ | MCP server liveness (local Node.js, not Google) | `gsc_mcp_server_ping` |
61
+
62
+ **Input validation** — Tool arguments are validated with Zod (dates, URLs, row limits, allowlisted dimensions).
63
+
64
+ **Clear errors** — Failures return MCP text with `isError: true` and actionable messages (auth, `site_url` format, quota).
65
+
66
+ ---
67
+
68
+ ## Quick start
69
+
70
+ ### Agentic install prompt
71
+
72
+ Paste the block below into **Cursor, Claude Code, Copilot, or Codex** and ask it to run the setup. The agent should execute the steps on your machine and wire up your MCP client.
73
+
74
+ **Assumptions (confirm with you before changing anything):**
75
+
76
+ | Assumption | Why it matters |
77
+ |------------|----------------|
78
+ | **Node.js 18+** | Required to build and run `searchconsole-mcp` |
79
+ | **`gcloud` CLI** | Used for API enablement and [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) (ADC) |
80
+ | **Google Cloud project** | Search Console API must be enabled on a GCP project ([not the same as a GSC property](#google-cloud-project-required)) |
81
+ | **Google account** | Must already have access to the Search Console properties you care about |
82
+ | **User ADC (default)** | Setup uses `gcloud auth application-default login` with `webmasters.readonly`, not a checked-in service account key |
83
+ | **Stdio transport** | MCP config must **not** pass `--transport http` unless you explicitly want HTTP mode |
84
+ | **Absolute paths** | MCP config needs the real path to `dist/server.js` after `npm run build` |
85
+ | **Which MCP client** | Config file shape differs (Cursor/Claude Code: `mcpServers`; VS Code Copilot: `servers`; Codex: `config.toml`) |
86
+
87
+ The agent should ask for: install directory, GCP project ID, and which client you use.
88
+
89
+ ```
90
+ Set up the searchconsole-mcp MCP server from https://github.com/vmandic/searchconsole-mcp on this machine end-to-end.
91
+
92
+ Before you change anything, confirm with me:
93
+ 1) Which MCP client I use (Cursor, Claude Code, GitHub Copilot in VS Code, OpenAI Codex, or Claude Desktop).
94
+ 2) A Google Cloud project ID where we can enable the Search Console API (or use my current gcloud default project).
95
+ 3) Where to clone the repo (default: ~/source/vmandic/searchconsole-mcp or a path I choose).
96
+
97
+ Then do the following, reporting each step:
98
+
99
+ A) Prerequisites
100
+ - Verify Node.js >= 18 and gcloud are installed.
101
+ - Do not commit or paste any secrets into the repo.
102
+
103
+ B) Clone, build, test
104
+ - git clone https://github.com/vmandic/searchconsole-mcp.git into the chosen directory.
105
+ - npm install && npm run build && npm test
106
+ - Confirm dist/server.js exists.
107
+
108
+ C) Google Cloud + auth (user ADC)
109
+ - gcloud services enable searchconsole.googleapis.com --project=PROJECT_ID
110
+ - gcloud auth application-default login --scopes=https://www.googleapis.com/auth/webmasters.readonly
111
+ (If this opens a browser, tell me to complete sign-in.)
112
+ - gcloud auth application-default set-quota-project PROJECT_ID
113
+ - Remind me: I need Search Console property access on my Google account; the GCP project only enables the API.
114
+
115
+ D) MCP client config (stdio only — no --transport http)
116
+ - Add searchconsole-mcp using command "node" and args ["ABSOLUTE_PATH/dist/server.js"], or command "searchconsole-mcp" if we npm link -g.
117
+ - Use the correct config file for my client (see the repo README "Connect your MCP client"):
118
+ - Claude Code: prefer `claude mcp add searchconsole-mcp --transport stdio -- node ABSOLUTE_PATH/dist/server.js` first if I use multiple clients.
119
+ - Cursor: ~/.cursor/mcp.json → mcpServers
120
+ - VS Code Copilot: .vscode/mcp.json or user MCP config → servers, type stdio
121
+ - Codex: ~/.codex/config.toml → [mcp_servers.searchconsole-mcp] or `codex mcp add`
122
+ - Use absolute paths only.
123
+
124
+ E) Verify
125
+ - Tell me to restart the MCP client.
126
+ - After restart, call tool gsc_mcp_server_ping (local server only, not Google).
127
+ - Call gsc_list_sites and show whether properties were returned; if auth fails, point me to README troubleshooting.
128
+
129
+ When finished, summarize: clone path, GCP project ID, config file edited, and the exact JSON/TOML or CLI command used.
130
+ ```
131
+
132
+ ---
133
+
134
+ ### Manual setup
135
+
136
+ Use this if you prefer to run commands yourself.
137
+
138
+ **1. Install and build** (from source):
139
+
140
+ ```bash
141
+ git clone https://github.com/vmandic/searchconsole-mcp.git
142
+ cd searchconsole-mcp
143
+ npm install
144
+ npm run build
145
+ ```
146
+
147
+ **2. Enable the API and authenticate** (one-time; requires a [Google Cloud project](#google-cloud-project-required)):
148
+
149
+ ```bash
150
+ gcloud services enable searchconsole.googleapis.com --project=YOUR_PROJECT_ID
151
+ ```
152
+
153
+ ```bash
154
+ gcloud auth application-default login \
155
+ --scopes=https://www.googleapis.com/auth/webmasters.readonly
156
+ ```
157
+
158
+ ```bash
159
+ gcloud auth application-default set-quota-project YOUR_PROJECT_ID
160
+ ```
161
+
162
+ Your Google user must have access to the Search Console properties you want. The GCP project does not replace that.
163
+
164
+ **3. Connect a client** — example for **Cursor** (`~/.cursor/mcp.json`). Using Claude Code, Copilot, or Codex too? See [Connect your MCP client](#connect-your-mcp-client) (set up **Claude Code first** if you use several).
165
+
166
+ ```json
167
+ {
168
+ "mcpServers": {
169
+ "searchconsole-mcp": {
170
+ "command": "node",
171
+ "args": ["/absolute/path/to/searchconsole-mcp/dist/server.js"],
172
+ "env": {}
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ **4. Restart the client**, then ask: *“List my Search Console properties”* (tool `gsc_list_sites`).
179
+
180
+ Stdio is the default (no extra flags). Logs go to stderr; JSON-RPC uses stdin/stdout.
181
+
182
+ ---
183
+
184
+ ## Requirements
185
+
186
+ | Requirement | Notes |
187
+ |-------------|--------|
188
+ | **Node.js** | 18 or newer |
189
+ | **Google account** | With access to the Search Console properties you care about |
190
+ | **Google Cloud project** | Required to [enable the Search Console API](https://developers.google.com/webmaster-tools/v1/prereqs); used for API quota (not the same as a GSC property) |
191
+ | **Credentials** | [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) (user login via `gcloud`) **or** a service account JSON via `GOOGLE_APPLICATION_CREDENTIALS` |
192
+ | **MCP client** | Cursor, Claude Desktop, or any client that supports stdio MCP (HTTP optional) |
193
+
194
+ Optional: [Google Cloud SDK](https://cloud.google.com/sdk) (`gcloud`) for the interactive ADC login flow.
195
+
196
+ ---
197
+
198
+ ## Installation
199
+
200
+ ### Option A — Run from a clone (recommended for development)
201
+
202
+ ```bash
203
+ git clone https://github.com/vmandic/searchconsole-mcp.git
204
+ cd searchconsole-mcp
205
+ npm install
206
+ npm test # optional: unit tests
207
+ npm run build # produces dist/server.js
208
+ ```
209
+
210
+ Verify:
211
+
212
+ ```bash
213
+ node dist/server.js --help
214
+ node dist/server.js --version
215
+ ```
216
+
217
+ ### Option B — Global CLI after build
218
+
219
+ ```bash
220
+ npm link -g
221
+ searchconsole-mcp --help
222
+ ```
223
+
224
+ Then point your MCP client at `searchconsole-mcp` instead of `node …/dist/server.js`.
225
+
226
+ ### Option C — `npx` (when published to npm)
227
+
228
+ The package is published under the **`@vmandic`** scope because npm blocks the unscoped name as too similar to [`search-console-mcp`](https://www.npmjs.com/package/search-console-mcp).
229
+
230
+ ```bash
231
+ npx -y @vmandic/searchconsole-mcp
232
+ ```
233
+
234
+ Until the package is on npm, use Option A or B from a local clone.
235
+
236
+ ---
237
+
238
+ ## Google authentication
239
+
240
+ The server never stores passwords. It uses **Application Default Credentials** (ADC): the same mechanism as `gcloud` and Google client libraries.
241
+
242
+ You need **two different things** from Google:
243
+
244
+ | What | Purpose |
245
+ |------|---------|
246
+ | **Search Console property access** | Your Google user (or service account) must be a user on the site in [Search Console](https://search.google.com/search-console) |
247
+ | **Google Cloud project** | Enables the Search Console API and attributes quota/billing for API calls |
248
+
249
+ Having a site in Search Console is **not** enough on its own. Google’s [API prerequisites](https://developers.google.com/webmaster-tools/v1/prereqs) require a Cloud project with the API turned on.
250
+
251
+ ### Google Cloud project (required)
252
+
253
+ 1. Create or pick a project in [Google Cloud Console](https://console.cloud.google.com/).
254
+ 2. Enable the API (once per project):
255
+
256
+ ```bash
257
+ gcloud services enable searchconsole.googleapis.com --project=YOUR_PROJECT_ID
258
+ ```
259
+
260
+ Or: **APIs & Services → Library** → search **Google Search Console API** → **Enable**.
261
+
262
+ 3. After ADC login, set the **quota project** if Google asks for one (common with user credentials):
263
+
264
+ ```bash
265
+ gcloud auth application-default set-quota-project YOUR_PROJECT_ID
266
+ ```
267
+
268
+ `YOUR_PROJECT_ID` is the Cloud project ID (e.g. `my-seo-tools`), not a Search Console property URL.
269
+
270
+ **Service accounts** must be created inside this same Cloud project. The JSON key you download is tied to that project; you still add the service account email as a user on each GSC property.
271
+
272
+ This MCP server does not ask you for a project ID in config. `GoogleAuth` picks it up from ADC, `GOOGLE_CLOUD_PROJECT`, or `gcloud config set project`.
273
+
274
+ ### Personal / laptop setup (most common)
275
+
276
+ ```bash
277
+ gcloud auth application-default login \
278
+ --scopes=https://www.googleapis.com/auth/webmasters.readonly
279
+ ```
280
+
281
+ That scope is enough for this server. Your Google user must already have access to the relevant Search Console properties, and the Search Console API must be [enabled on a Cloud project](#google-cloud-project-required) (see above).
282
+
283
+ ### Service account
284
+
285
+ Set a key file path:
286
+
287
+ ```bash
288
+ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
289
+ ```
290
+
291
+ The service account must be added as a user on each GSC property (Settings → Users and permissions).
292
+
293
+ ### Sharing ADC with Analytics MCP
294
+
295
+ If you use [Google’s Analytics MCP](https://github.com/googleanalytics/google-analytics-mcp) on the same machine, you can request multiple scopes in one login:
296
+
297
+ ```bash
298
+ gcloud auth application-default login \
299
+ --scopes=https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/webmasters.readonly
300
+ ```
301
+
302
+ This server only needs `webmasters.readonly`; extra scopes are optional for your workflow.
303
+
304
+ ---
305
+
306
+ ## Connect your MCP client
307
+
308
+ Every client runs the **same local Node.js MCP server** (`searchconsole-mcp`). That process talks to **Google Search Console** on your behalf. The client (Cursor, Claude Code, Copilot, Codex) only spawns the server and passes tool calls over stdio.
309
+
310
+ **Before you connect any client**
311
+
312
+ 1. Run `npm run build` so `dist/server.js` exists.
313
+ 2. Complete [Google authentication](#google-authentication) (ADC) once on the machine.
314
+ 3. Use an **absolute path** to `dist/server.js` in config (or `searchconsole-mcp` after `npm link -g`).
315
+
316
+ **If you use more than one client**, set up **Claude Code first**. You will reuse the same binary and credentials; doing auth and paths once avoids confusion when you add Cursor, Copilot, or Codex.
317
+
318
+ | Client | Config location | Config key |
319
+ |--------|-----------------|------------|
320
+ | Claude Code | CLI, `~/.claude.json`, or project `.mcp.json` | `mcpServers` |
321
+ | Cursor | `~/.cursor/mcp.json` | `mcpServers` |
322
+ | GitHub Copilot (VS Code) | `.vscode/mcp.json` or user MCP config | `servers` |
323
+ | OpenAI Codex | `~/.codex/config.toml` | `[mcp_servers.<name>]` |
324
+ | Claude Desktop | OS-specific Claude config | `mcpServers` |
325
+
326
+ All examples below use **stdio** (default). Do not pass `--transport http` unless you intend [HTTP mode](#transports-stdio-vs-http).
327
+
328
+ ---
329
+
330
+ ### 1. Claude Code (set up first)
331
+
332
+ [Claude Code](https://code.claude.com/) is Anthropic’s terminal coding agent. Configure searchconsole-mcp here first if you plan to use multiple tools on one machine.
333
+
334
+ **Option A — CLI (quick)**
335
+
336
+ ```bash
337
+ claude mcp add searchconsole-mcp --transport stdio -- \
338
+ node /absolute/path/to/searchconsole-mcp/dist/server.js
339
+ ```
340
+
341
+ Check: `claude mcp list` (or `/mcp` in the Claude Code session).
342
+
343
+ **Option B — Project file (team-friendly)**
344
+
345
+ Add `.mcp.json` at the project root:
346
+
347
+ ```json
348
+ {
349
+ "mcpServers": {
350
+ "searchconsole-mcp": {
351
+ "command": "node",
352
+ "args": ["/absolute/path/to/searchconsole-mcp/dist/server.js"]
353
+ }
354
+ }
355
+ }
356
+ ```
357
+
358
+ User-wide servers can also live under `mcpServers` in `~/.claude.json` (see [Claude Code MCP docs](https://code.claude.com/docs/en/mcp)).
359
+
360
+ ---
361
+
362
+ ### 2. Cursor
363
+
364
+ [Cursor](https://cursor.com/) runs its **own** MCP host inside the IDE (separate config from Claude Code). You do **not** need the Claude Code app for Cursor, but if you use both, complete [Claude Code](#1-claude-code-set-up-first) setup and ADC first.
365
+
366
+ Edit **`~/.cursor/mcp.json`** (or MCP settings in the project):
367
+
368
+ ```json
369
+ {
370
+ "mcpServers": {
371
+ "searchconsole-mcp": {
372
+ "command": "node",
373
+ "args": ["/absolute/path/to/searchconsole-mcp/dist/server.js"],
374
+ "env": {}
375
+ }
376
+ }
377
+ }
378
+ ```
379
+
380
+ After `npm link -g`:
381
+
382
+ ```json
383
+ "command": "searchconsole-mcp",
384
+ "args": []
385
+ ```
386
+
387
+ Restart Cursor, then open **Settings → MCP** and confirm `searchconsole-mcp` is connected. The tools listed are served by the **local Node server**, not by Google directly.
388
+
389
+ ---
390
+
391
+ ### 3. GitHub Copilot (VS Code)
392
+
393
+ Copilot Chat in VS Code uses a different JSON shape: top-level **`servers`**, not `mcpServers`.
394
+
395
+ **Workspace** — `.vscode/mcp.json` (commit for your team):
396
+
397
+ ```json
398
+ {
399
+ "servers": {
400
+ "searchconsole-mcp": {
401
+ "type": "stdio",
402
+ "command": "node",
403
+ "args": ["/absolute/path/to/searchconsole-mcp/dist/server.js"]
404
+ }
405
+ }
406
+ }
407
+ ```
408
+
409
+ **User profile** — Command Palette → **MCP: Open User Configuration**.
410
+
411
+ Verify with **MCP: List Servers**. See [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) and [Copilot MCP guide](https://docs.github.com/en/copilot/customizing-copilot/using-model-context-protocol/extending-copilot-chat-with-mcp).
412
+
413
+ ---
414
+
415
+ ### 4. OpenAI Codex
416
+
417
+ [Codex](https://developers.openai.com/codex) (CLI and IDE extension) stores MCP servers in TOML.
418
+
419
+ **Option A — CLI**
420
+
421
+ ```bash
422
+ codex mcp add searchconsole-mcp -- \
423
+ node /absolute/path/to/searchconsole-mcp/dist/server.js
424
+ ```
425
+
426
+ **Option B — `~/.codex/config.toml`**
427
+
428
+ ```toml
429
+ [mcp_servers.searchconsole-mcp]
430
+ command = "node"
431
+ args = ["/absolute/path/to/searchconsole-mcp/dist/server.js"]
432
+ ```
433
+
434
+ Project-level: `.codex/config.toml` in a trusted project. In the Codex TUI, run `/mcp` to see active servers. Details: [Codex MCP](https://developers.openai.com/codex/mcp).
435
+
436
+ ---
437
+
438
+ ### 5. Claude Desktop
439
+
440
+ Add under `mcpServers` in Claude Desktop’s config file (path depends on OS):
441
+
442
+ ```json
443
+ {
444
+ "mcpServers": {
445
+ "searchconsole-mcp": {
446
+ "command": "node",
447
+ "args": ["/absolute/path/to/searchconsole-mcp/dist/server.js"]
448
+ }
449
+ }
450
+ }
451
+ ```
452
+
453
+ Restart Claude Desktop after saving.
454
+
455
+ ---
456
+
457
+ ### Other MCP clients
458
+
459
+ Any client that supports **stdio MCP** can use:
460
+
461
+ ```bash
462
+ node /absolute/path/to/searchconsole-mcp/dist/server.js
463
+ ```
464
+
465
+ Do not wrap the process in HTTP unless the client requires streamable HTTP and you accept the [security tradeoffs](#security).
466
+
467
+ ---
468
+
469
+ ## Tools reference
470
+
471
+ ### `gsc_mcp_server_ping`
472
+
473
+ Checks that **this MCP server** (the local Node.js process) is running. Returns `pong`.
474
+
475
+ Does **not** contact Google Search Console, your site, or Search Console’s APIs. Use the `gsc_*` tools for GSC data.
476
+
477
+ ---
478
+
479
+ ### `gsc_list_sites`
480
+
481
+ Lists Search Console properties the authenticated identity can access.
482
+
483
+ **Parameters:** none
484
+
485
+ **Returns:** JSON from the Search Console API (`siteEntry`, etc.)
486
+
487
+ ---
488
+
489
+ ### `gsc_search_analytics`
490
+
491
+ Queries search analytics: impressions, clicks, CTR, average position.
492
+
493
+ | Parameter | Required | Description |
494
+ |-----------|----------|-------------|
495
+ | `site_url` | yes | Property URL, e.g. `https://example.com/` or `sc-domain:example.com` |
496
+ | `start_date` | yes | `YYYY-MM-DD` |
497
+ | `end_date` | yes | `YYYY-MM-DD` |
498
+ | `dimensions` | no | Up to 5 of: `query`, `page`, `country`, `device`, `searchAppearance`, `date` |
499
+ | `type` | no | `web`, `image`, `video`, `news`, `discover`, `googleNews` |
500
+ | `row_limit` | no | 1–25000 (API max) |
501
+ | `start_row` | no | Pagination offset |
502
+ | `dimension_filter_groups` | no | Structured filters (bounded schema) |
503
+ | `aggregation_type` | no | `auto`, `byProperty`, `byPage` |
504
+ | `data_state` | no | `final` or `all` |
505
+
506
+ ---
507
+
508
+ ### `gsc_inspect_url`
509
+
510
+ Runs URL inspection (index status, crawl, mobile usability, rich results).
511
+
512
+ | Parameter | Required | Description |
513
+ |-----------|----------|-------------|
514
+ | `site_url` | yes | Property URL |
515
+ | `inspection_url` | yes | Full URL to inspect (must belong to the property) |
516
+ | `language_code` | no | Issue message language (default `en-US`) |
517
+
518
+ ---
519
+
520
+ ### `gsc_list_sitemaps`
521
+
522
+ Lists sitemaps submitted for a property.
523
+
524
+ | Parameter | Required | Description |
525
+ |-----------|----------|-------------|
526
+ | `site_url` | yes | Property URL |
527
+
528
+ ---
529
+
530
+ ## Example prompts for agents
531
+
532
+ Once the server is connected, you can ask your agent things like:
533
+
534
+ - *“List all Search Console properties I have access to.”*
535
+ - *“For `https://example.com/`, show top queries by clicks for the last 28 days.”*
536
+ - *“Pull search analytics for `sc-domain:example.com` with dimensions `date` and `query`, limit 50 rows.”*
537
+ - *“Inspect `https://example.com/pricing` in Search Console and summarize indexing status.”*
538
+ - *“What sitemaps are submitted for `https://example.com/`?”*
539
+
540
+ Tip: `site_url` must match GSC exactly (often a trailing slash on URL-prefix properties).
541
+
542
+ ---
543
+
544
+ ## Security
545
+
546
+ This server can access **your** Search Console data using **your** Google credentials. Treat it like any tool that runs API calls on your machine.
547
+
548
+ ### What we enforce in code
549
+
550
+ | Control | Detail |
551
+ |---------|--------|
552
+ | **Read-only OAuth** | Scope `https://www.googleapis.com/auth/webmasters.readonly` only |
553
+ | **No write tools** | No sitemap submit, no site add/remove |
554
+ | **Stdio default** | MCP clients spawn the process locally; no network listener unless you opt in |
555
+ | **Loopback HTTP bind** | `--host` defaults to `127.0.0.1` |
556
+ | **HTTP warnings** | Binding to `0.0.0.0` logs an explicit exposure warning |
557
+ | **Request limits** | HTTP POST body max **4 MB**; max **32** concurrent HTTP sessions |
558
+ | **Input validation** | Zod schemas on tool parameters |
559
+ | **Error redaction** | Tool and log messages strip home paths and noisy stack details |
560
+
561
+ ### What you should do
562
+
563
+ 1. **Prefer stdio** for Cursor and Claude Desktop (default).
564
+ 2. **Do not expose HTTP** to the public internet without extra auth (reverse proxy, VPN, mTLS, or shared secret). HTTP mode has **no MCP-layer authentication**.
565
+ 3. **Use least-privilege Google access** — only the GSC scope above unless you deliberately share ADC with other MCP servers.
566
+ 4. **Never commit** service account JSON or `.env` secrets (see `.gitignore`).
567
+ 5. **Review** [security_best_practices_report.md](security_best_practices_report.md) for the full audit and residual risks.
568
+
569
+ ### Threat model (short)
570
+
571
+ | Mode | Who can call tools? |
572
+ |------|---------------------|
573
+ | **stdio** | The MCP client process that started the server (your IDE / agent host) |
574
+ | **HTTP on `127.0.0.1`** | Processes on your machine that can open that port |
575
+ | **HTTP on `0.0.0.0`** | Anyone on the network that can reach the port |
576
+
577
+ ---
578
+
579
+ ## Transports: stdio vs HTTP
580
+
581
+ ```
582
+ ┌─────────────┐ stdin/stdout (JSON-RPC) ┌──────────────┐
583
+ │ MCP client │ ◄──────────────────────────────► │ searchconsole-mcp │
584
+ │ (Cursor) │ default: stdio │ + Google │
585
+ └─────────────┘ │ Search │
586
+ │ Console API│
587
+ ┌─────────────┐ HTTP POST/GET /mcp └──────────────┘
588
+ │ MCP client │ ◄──────────────────────────────► ▲
589
+ │ (optional) │ --transport http │
590
+ └─────────────┘ │
591
+ ADC /
592
+ service account
593
+ ```
594
+
595
+ ### Stdio (default, recommended)
596
+
597
+ ```bash
598
+ node dist/server.js
599
+ # or
600
+ searchconsole-mcp
601
+ ```
602
+
603
+ - Best for Cursor, Claude Desktop, and local agents
604
+ - Uses `stdio-guard` so logs go to stderr and JSON-RPC stays on stdout
605
+ - No port to firewall
606
+
607
+ ### HTTP (optional)
608
+
609
+ ```bash
610
+ node dist/server.js --transport http --host 127.0.0.1 --port 3000
611
+ ```
612
+
613
+ Endpoint: `http://<host>:<port>/mcp` (streamable HTTP transport per MCP SDK).
614
+
615
+ Use HTTP only when your client requires it and you understand the [security](#security) implications.
616
+
617
+ ---
618
+
619
+ ## Configuration
620
+
621
+ ### CLI
622
+
623
+ ```
624
+ searchconsole-mcp [--transport stdio|http] [--host <addr>] [--port <n>] [--version] [--help]
625
+ ```
626
+
627
+ | Flag / variable | Default | Description |
628
+ |-----------------|---------|-------------|
629
+ | `--transport` / `GSC_MCP_TRANSPORT` | `stdio` | `stdio` or `http` |
630
+ | `--host` / `GSC_MCP_HOST` | `127.0.0.1` | HTTP bind address |
631
+ | `--port` / `GSC_MCP_PORT` | `3000` | HTTP port |
632
+ | `GOOGLE_APPLICATION_CREDENTIALS` | — | Path to service account JSON |
633
+
634
+ ### Smithery
635
+
636
+ [Smithery](https://smithery.ai/) is a registry for discovering and installing MCP servers in compatible clients. [smithery.yaml](smithery.yaml) tells Smithery to run this server over stdio via `npx -y @vmandic/searchconsole-mcp`.
637
+
638
+ ---
639
+
640
+ ## Troubleshooting
641
+
642
+ | Symptom | What to try |
643
+ |---------|-------------|
644
+ | **Authentication failed** | Run `gcloud auth application-default login` with `webmasters.readonly`. Enable [Search Console API](#google-cloud-project-required) on a Cloud project; set [quota project](#google-cloud-project-required). Confirm GSC property access. |
645
+ | **API has not been used / disabled / access not configured** | Enable `searchconsole.googleapis.com` on your GCP project. Wait a few minutes after enabling. |
646
+ | **Permission denied** | Property not shared with your account or service account. |
647
+ | **Site or resource not found** | Fix `site_url` (trailing slash, `https://` vs `sc-domain:`). |
648
+ | **Tools missing in Cursor** | Restart Cursor; check MCP logs; verify absolute path to `dist/server.js` and that you ran `npm run build`. |
649
+ | **Server exits immediately** | Run `node dist/server.js` in a terminal: stdio servers wait for input; that is normal. |
650
+ | **Insufficient authentication scopes** | Re-run ADC login including `webmasters.readonly`. |
651
+ | **Quota exceeded** | Wait and retry; reduce `row_limit` or query frequency. |
652
+
653
+ Enable integration tests against the live API:
654
+
655
+ ```bash
656
+ gcloud auth application-default login \
657
+ --scopes=https://www.googleapis.com/auth/webmasters.readonly
658
+
659
+ GSC_INTEGRATION=1 \
660
+ GSC_SITE_URL="https://your-site.example/" \
661
+ npm run test:integration
662
+ ```
663
+
664
+ ---
665
+
666
+ ## Development
667
+
668
+ Every push and pull request to `main` runs [CI](.github/workflows/ci.yml): `npm ci`, `npm test`, production build, CLI smoke checks, and `npm audit` (high+) on Node 18, 20, and 22. Integration tests are local only (they need your Google ADC).
669
+
670
+ ```bash
671
+ npm run typecheck # TypeScript check (src)
672
+ npm test # Unit tests (mocked GSC client)
673
+ npm run build # dist/server.js
674
+ npm run build:prod # Minified production bundle
675
+ npm run test:integration # Live API (requires ADC + GSC_INTEGRATION=1)
676
+ ```
677
+
678
+ Project layout:
679
+
680
+ ```
681
+ src/
682
+ server.ts # Entry, transport selection
683
+ cli.ts # Args and help
684
+ stdio-guard.ts # MCP-safe stdout
685
+ http-transport.ts # Optional HTTP MCP
686
+ http-body.ts # Bounded POST reader
687
+ tools/ # GSC tool implementations + Zod schemas
688
+ test/ # Node test runner suites
689
+ ```
690
+
691
+ Contributions welcome via [issues](https://github.com/vmandic/searchconsole-mcp/issues) and pull requests.
692
+
693
+ ---
694
+
695
+ ## License
696
+
697
+ [MIT](LICENSE) — Copyright (c) Vedran Mandić and contributors.
package/dist/server.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ var ut=Object.defineProperty;var u=(t,e)=>()=>(t&&(e=t(t=0)),e);var dt=(t,e)=>{for(var o in e)ut(t,o,{get:e[o],enumerable:!0})};var F,A,R,$,N,G=u(()=>{"use strict";F="searchconsole-mcp",A="https://www.googleapis.com/auth/webmasters.readonly",R=A,$="https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform,"+A,N="1.0.0"});function mt(t){if(!(t instanceof Error))return;let e=t,o=e.response?.data?.error?.status;if(typeof o=="string")return o;if(typeof e.code=="string")return e.code}function b(t){if(!(t instanceof Error))return"An unexpected error occurred.";let e=mt(t),o=t.message;return e==="UNAUTHENTICATED"||o.includes("UNAUTHENTICATED")||o.includes("Could not load the default credentials")||o.includes("insufficient authentication scopes")?`Authentication failed. Run: gcloud auth application-default login --scopes=${R}`:e==="PERMISSION_DENIED"||o.includes("PERMISSION_DENIED")||o.includes("Forbidden")?"Permission denied. Ensure your Google account has access to this Search Console property.":e==="NOT_FOUND"||o.includes("NOT_FOUND")?'Site or resource not found. Check site_url matches GSC (e.g. "https://example.com/" with trailing slash).':e==="RESOURCE_EXHAUSTED"||o.includes("RESOURCE_EXHAUSTED")||o.includes("quota")?"API quota exceeded. Please wait a moment and try again.":e==="INVALID_ARGUMENT"||o.includes("INVALID_ARGUMENT")?"Invalid request parameters. Check site_url, dates, and dimension names.":o.replace(/projects\/[^\s/]+/g,"projects/***").replace(/\/home\/[^\s/]+/g,"/home/***").replace(/\/Users\/[^\s/]+/g,"/Users/***").replace(/at\s+.+\(.+:\d+:\d+\)/g,"").trim()||"An unexpected error occurred."}function S(t){return b(t)}var O=u(()=>{"use strict";G()});import{searchconsole as St}from"@googleapis/searchconsole";function g(t){return K||(M||(M=St({version:"v1",auth:t})),M)}var M,K,x=u(()=>{"use strict";M=null,K=null});async function Q(t,e){return(await g(t).searchanalytics.query({siteUrl:e.site_url,requestBody:{startDate:e.start_date,endDate:e.end_date,dimensions:e.dimensions,type:e.type,rowLimit:e.row_limit,startRow:e.start_row,dimensionFilterGroups:e.dimension_filter_groups,aggregationType:e.aggregation_type,dataState:e.data_state}})).data}var Z=u(()=>{"use strict";x()});async function tt(t,e){return(await g(t).urlInspection.index.inspect({requestBody:{siteUrl:e.site_url,inspectionUrl:e.inspection_url,languageCode:e.language_code??"en-US"}})).data}var et=u(()=>{"use strict";x()});async function ot(t,e){return(await g(t).sitemaps.list({siteUrl:e})).data}var rt=u(()=>{"use strict";x()});async function st(t){return(await g(t).sites.list({})).data}var nt=u(()=>{"use strict";x()});import{z as s}from"zod";var it,k,yt,wt,Tt,xt,p,E,U,at=u(()=>{"use strict";it=s.string().regex(/^\d{4}-\d{2}-\d{2}$/,"Expected YYYY-MM-DD"),k=s.string().min(1).max(2048).refine(t=>t.startsWith("sc-domain:")||/^https?:\/\//i.test(t),{message:"site_url must start with https:// or sc-domain:"}),yt=s.enum(["query","page","country","device","searchAppearance","date"]),wt=s.enum(["web","image","video","news","discover","googleNews"]),Tt=s.object({dimension:s.string().min(1).max(64),operator:s.string().min(1).max(32),expression:s.string().min(1).max(512)}),xt=s.object({groupType:s.string().max(32).optional(),filters:s.array(Tt).max(20).optional()}),p=s.object({site_url:k,start_date:it,end_date:it,dimensions:s.array(yt).max(5).optional(),type:wt.optional(),row_limit:s.number().int().min(1).max(25e3).optional(),start_row:s.number().int().min(0).max(24999).optional(),dimension_filter_groups:s.array(xt).max(5).optional(),aggregation_type:s.enum(["auto","byProperty","byPage"]).optional(),data_state:s.enum(["final","all"]).optional()}),E=s.object({site_url:k,inspection_url:s.string().url().max(2048),language_code:s.string().min(2).max(16).optional()}),U=s.object({site_url:k})});var ct={};dt(ct,{registerGscTools:()=>Ct});function I(t){return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}function Et(t){return{content:[{type:"text",text:t}],isError:!0}}function D(t,e){return async o=>{let r=t.safeParse(o);if(!r.success)return Et(`Invalid request parameters. ${JSON.stringify(r.error.flatten(),null,2)}`);try{return await e(r.data)}catch(i){return{content:[{type:"text",text:b(i)}],isError:!0}}}}function Ct(t,e){t.tool("gsc_mcp_server_ping","Liveness check for this MCP server process (local Node.js). Returns pong. Does not call Google Search Console.",{},async()=>({content:[{type:"text",text:"pong"}]})),t.tool("gsc_list_sites","Lists all sites (properties) the authenticated user has access to in Google Search Console.",{},async()=>{try{return I(await st(e))}catch(o){return{content:[{type:"text",text:b(o)}],isError:!0}}}),t.tool("gsc_search_analytics","Queries Google Search Console search analytics data \u2014 impressions, clicks, CTR, and position for queries, pages, countries, and devices.",{site_url:p.shape.site_url,start_date:p.shape.start_date,end_date:p.shape.end_date,dimensions:p.shape.dimensions,type:p.shape.type,row_limit:p.shape.row_limit,start_row:p.shape.start_row,dimension_filter_groups:p.shape.dimension_filter_groups,aggregation_type:p.shape.aggregation_type,data_state:p.shape.data_state},D(p,async o=>I(await Q(e,o)))),t.tool("gsc_inspect_url","Inspects a URL in Google Search Console \u2014 index status, crawl info, mobile usability, and rich results.",{site_url:E.shape.site_url,inspection_url:E.shape.inspection_url,language_code:E.shape.language_code},D(E,async o=>I(await tt(e,o)))),t.tool("gsc_list_sitemaps","Lists all sitemaps submitted for a site in Google Search Console.",{site_url:U.shape.site_url},D(U,async({site_url:o})=>I(await ot(e,o))))}var pt=u(()=>{"use strict";O();Z();et();rt();nt();at()});function V(){let t=process.stdout.write.bind(process.stdout),e=process.stderr.write.bind(process.stderr);return console.log=(...o)=>{e(Buffer.from(o.join(" ")+`
3
+ `))},console.info=console.log,console.debug=console.log,console.warn=(...o)=>{e(Buffer.from("[WARN] "+o.join(" ")+`
4
+ `))},process.stdout.write=((o,r,i)=>(typeof o=="string"?o:o?.toString?.()??"").includes('"jsonrpc"')?t(o,r,i):e(o,r,i)),{writeStdout:t,writeStderr:e}}G();var _="127.0.0.1";var Y=["stdio","http"];function H(t,e,o){let r=t.indexOf(e);if(r!==-1&&t[r+1])return t[r+1];if(o)return process.env[o]}function J(t){t(`searchconsole-mcp \u2014 Google Search Console MCP server (read-only)
5
+
6
+ Usage: searchconsole-mcp [options]
7
+
8
+ Options:
9
+ --transport <type> Transport: stdio (default) or http
10
+ --port <number> HTTP port when using --transport http (default: 3000)
11
+ --host <address> HTTP bind address (default: 127.0.0.1). Use 0.0.0.0 only on trusted networks.
12
+ --version Show version and exit
13
+ --help Show this help and exit
14
+
15
+ Environment:
16
+ GOOGLE_APPLICATION_CREDENTIALS Path to service account JSON key
17
+ GSC_MCP_TRANSPORT Same as --transport
18
+ GSC_MCP_PORT Same as --port
19
+ GSC_MCP_HOST Same as --host
20
+
21
+ Auth (Application Default Credentials):
22
+ gcloud auth application-default login --scopes=${R}
23
+ (If you also use Analytics MCP: --scopes=${$})
24
+
25
+ HTTP security:
26
+ HTTP mode exposes your Google credentials to anyone who can reach the bind address.
27
+ Default bind is loopback (127.0.0.1). Do not use 0.0.0.0 on untrusted networks.
28
+
29
+ Examples:
30
+ npx -y @vmandic/searchconsole-mcp
31
+ node dist/server.js --transport http --port 3000
32
+ `)}function q(t){return"error"in t}function W(t){if(t.includes("--help")||t.includes("-h"))return{transport:"stdio",host:_,port:3e3,showHelp:!0,showVersion:!1};if(t.includes("--version")||t.includes("-v"))return{transport:"stdio",host:_,port:3e3,showHelp:!1,showVersion:!0};let e=H(t,"--transport","GSC_MCP_TRANSPORT")??"stdio";if(!Y.includes(e))return{error:`Unknown transport: ${e}. Valid: ${Y.join(", ")}`};let o=H(t,"--port","GSC_MCP_PORT")??"3000",r=parseInt(o,10);if(Number.isNaN(r)||r<1||r>65535)return{error:`Invalid port: ${o}`};let i=H(t,"--host","GSC_MCP_HOST")??_;return!i||i.includes("/")||i.includes(" ")?{error:`Invalid host: ${i}`}:{transport:e,host:i,port:r,showHelp:!1,showVersion:!1}}G();async function X(t,e){let o=[],r=0;for await(let i of t){let d=i;if(r+=d.length,r>e)return{ok:!1,status:413,jsonRpcMessage:"Payload too large"};o.push(d)}try{return{ok:!0,body:JSON.parse(Buffer.concat(o).toString("utf8"))}}catch{return{ok:!1,status:400,jsonRpcMessage:"Parse error"}}}O();function T(t,e,o){return{status:t,body:JSON.stringify({jsonrpc:"2.0",error:{code:e,message:o},id:null})}}function ht(t){t.setHeader("X-Content-Type-Options","nosniff")}function _t(t){return t==="0.0.0.0"||t==="::"||t==="[::]"}async function z(t,e){let o=t.host??_,r=t.port,i=await import("@modelcontextprotocol/sdk/server/streamableHttp.js"),d=await import("node:http"),{randomUUID:P}=await import("node:crypto"),{isInitializeRequest:v}=await import("@modelcontextprotocol/sdk/types.js"),a={},y=d.createServer(async(l,n)=>{if(ht(n),new URL(l.url??"/",`http://${o}:${r}`).pathname!=="/mcp"){n.writeHead(404,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Not found. Use /mcp"}));return}let f=l.headers["mcp-session-id"];if(l.method==="POST"){let w=await X(l,4194304);if(!w.ok){let c=w.status===413?T(413,-32e3,w.jsonRpcMessage):T(400,-32700,w.jsonRpcMessage);n.writeHead(c.status,{"Content-Type":"application/json"}),n.end(c.body);return}let B=w.body;try{let c;if(f&&a[f])c=a[f];else if(!f&&v(B)){if(Object.keys(a).length>=32){let m=T(503,-32e3,"Too many active sessions");n.writeHead(m.status,{"Content-Type":"application/json"}),n.end(m.body);return}c=new i.StreamableHTTPServerTransport({sessionIdGenerator:()=>P(),onsessioninitialized:m=>{a[m]=c}}),c.onclose=()=>{let m=c.sessionId;m&&a[m]&&delete a[m]},await e().connect(c)}else{let h=T(400,-32e3,"Bad Request: No valid session ID provided");n.writeHead(h.status,{"Content-Type":"application/json"}),n.end(h.body);return}await c.handleRequest(l,n,B)}catch(c){if(console.error("[searchconsole-mcp] Error handling MCP request:",S(c)),!n.headersSent){let h=T(500,-32603,"Internal server error");n.writeHead(h.status,{"Content-Type":"application/json"}),n.end(h.body)}}return}if(l.method==="GET"||l.method==="DELETE"){if(!f||!a[f]){n.writeHead(400,{"Content-Type":"text/plain"}),n.end("Invalid or missing session ID");return}await a[f].handleRequest(l,n);return}n.writeHead(405,{"Content-Type":"text/plain"}),n.end("Method not allowed")});y.listen(r,o,()=>{console.error(`[searchconsole-mcp] Streamable HTTP server listening on http://${o}:${r}/mcp`),_t(o)&&console.error("[searchconsole-mcp] WARNING: HTTP is bound to all interfaces. Anyone on the network can use your Google credentials via MCP.")});let j=async()=>{console.error("[searchconsole-mcp] Shutting down HTTP server...");for(let l of Object.keys(a)){try{await a[l].close()}catch{}delete a[l]}y.close(),process.exit(0)};process.on("SIGTERM",j),process.on("SIGINT",j)}O();var{writeStdout:lt,writeStderr:Pt}=V(),At=process.argv.slice(2),L=W(At);q(L)&&(Pt(Buffer.from(`[searchconsole-mcp] Error: ${L.error}
33
+ `)),process.exit(1));var C=L;C.showHelp&&(J(t=>lt(Buffer.from(t))),process.exit(0));C.showVersion&&(lt(Buffer.from(N+`
34
+ `)),process.exit(0));async function Rt(){let t=await import("@modelcontextprotocol/sdk/server/mcp.js"),e=await import("@modelcontextprotocol/sdk/server/stdio.js"),o=await import("google-auth-library"),r=await Promise.resolve().then(()=>(pt(),ct)),i=new o.GoogleAuth({scopes:[A]});function d(){let y=new t.McpServer({name:F,version:N});return r.registerGscTools(y,i),y}if(C.transport==="http"){await z({host:C.host,port:C.port},d);return}let P=d(),v=new e.StdioServerTransport;await P.connect(v),console.error("[searchconsole-mcp] Server started, waiting for connections...");let a=()=>{console.error("[searchconsole-mcp] Shutting down..."),P.close().then(()=>process.exit(0))};process.on("SIGTERM",a),process.on("SIGINT",a)}process.on("uncaughtException",t=>{console.error("[searchconsole-mcp] Uncaught exception:",S(t))});process.on("unhandledRejection",t=>{console.error("[searchconsole-mcp] Unhandled rejection:",S(t))});Rt().catch(t=>{console.error("[searchconsole-mcp] Fatal error:",S(t)),process.exit(1)});
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@vmandic/searchconsole-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Read-only Google Search Console MCP server for Cursor and other MCP clients.",
5
+ "type": "module",
6
+ "bin": {
7
+ "searchconsole-mcp": "dist/server.js"
8
+ },
9
+ "files": [
10
+ "dist/server.js",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "build": "node esbuild.config.mjs",
16
+ "build:prod": "NODE_ENV=production node esbuild.config.mjs",
17
+ "typecheck": "tsc --noEmit",
18
+ "test": "npm run typecheck && tsc --noEmit -p tsconfig.test.json && node --import tsx --test test/*.test.ts",
19
+ "test:integration": "npm run typecheck && tsc --noEmit -p tsconfig.test.json && GSC_INTEGRATION=1 node --import tsx --test test/integration.test.ts",
20
+ "prepublishOnly": "npm run build:prod"
21
+ },
22
+ "keywords": [
23
+ "mcp",
24
+ "model-context-protocol",
25
+ "google-search-console",
26
+ "gsc",
27
+ "seo",
28
+ "cursor",
29
+ "stdio"
30
+ ],
31
+ "author": {
32
+ "name": "Vedran Mandić",
33
+ "url": "https://github.com/vmandic"
34
+ },
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/vmandic/searchconsole-mcp.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/vmandic/searchconsole-mcp/issues"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "dependencies": {
50
+ "@googleapis/searchconsole": "^6.0.1",
51
+ "@modelcontextprotocol/sdk": "^1.27.1",
52
+ "google-auth-library": "^10.6.2",
53
+ "zod": "^3.25.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^20.0.0",
57
+ "esbuild": "^0.25.0",
58
+ "tsx": "^4.19.0",
59
+ "typescript": "^5.7.0"
60
+ }
61
+ }