@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 +22 -0
- package/README.md +697 -0
- package/dist/server.js +34 -0
- package/package.json +61 -0
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
|
+
[](https://github.com/vmandic/searchconsole-mcp/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](package.json)
|
|
6
|
+
[](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
|
+
}
|