Skip to content

fix(google-analytics): fix input validation and align output schemas#475

Merged
yuriassuncx merged 5 commits into
mainfrom
fix/google-analytics-input-validation
Jul 9, 2026
Merged

fix(google-analytics): fix input validation and align output schemas#475
yuriassuncx merged 5 commits into
mainfrom
fix/google-analytics-input-validation

Conversation

@yuriassuncx

@yuriassuncx yuriassuncx commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix input validation errors — some MCP clients (e.g. Claude Desktop, third-party hosts) serialize array and object parameters as JSON strings instead of native types. Added a fromJson preprocessor (z.preprocess) in reports.ts that transparently parses JSON strings before Zod validation. This fixes all 6 reported errors: dateRanges, dimensions, metrics, dimensionFilter, metricFilter, orderBys, orderBys, limit, and offset.
  • Remove { response: ... } wrapper from all output schemas — all tools now return data directly (e.g. { rows: [...] } instead of { response: { rows: [...] } }), consistent with google-search-console and google-tag-manager patterns.
  • Flatten getCustomDimensionsAndMetrics output — from { dimensions: { customDimensions: [...] }, metrics: { customMetrics: [...] } } to { customDimensions: [...], customMetrics: [...] }.
  • Fix code formatting — remove unnecessary blank lines between chained Zod method calls across all tool files.

Root cause

The validation error was:
```
Invalid input: expected array, received string (dateRanges, dimensions, metrics, orderBys)
Invalid input: expected record, received string (dimensionFilter)
Invalid input: expected number, received string (limit)
```

The fix uses z.preprocess to attempt JSON.parse on string inputs before Zod validates them. If parsing fails, the original value is passed through and Zod produces a clear type error.

Test plan

  • Run run-report with native JSON arrays — should work as before
  • Run run-report with JSON-stringified arrays — should now pass validation
  • Verify get-account-summaries output is now { accountSummaries: [...] } (no response wrapper)
  • Verify run-report output is now { rows: [...], dimensionHeaders: [...], ... } (no response wrapper)
  • Verify get-custom-dimensions-and-metrics output is { customDimensions: [...], customMetrics: [...] } (flat)

🤖 Generated with Claude Code


Summary by cubic

Fixes GA4 tool input validation across MCP clients (handles JSON‑stringified arrays/objects/booleans and both "null" strings and native nulls) and updates all outputs to return top‑level data without a response wrapper, matching google-search-console and google-tag-manager. Also aligns schemas with the GA4 API, flattens the custom dimensions/metrics output, and restores field documentation comments.

  • Bug Fixes

    • Parse JSON‑stringified arrays/objects/booleans and coerce "null"/null to undefined before validation.
    • Handles: dateRanges, dimensions, metrics, dimensionFilter, metricFilter, orderBys, limit, offset, funnelSteps, returnPropertyQuota, isDirectlyFollowedBy.
    • Schema alignment: rename quota field to tokensPerProjectPerHour; add PropertySummary.canEdit, ReportingDataAnnotation.annotationDateRange, and ResponseMetaData.dataLossFromOtherRow/emptyReason.
    • Restore JSDoc field docs in schemas.ts.
  • Migration

    • All tools now return data directly (no { response: ... }). Remove response. from callers.
    • getCustomDimensionsAndMetrics now returns { customDimensions, customMetrics }.
    • Affected tools: accounts, properties, ads links, annotations, runReport, runRealtimeReport, runFunnelReport.

Written for commit 4923302. Summary will update on new commits.

Review in cubic

@yuriassuncx
yuriassuncx enabled auto-merge (squash) June 24, 2026 20:50
yuriassuncx and others added 4 commits July 9, 2026 11:26
…with other Google MCPs

- Add `fromJson` preprocessor (z.preprocess) to handle MCP clients that
  serialize arrays and objects as JSON strings instead of native types.
  Fixes validation errors for dateRanges, dimensions, metrics,
  dimensionFilter, metricFilter, orderBys, limit, offset, and funnelSteps.
- Remove the `response` wrapper from all output schemas so tools return
  data directly (e.g. `{ rows: [...] }` instead of `{ response: { rows: [...] } }`),
  consistent with google-search-console and google-tag-manager patterns.
- Flatten `getCustomDimensionsAndMetrics` output from
  `{ dimensions: { customDimensions: [...] }, metrics: { customMetrics: [...] } }`
  to `{ customDimensions: [...], customMetrics: [...] }`.
- Fix code formatting: remove unnecessary blank lines between chained
  Zod method calls across all tool files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ull strings

- Apply fromJson() to returnPropertyQuota (all 3 report tools) and
  FunnelStepSchema.isDirectlyFollowedBy so MCP clients that serialize
  booleans as JSON strings (e.g. "true") pass validation, consistent
  with the existing fix for arrays, objects, and numbers.
- In fromJson, treat JSON-parsed null as undefined so optional fields
  that receive the string "null" degrade gracefully instead of failing
  Zod validation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eprocessor

MCP clients that send null explicitly for absent optional fields (e.g.
{"dimensionFilter": null}) were getting a hard Zod type error instead of
the field being treated as absent. Add an explicit null check so both
native null and the JSON string "null" are treated as undefined.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… schemas with GA4 API

- fromJson(schema).optional() didn't accept null/"null" for optional
  fields because ZodOptional only short-circuits on undefined before the
  preprocessor runs; moved .optional() inside fromJson(...) so the
  coercion to undefined is honored by the resulting schema.
- Fixed PropertyQuota's tokensPerProjectPerDay -> tokensPerProjectPerHour
  (wrong field name silently dropped real quota data).
- Added missing fields per the GA4 REST API: PropertySummary.canEdit,
  ReportingDataAnnotation.annotationDateRange, and
  ResponseMetaData.dataLossFromOtherRow/emptyReason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@yuriassuncx
yuriassuncx force-pushed the fix/google-analytics-input-validation branch from fd42de0 to 532d1c4 Compare July 9, 2026 14:54
…hemas.ts

The input-validation fix accidentally dropped JSDoc comments documenting
resource-name formats and enum values for GA4 API fields. Restore them —
they're useful reference for anyone reading the schema, independent of
the response-wrapper removal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@yuriassuncx
yuriassuncx requested a review from JonasJesus42 July 9, 2026 15:15
@yuriassuncx
yuriassuncx merged commit 6f3612c into main Jul 9, 2026
2 checks passed
yuriassuncx added a commit that referenced this pull request Jul 9, 2026
Revert "fix(google-analytics): fix input validation and align output schemas with other Google MCPs (#475)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants