A Google Analytics 4 MCP server for every property you own

QueryInbox serves the GA4 Data and Admin APIs over MCP at a single URL. Your agent asks a question in Google's own terms, and reads the answer for one property or for all of them at once.

QueryInbox is free while in public beta, and it asks for read-only Google access only. Sign in with Google to mint a key, or read the full setup guide first.

Read-only, by construction#

A QueryInbox key carries exactly the two Google scopes you granted when you signed in: read-only Search Console and read-only Google Analytics. The key cannot publish, edit or delete anything in your Google account, and the agent never sees your Google password or refresh token. It sees only the key.

The exposed surface is the read API those scopes allow, minus the endpoints that create state. Writes, account configuration and the exports that create a long-running job (audience exports, report tasks) are deliberately absent and the API says so with a specific error rather than failing halfway.

Revoking access#

Keys do not expire on their own. Revoke one in Settings → Agent access and it stops working immediately; the row stays in the list so you can still see what it did. Disconnect Google is the wider switch: it removes the stored Google authorization and every key at once.

Why GA4 is hard for an agent#

The GA4 Data API does not accept a table name and a SQL query. It wants a dateRanges array, dimensions and metrics as arrays of objects, and a property id that is a bare number, not the measurement id you see in your site's HTML and not the display name. An agent asked about "my analytics" has to guess which of those three identifiers you mean.

QueryInbox resolves the property for it: pass the display name, the numeric id, or any unique substring, and the selector is matched against the properties your account can actually read before the call goes out. The same applies to the Admin API, which is where the definitions live: which event a keyEvents metric counts, which custom dimension a report column refers to.

Seeing every property as one#

GA4 has no free way to aggregate properties. Roll-up properties are Google's own answer to this, and they are available only to Analytics 360 accounts that are linked to a Google Marketing Platform organization with an active 360 order (Analytics Help, "About roll-up properties"). If you are on standard GA4, Google's documentation offers you no supported path to a single total across properties.

The usual workarounds are BigQuery export (which is a data pipeline to build and maintain) or a Looker Studio blend, which accepts at most five data sources (Looker Studio Help, "How blends work") and slows down as the date range grows. QueryInbox takes neither route: it reads each property through the Data API at request time and aggregates in your browser, so the number of properties is not a limit and nothing is copied into a warehouse.

  • One question can span every property in your account. "Which sites lost sessions this week" is a single ask.

  • Ratios stay correct when aggregated: engagement rate is recomputed from summed sessions and engaged sessions rather than averaged across properties, because averaging rates would weight a property with twelve sessions the same as one with twelve thousand.

  • Nothing is stored. The reports are read from Google when you ask and cached for 5 to 15 minutes, so a repeat view is instant without becoming a history.

Realtime, and the trap in it#

properties.runRealtimeReport covers the last 30 minutes, and it does not accept the same metrics as the standard report. There is no sessions metric in realtime, so a question about "sessions right now" has to be answered with activeUsers or screenPageViews instead. An agent that carries the standard metric names into a realtime call gets an error rather than a wrong number, which is the better failure.

There is a subtler trap in historical data. Grouping by dateHour and summing a metric across the hours double-counts people: activeUsers is per hour, so someone active in two hours is counted twice. QueryInbox's own 24-hour view avoids this by asking Google for the deduplicated total for the window rather than adding up the hourly rows.

The endpoint#

One URL, one header, and the method name in the body. This request returns sessions and active users by date:

bash
curl -s https://queryinbox.com/api/agent/ga4 \
  -H "Authorization: Bearer $QUERYINBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"method":"properties.runReport","property":"example.com",
       "dateRanges":[{"startDate":"2026-09-01","endDate":"2026-09-28"}],
       "dimensions":[{"name":"date"}],
       "metrics":[{"name":"sessions"},{"name":"activeUsers"}]}'

What the agent can ask#

The Data API answers report questions; the Admin API answers the questions about what your reports mean. Both are on the same key.

  • "How many sessions and users did each site get last month?" properties.runReport grouped by date, run across every selected property.

  • "Which landing pages brought organic traffic?" Grouped by landingPagePlusQueryString, filtered on sessionDefaultChannelGroup.

  • "What is happening right now?" properties.runRealtimeReport, remembering that the realtime metric set is smaller.

  • "Which custom dimensions and metrics exist on this property?" properties.customDimensions.list and properties.customMetrics.list, from the Admin API.

  • "Which events count as key events?" properties.keyEvents.list, which is how a report's conversion column is defined.

Working on search too? The Search Console MCP server page covers GSC.

Connect your agent#

Mint a key in Settings → Agent access, then point your client at the hosted server. Claude Code is the shortest path:

bash
claude mcp add --transport http queryinbox https://queryinbox.com/mcp \
  --header "Authorization: Bearer qi_..."

Every other client (Codex, Cursor, opencode, pi, and anything else that accepts a URL and headers) is documented with a copyable command in the setup guide.

When a call fails#

The API answers with a code rather than prose, so an agent can act on it without parsing a sentence.

HTTPCodeWhat it means
401invalid_api_keyThe key is wrong or was revoked. Create a new one in Settings.
409reauth_requiredThe key is fine, but the Google authorization behind it expired. The response carries a reauthUrl; sign in there and the same key keeps working.
429rate_limitedMore than 120 requests in a minute from one key. Back off.
429google_quota_exceededGoogle's own quota rather than QueryInbox's. Back off, honoring retryAfter when the response includes it.
502google_errorGoogle rejected the request or failed. The message says why.

Questions#

What does it cost?

It is free while QueryInbox is in public beta. When the beta ends a paid plan arrives, and beta accounts get a heads-up before anything changes.

Do I need BigQuery or a service account?

No. There is no export to configure and no service account key to store. You sign in with Google once, and the key you mint reads the Analytics Data and Admin APIs with the read-only scope you already granted.

Can it read several GA4 properties at once?

Yes. One key covers every property your Google account can access, and the dashboard aggregates them in your browser rather than in a warehouse. The number of properties is not capped.

Why is my property id rejected?

The Data API wants the numeric property id, not the measurement id from your site's HTML and not the display name. QueryInbox resolves the display name or a unique substring to the numeric id before the call, so you can pass whichever one you have. A measurement id like G-XF6X84FDQT is not a property and will not match.

Does it include realtime data?

Yes, through properties.runRealtimeReport, which covers the last 30 minutes. Its metric set differs from the standard report, because there is no sessions metric in realtime, so questions about right now have to be asked in terms of active users or page views.

Try it with your own properties

Sign in with Google, pick the sites and properties you want, and mint a key. No card, and nothing is stored beyond your site list.