# OpenSEO Google Search Console Google Analytics Integration: Complete Implementation Guide

> Learn how OpenSEO integrates Google Search Console and Google Analytics using OAuth 2.0 and a layered MCP architecture for structured, LLM-readable data. Implement today!

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-09-11

---

**OpenSEO integrates with Google Search Console and Google Analytics through a layered MCP-based architecture that uses OAuth 2.0 authentication, project-scoped service layers, and typed tools to expose GSC and GA4 data as structured, LLM-readable functions.**

OpenSEO (every-app/open-seo) is an open-source SEO platform that unifies search performance data by connecting directly to Google's APIs. This guide explains how the OpenSEO Google Search Console Google Analytics integration works under the hood, from OAuth credential handling to cross-service data fusion and UI components.

## Integration Architecture

OpenSEO implements a six-layer architecture to connect with Google's services safely and scalably:

- **OAuth & Connection Management**: Handles `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `BETTER_AUTH_SECRET` for self-hosted deployments, or uses built-in SaaS credentials in the hosted version. Implemented in [`src/server/features/google/oauth-config.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/google/oauth-config.ts).

- **Project-Scoped Context**: Every request wraps a `ProjectAuthContext` containing the organization ID and base URL. This context scopes all GSC/GA4 connections to specific projects and builds UI links. Defined in [`src/server/mcp/context.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/context.ts) via `buildProjectMeta`.

- **Service Layer**: Low-level API wrappers including `GscService` for Search Console (`searchAnalytics`, `urlInspection`) and `Ga4ReportingService` for GA4 Admin & Data APIs.

- **MCP (Model-Chat-Program) Tools**: Expose typed, read-only functions that LLMs and UI components invoke. Each tool validates input via Zod and returns formatted text plus structured JSON. Search Console tools live in [`src/server/mcp/tools/search-console-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/search-console-tools.ts) (e.g., `getSearchConsolePerformanceTool`), while GA4 tools reside in [`src/server/mcp/tools/google-analytics-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/google-analytics-tools.ts) (e.g., `getGoogleAnalyticsOrganicLandingPagesTool`).

- **UI Integration**: React components [`SearchConsoleConnectionCard.tsx`](https://github.com/every-app/open-seo/blob/main/SearchConsoleConnectionCard.tsx) and [`GoogleAnalyticsConnectionCard.tsx`](https://github.com/every-app/open-seo/blob/main/GoogleAnalyticsConnectionCard.tsx) let users connect accounts, view status, and navigate to settings.

- **Routing**: The settings page at `src/routes/_project/p/$projectId/settings/integrations.tsx` renders connection cards with deep-link anchors (`#search-console`, `#google-analytics`).

## Connection Workflow

### OAuth Configuration Detection

When running self-hosted, the server first checks for required environment variables. If `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, or `BETTER_AUTH_SECRET` are missing, the tool returns a nudge with documentation links:

```typescript
// src/server/mcp/tools/search-console-tools.ts
// Lines 70-80: detect missing OAuth config and return a helpful message
if (!config.clientId || !config.clientSecret) {
  return {
    text: `Google OAuth is not configured...`,
    structuredContent: { status: "error", setupUrl: GSC_SELF_HOSTED_SETUP_DOCS_URL }
  };
}

```

### User-Initiated Connection

The UI's `SearchConsoleConnectionCard` (or `GoogleAnalyticsConnectionCard`) presents a **Connect** button that redirects to Google's OAuth consent screen. After consent, Google redirects back with an authorization code that the server exchanges for refresh and access tokens, storing them scoped to the specific project.

### Token Refresh and Error Handling

When MCP tools execute, they validate stored tokens. If expired or revoked, the service catches `GscTokenError` (for Search Console) or `Ga4ReportError` and returns a response containing a reconnect URL:

```typescript
// src/server/mcp/tools/google-analytics-tools.ts
// Lines 191-202: translate GA4 errors into user-visible messages + reconnect link
} catch (error) {
  if (error.code === 'UNAUTHENTICATED') {
    return {
      text: "Google Analytics connection expired",
      structuredContent: { status: "error", reconnectUrl: buildReconnectUrl(projectId) }
    };
  }
}

```

## Data Retrieval Methods

### Search Console Performance

The `get_search_console_performance` MCP tool builds a `GscPerformanceInput` object from Zod-validated user input, forwards it to `GscService.getPerformance`, and formats results into markdown tables with structured JSON:

```typescript
// src/server/mcp/tools/search-console-tools.ts
// Lines 58-62: invoke the service and format the result
const result = await gscService.getPerformance(projectId, input);
return {
  text: formatAsMarkdownTable(result.rows),
  structuredContent: { status: "ok", rows: result.rows }
};

```

### Google Analytics Organic Data

The `get_google_analytics_organic_landing_pages` tool calls `Ga4ReportingService.runReport` with specific parameters to isolate organic search traffic:

```typescript
// src/server/mcp/tools/google-analytics-tools.ts
// Lines 46-48: configure the report kind for organic landing pages
const report = await ga4Service.runReport({
  kind: "landing_pages",
  channel: "organic_search",
  ...dateRange
});

```

Both tools return a **structured JSON envelope** with `status: "ok"` or `"error"` that downstream LLM agents parse reliably.

## Cross-Service Analysis

OpenSEO fuses GSC and GA4 data through the **search opportunities** tool (`get_search_opportunities`), which merges Search Console ranking data (pages ranking in positions 4-20) with GA4 organic landing page metrics. The `SearchOpportunityService` scores opportunity rows and exposes them via an MCP tool:

```typescript
// src/server/mcp/tools/google-analytics-tools.ts
// Lines 48-50: invoke the opportunity service
const opportunities = await searchOpportunityService.find({
  projectId,
  dateRange,
  limit: args.limit
});

```

## Implementation Examples

### Fetching Search Console Performance

```typescript
import { getSearchConsolePerformanceTool } from "@/server/mcp/tools/search-console-tools";

const args = {
  projectId: "proj_123",
  dimensions: ["query", "page"],
  dateRange: "last_28_days",
  rowLimit: 500,
};

const response = await getSearchConsolePerformanceTool.handler(args, {
  auth: { organizationId: "org_456" },
  baseUrl: "https://app.openseo.so",
});

console.log(response.text);   // Human-readable table
console.log(response.structuredContent?.rows); // Structured rows

```

### Retrieving GA4 Organic Landing Pages

```typescript
import { getGoogleAnalyticsOrganicLandingPagesTool } from "@/server/mcp/tools/google-analytics-tools";

const args = {
  projectId: "proj_123",
  startDate: "2024-08-01",
  endDate: "2024-08-28",
};

const response = await getGoogleAnalyticsOrganicLandingPagesTool.handler(
  args,
  { auth: { organizationId: "org_456" }, baseUrl: "https://app.openseo.so" }
);

console.log(response.text);                // Summary + table
console.log(response.structuredContent?.rows?.length); // Row count

```

### Combining GSC and GA4 for Search Opportunities

```typescript
import { getSearchOpportunitiesTool } from "@/server/mcp/tools/google-analytics-tools";

const args = {
  projectId: "proj_123",
  startDate: "2024-08-01",
  endDate: "2024-08-28",
  limit: 100,
};

const response = await getSearchOpportunitiesTool.handler(
  args,
  { auth: { organizationId: "org_456" }, baseUrl: "https://app.openseo.so" }
);

console.log(response.text); // Human-readable summary of scored opportunities

```

## Summary

- **Layered Architecture**: OpenSEO uses distinct layers for OAuth management, project context, API services, MCP tools, and UI components to ensure secure, scalable integrations.

- **OAuth Flexibility**: The system supports both self-hosted configurations (requiring `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `BETTER_AUTH_SECRET`) and hosted SaaS deployments with built-in credentials.

- **Project Scoping**: All data access is wrapped in `ProjectAuthContext` from [`src/server/mcp/context.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/context.ts), ensuring organization-level isolation of Google account connections.

- **MCP Tool Design**: Functions like `getSearchConsolePerformanceTool` and `getGoogleAnalyticsOrganicLandingPagesTool` provide typed, Zod-validated interfaces that return both human-readable text and structured JSON for LLM consumption.

- **Error Resilience**: Token expiration and authentication errors are caught as `GscTokenError` or `Ga4ReportError` and surfaced to users with specific reconnect URLs rather than raw failures.

## Frequently Asked Questions

### How does OpenSEO handle authentication for self-hosted deployments?

Self-hosted deployments must provide `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `BETTER_AUTH_SECRET` environment variables. The system detects missing configuration in [`src/server/features/google/oauth-config.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/google/oauth-config.ts) and returns setup documentation links rather than failing silently.

### Can OpenSEO refresh expired Google tokens automatically?

Yes. When MCP tools execute, they validate token freshness. If a token is expired or revoked, the tool catches the error (such as `GscTokenError` for Search Console) and returns a structured response containing a reconnect URL, prompting the user to re-authenticate without breaking the workflow.

### What specific Google Analytics data does OpenSEO retrieve?

OpenSEO retrieves organic search-specific metrics through `Ga4ReportingService`, particularly focusing on landing page performance via the `get_google_analytics_organic_landing_pages` tool. It filters for `channel: "organic_search"` to isolate SEO-relevant traffic from other acquisition sources.

### Where are the UI components for connecting Google accounts located?

The connection interface components are located at [`src/client/features/gsc/SearchConsoleConnectionCard.tsx`](https://github.com/every-app/open-seo/blob/main/src/client/features/gsc/SearchConsoleConnectionCard.tsx) for Search Console and [`src/client/features/ga4/GoogleAnalyticsConnectionCard.tsx`](https://github.com/every-app/open-seo/blob/main/src/client/features/ga4/GoogleAnalyticsConnectionCard.tsx) for Google Analytics. These components render on the integrations settings page at `src/routes/_project/p/$projectId/settings/integrations.tsx`.