A Google Search Console MCP server gives an AI agent controlled access to the search performance and indexing data for sites you manage. The agent can investigate real clicks, impressions, queries, pages, countries, devices, sitemaps, and URL indexing evidence instead of relying on a CSV export or generic SEO advice.
You can build this integration yourself. The individual pieces are well documented, but a useful remote server combines Google Search Console setup, a Google Cloud project, a Google OAuth app, the Search Console API, an MCP server, a second OAuth boundary for MCP clients, secure token storage, deployment, and ongoing maintenance.
For an experienced developer with an already verified Search Console property, a minimal private version can take a couple of focused hours. A first implementation can easily occupy half a day. DNS propagation, debugging OAuth redirects, or preparing a public Google OAuth app can extend the elapsed time to several days.
Want the result without building the infrastructure? Connect SearchConsole.ai for free. It provides a hosted, read-only Google Search Console MCP server and handles the Google and MCP authentication flows for you.
This guide explains both paths so you can decide whether owning the infrastructure is worth the setup and maintenance.
What you are actually building
There are two separate integrations, and therefore two separate trust boundaries:
- Your AI client connects and authenticates to the remote MCP server.
- The MCP server uses your Google authorization to call the Search Console API.
The complete request path looks like this:
ChatGPT, Claude Code, Cursor, or Codex
|
MCP authorization
|
Remote MCP server over HTTPS
|
Encrypted Google OAuth token storage
|
Google Search Console API
|
Structured, read-only results
The protocol implementation is only one part of the system. You still have to create the Google Cloud resources, configure consent, protect OAuth credentials, store refresh tokens, deploy a secure endpoint, and operate the service.
How long does the DIY route take?
The following estimates are practical planning ranges, not guarantees from Google:
| Starting point | Reasonable planning estimate |
|---|---|
| Experienced developer, verified property, private local prototype | About 2–3 focused hours |
| First Google OAuth and MCP implementation | Half a day or more |
| Production-ready hosted service | One or more working days, plus review time |
| New Domain property requiring DNS verification | DNS changes can take up to 2–3 days to propagate |
| Public OAuth app requiring Google review | Brand review is often measured in business days; scope review can take longer |
The coding is only one part. Most delays occur at system boundaries: an incorrect property identifier, an exact-match redirect URI failure, a missing refresh token, a client that implements remote MCP differently, or a Google OAuth app that is still in testing.
Step 1: create and verify a Search Console property
The Google account used in the OAuth flow must already have access to a Search Console property. If the site is not in Search Console yet, add a property and choose one of two property types:
- A Domain property covers every protocol and subdomain, such as
example.com,www.example.com, andhttps://shop.example.com. It requires DNS verification. - A URL-prefix property covers only the exact prefix you enter, such as
https://www.example.com/. It can be verified with an HTML file, an HTML tag, Google Analytics, Google Tag Manager, or DNS.
Domain properties are usually more complete, but DNS access is required. Add the TXT record at your DNS provider and wait for Google to see it. Google notes that DNS changes can take up to two or three days to propagate. Keep the verification record in place after verification or you can lose verified-owner status.
This distinction remains visible in the API. A Domain property is identified as sc-domain:example.com, while a URL-prefix property uses the complete URL. Your MCP tools should list the available properties first and reuse the exact identifier returned by Google rather than reconstructing it.
Step 2: create a Google Cloud project
Open the Google Cloud project selector and create a project for the integration. A Cloud project is the container for the enabled API, OAuth app, client credentials, quotas, and policy configuration.
Before continuing, confirm:
- the project belongs to the correct Google Cloud organization;
- the appropriate team members have access;
- the project name clearly distinguishes development from production;
- production credentials will not be reused in a local test application.
Google recommends separate projects for testing and production when an OAuth app will be made public. That prevents test users, redirect URIs, and experimental credentials from leaking into the production configuration.
Step 3: enable the Search Console API
In the project's API Library, find Google Search Console API and enable it. Enabling a similarly named Google API is not enough; the OAuth client and Search Console API must belong to the project you are actually using.
The API provides four relevant groups of operations in its official reference:
- Sites lists the properties the authorized user can access.
- Search Analytics returns clicks, impressions, CTR, and average position by supported dimensions.
- URL Inspection returns Google's indexed-version evidence for a URL.
- Sitemaps lists and inspects submitted sitemaps.
The API also exposes write operations such as adding properties or submitting and deleting sitemaps. If your server is intended for analysis, do not expose those operations. A narrow, read-only surface is easier to trust and safer to operate.
Step 4: configure the Google OAuth app
Search Console contains private user data, so every API request that accesses it must use OAuth 2.0. A service account is not a shortcut unless someone explicitly adds that service account as a user on every Search Console property.
In Google Auth Platform, configure four areas.
Branding
Provide the application name, support email, developer contact, homepage, privacy policy, and terms where applicable. For a public app, these values must accurately describe the service and use a domain you control.
Audience
Choose Internal only when the app is restricted to users in your Google Workspace organization. Otherwise choose External.
An external app begins in testing. You must add each Google account as a test user. Testing is limited to 100 users, displays a testing warning, and test authorizations generally expire after seven days. That is workable for development but unsuitable for a dependable public integration.
Data access
Request the narrowest Search Console scope that supports the product:
https://www.googleapis.com/auth/webmasters.readonly
The broader https://www.googleapis.com/auth/webmasters scope permits read/write access and is unnecessary for an analysis-only MCP server.
Google Auth Platform shows the classification of every requested scope. A public app that accesses user data may need the corresponding verification. Depending on the classification and app configuration, Google can require a scope justification, demonstration video, verified domain, privacy policy, or additional review. Brand verification commonly takes two or three business days when manual review is needed; sensitive-scope verification can take longer.
OAuth client
Create an OAuth client of type Web application and add every authorized redirect URI. Google requires an exact match, including scheme, host, path, capitalization, and trailing slash. A mismatch produces the familiar redirect_uri_mismatch error.
Use HTTPS for deployed callbacks. Google permits plain HTTP only for localhost development. Download the credentials, store the client ID and client secret in a secret manager or environment configuration, and never commit the secret to the repository.
Step 5: implement Google's authorization-code flow
Your application now needs multiple routes and a persistent token lifecycle:
- An authorization-start route creates a cryptographically random
statevalue, stores it against the user's session, and redirects to Google. - The Google request includes the read-only Search Console scope and
access_type=offlineso the application can obtain a refresh token. - A callback route checks
state, handles denial and error responses, and exchanges the one-time authorization code for tokens. - The application encrypts and stores the refresh token against the correct account.
- Before API calls, it refreshes expired access tokens and handles revoked or invalid grants by asking the user to reconnect.
- A disconnect or account-deletion flow revokes authorization where appropriate and removes stored credentials.
The Google OAuth guide for server-side web applications is explicit about CSRF state, exact redirects, offline access, and secure client-secret storage.
Refresh-token handling is a common source of bugs. Google may return a refresh token only on the first authorization. Re-running the flow without the correct consent behavior does not guarantee a new one. Tokens can also stop working because the user revoked access, changed security settings, exceeded token limits, or remained in an expiring test configuration.
At this point, you have authenticated to Google, but you have not built an MCP server yet.
Step 6: build a reliable Search Console API layer
Start with the Sites resource and return the exact properties and permission levels available to the authorized account. Do not ask the user or model to guess whether the property uses sc-domain: or a URL prefix.
Then wrap only the read operations the agent needs.
Search performance
searchanalytics.query accepts a date range, dimensions, filters, search type, aggregation settings, and pagination. Useful dimensions include query, page, country, device, date, and supported search appearance values.
The wrapper must account for several documented constraints:
- The default response is 1,000 rows;
rowLimitcan be set from 1 to 25,000. - Pagination uses
startRow. Continue until a page contains fewer rows than requested. - The API returns top rows rather than guaranteeing every possible row.
- Google documents a maximum export of 50,000 rows per day per search type through this paging method.
- Recent data may be preliminary, and privacy filtering can omit anonymized queries.
- Totals grouped by page or query may not reconcile exactly with the ungrouped total.
A production wrapper should validate dates and dimensions, paginate deliberately, cap output so the AI client's context is not flooded, and label partial or preliminary results. It should also compare equivalent windows rather than mixing incomplete recent days with stable historical data.
URL inspection
The URL Inspection API can return last crawl time, crawl permission, indexing state, robots information, Google-selected and user-declared canonicals, and other indexed-version evidence. It does not run the live inspection test available in Search Console's web interface. Tool descriptions and model prompts should preserve that distinction.
Sitemaps
For a read-only server, list submitted sitemaps and return their processing status, last submission/download dates, warnings, errors, and discovered URL counts where available. Do not expose submit or delete operations merely because the underlying API supports them.
Quotas and caching
The Search Console API usage limits include both request-rate and load quotas. Search Analytics currently documents 1,200 queries per minute per site and per user, plus project-level quotas. URL Inspection is much tighter at 2,000 requests per day and 600 per minute per site.
Long date ranges and repeated queries increase load. Cache stable results, deduplicate identical requests, rate-limit users, and return a useful retry message instead of forwarding a raw Google error.
Step 7: expose the data as MCP tools
A remote MCP implementation should use Streamable HTTP over HTTPS and define small, explicit, typed tools. A practical read-only surface might include:
list_sites()
search_analytics(site_url, start_date, end_date, dimensions, filters, search_type)
inspect_url(site_url, inspection_url)
list_sitemaps(site_url)
Each schema should reject unknown dimensions, invalid dates, oversized requests, and properties the current user cannot access. Tool results should distinguish:
- facts returned by Google;
- limitations or partial coverage;
- calculations made by your server;
- hypotheses that an AI agent may investigate next.
Keep responses bounded. Returning 25,000 rows to a language model is usually slower, more expensive, and less useful than returning an aggregated answer plus a controlled set of opportunity rows.
You also need consistent error translation. The AI client should be able to tell the difference between an expired Google grant, insufficient property permissions, a bad property identifier, a quota error, and an internal server failure.
Local Python MCP server example
Before building the remote OAuth and deployment layers, you can prove the protocol locally with a stdio server. The following educational prototype uses only Python's standard library. It exposes two read-only tools: one lists accessible Search Console properties and the other queries a small performance window.
Create local_gsc_mcp.py:
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
PROTOCOL_VERSION = "2025-11-25"
TOKEN_ENV = "GOOGLE_SEARCH_CONSOLE_ACCESS_TOKEN"
def google_request(url, method="GET", payload=None):
token = os.environ.get(TOKEN_ENV)
if not token:
raise RuntimeError(f"Set {TOKEN_ENV} before starting the server")
body = None if payload is None else json.dumps(payload).encode("utf-8")
request = urllib.request.Request(
url,
data=body,
method=method,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as error:
detail = error.read().decode("utf-8", errors="replace")
raise RuntimeError(f"Google API returned {error.code}: {detail}") from error
def list_sites():
return google_request("https://www.googleapis.com/webmasters/v3/sites")
def search_analytics(arguments):
required = ("site_url", "start_date", "end_date")
missing = [name for name in required if not arguments.get(name)]
if missing:
raise ValueError(f"Missing required arguments: {', '.join(missing)}")
site_url = urllib.parse.quote(arguments["site_url"], safe="")
url = (
"https://www.googleapis.com/webmasters/v3/sites/"
f"{site_url}/searchAnalytics/query"
)
row_limit = min(max(int(arguments.get("row_limit", 25)), 1), 100)
payload = {
"startDate": arguments["start_date"],
"endDate": arguments["end_date"],
"dimensions": arguments.get("dimensions", ["query"]),
"rowLimit": row_limit,
"dataState": "final",
}
return google_request(url, method="POST", payload=payload)
TOOLS = [
{
"name": "gsc_list_sites",
"description": "List Search Console properties available to the user.",
"inputSchema": {"type": "object", "properties": {}},
},
{
"name": "gsc_search_analytics",
"description": "Query final Search Console performance data.",
"inputSchema": {
"type": "object",
"properties": {
"site_url": {"type": "string"},
"start_date": {"type": "string", "format": "date"},
"end_date": {"type": "string", "format": "date"},
"dimensions": {
"type": "array",
"items": {
"type": "string",
"enum": ["query", "page", "country", "device", "date"],
},
},
"row_limit": {"type": "integer", "minimum": 1, "maximum": 100},
},
"required": ["site_url", "start_date", "end_date"],
"additionalProperties": False,
},
},
]
def tool_result(value, is_error=False):
return {
"content": [
{
"type": "text",
"text": value if isinstance(value, str) else json.dumps(value),
}
],
"isError": is_error,
}
def handle(request):
method = request.get("method")
request_id = request.get("id")
if method == "initialize":
result = {
"protocolVersion": PROTOCOL_VERSION,
"capabilities": {"tools": {"listChanged": False}},
"serverInfo": {"name": "local-gsc", "version": "0.1.0"},
}
elif method == "ping":
result = {}
elif method == "tools/list":
result = {"tools": TOOLS}
elif method == "tools/call":
params = request.get("params", {})
try:
if params.get("name") == "gsc_list_sites":
result = tool_result(list_sites())
elif params.get("name") == "gsc_search_analytics":
result = tool_result(search_analytics(params.get("arguments", {})))
else:
result = tool_result("Unknown tool", is_error=True)
except (RuntimeError, ValueError) as error:
result = tool_result(str(error), is_error=True)
elif request_id is None:
return None
else:
return {
"jsonrpc": "2.0",
"id": request_id,
"error": {"code": -32601, "message": f"Unknown method: {method}"},
}
if request_id is None:
return None
return {"jsonrpc": "2.0", "id": request_id, "result": result}
for line in sys.stdin:
try:
incoming = json.loads(line)
outgoing = handle(incoming)
if outgoing is not None:
sys.stdout.write(json.dumps(outgoing) + "\n")
sys.stdout.flush()
except Exception as error:
sys.stderr.write(f"Local MCP error: {error}\n")
sys.stderr.flush()
This prototype deliberately accepts a temporary Google access token through the process environment. To start it from a terminal:
export GOOGLE_SEARCH_CONSOLE_ACCESS_TOKEN="replace-with-a-temporary-access-token"
python3 /absolute/path/local_gsc_mcp.py
The process appears to wait because stdio MCP servers read JSON-RPC messages from standard input. Normally the AI client starts the process and communicates over those streams. A generic local MCP configuration looks like this:
{
"mcpServers": {
"local-gsc": {
"command": "python3",
"args": ["/absolute/path/local_gsc_mcp.py"]
}
}
}
Launch the AI client from the same environment so the child process inherits the token. Do not paste a real token into a version-controlled configuration file.
You can also smoke-test initialization without an AI client:
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"manual-test","version":"0.1.0"}}}' | python3 /absolute/path/local_gsc_mcp.py
The response should include the server name and its tools capability. If the client connects but the Google calls fail, check that the access token was issued for the read-only Search Console scope and that the account can access at least one verified property.
Why this is still only a prototype
The temporary access token expires, the example does not refresh it, and a local stdio process is available only on the machine where it runs. It also omits complete schema validation, pagination, filters, retries, caching, audit logs, encrypted credential storage, and user isolation.
Turning this example into a dependable integration means implementing the Google authorization-code and refresh-token lifecycle described above. Turning it into a remote server additionally requires Streamable HTTP, HTTPS deployment, and the MCP authorization layer described next. That is where a quick local demonstration becomes a multi-hour production project.
Step 8: add authorization for the remote MCP server
Google OAuth protects the connection from your service to Google. It does not automatically authenticate ChatGPT, Claude Code, Cursor, Codex, or another client to your MCP endpoint.
For a protected remote server, the current MCP authorization specification follows OAuth conventions and requires several cooperating pieces:
- Protected Resource Metadata so a client can discover how the MCP resource is protected.
- Authorization Server Metadata so the client can locate authorization and token endpoints.
- client registration or supported client metadata behavior;
- authorization code flow with PKCE, normally S256;
- resource indicators and access-token audience validation;
- short-lived access tokens, refresh behavior, revocation, and expiry handling.
Your server must map the authenticated MCP identity to the correct encrypted Google authorization. It must never accept a user-supplied account ID and blindly load another user's tokens.
Client compatibility requires testing. Remote MCP support and authorization UX vary between clients, so a flow that works in one client may expose a discovery, callback, or registration problem in another.
Step 9: deploy and operate it safely
Before exposing the server to anyone else, add the operational controls that a local prototype can avoid:
- HTTPS for all public endpoints and OAuth callbacks;
- encryption for Google refresh tokens at rest;
- secret management and credential rotation;
- CSRF protection and secure sessions for browser callbacks;
- access-token validation, rate limiting, and per-user authorization checks;
- redacted logs that do not record OAuth tokens or sensitive query and page data;
- timeouts, retries, quota-aware caching, and health checks;
- a privacy policy, terms, support contact, and account-deletion path;
- monitoring for failed refreshes, API changes, and MCP client compatibility.
Completing the request handlers is therefore not the end of the implementation. You still need to operate the authorization boundaries and take responsibility for user credentials and production reliability.
Common setup failures
Use this checklist before debugging the AI client itself:
- The Search Console property is verified and the connecting Google account can access it.
- The Google Search Console API is enabled in the same project as the OAuth credentials.
- The OAuth app audience and test-user list include the connecting account.
- The callback URI exactly matches an authorized redirect URI.
- The app requests
webmasters.readonly, offline access, and stores the refresh token. - The API call uses the exact property identifier returned by Sites.
- Search Analytics pagination and recent-data behavior are handled explicitly.
- The remote MCP endpoint is publicly reachable over HTTPS.
- MCP discovery metadata, PKCE, resource indicators, and token audience checks work with the target client.
- Logs and error messages do not disclose Google tokens or another user's data.
DIY server versus SearchConsole.ai
| Requirement | Build it yourself | SearchConsole.ai |
|---|---|---|
| Verify your Search Console site | Required | Required |
| Create a Google Cloud project | Required | Already handled |
| Enable and maintain the API | Required | Already handled |
| Configure and review a Google OAuth app | Required | Already handled |
| Implement refresh-token storage | Required | Already handled |
| Build Search Console API wrappers | Required | Already handled |
| Implement remote MCP authorization | Required | Already handled |
| Deploy, monitor, and maintain the server | Required | Already handled |
| Public analysis tools | Your responsibility | Read-only |
| Time to first query | Usually a couple of hours or more | A few minutes |
| Starting price | Infrastructure and development time | Free |
Self-hosting is reasonable when you need complete infrastructure control, custom write operations, private-network deployment, or a deeply specialized internal workflow. If the goal is simply to let an AI agent analyze Search Console data, the hosted path removes most of the work above.
Sign up to SearchConsole.ai for free, authorize the read-only Search Console scope, and connect
https://searchconsole.ai/mcpin your supported AI client.
Connect your AI client
Follow the guide for the client where you want to use the data:
- How to Connect ChatGPT to Google Search Console in 2026
- How to Use Google Search Console with Claude Code in 2026
- How to Add Google Search Console to Cursor in 2026
- How to Connect Codex to Google Search Console in 2026
After connecting, begin with a small verification prompt:
Use SearchConsole.ai to list every Search Console property I can access. Then show clicks, impressions, CTR, and average position for the last stable 28 days for the property I choose, compared with the previous 28 days. Keep Google-reported evidence separate from your recommendations.
Know the limits of the evidence
An MCP server makes Search Console data easier to use; it does not make the underlying dataset complete. Google filters some queries for privacy, returns top rows for Search Analytics, can show preliminary recent data, and calculates some grouped totals differently. Search Console traffic also should not be expected to match Google Analytics exactly.
A trustworthy agent should state the date window, property, search type, dimensions, filters, and known limitations with every important conclusion. It should treat URL Inspection results as evidence about Google's indexed version and treat proposed SEO causes as hypotheses until corroborated.
Once the connection is working, continue with Run an AI SEO audit using Search Console data.
Official references
- Create and manage a Search Console property
- Verify ownership of a Search Console property
- Authorize Search Console API requests
- OAuth 2.0 for server-side web applications
- Search Console API reference
- Search Analytics query reference
- Search Console API usage limits
- MCP authorization specification
- MCP transport specification