# MCP Apps for Google Search Console

This directory contains 5 interactive HTML applications that render as rich UIs in Claude Desktop via the MCP Apps protocol.

## Apps Overview

### 1. Performance Dashboard (`dashboard.ts`)
- **Tool:** `performance_dashboard`
- **Shows:** KPI cards, sparkline charts, top queries/pages with sortable tables
- **Data needs:** `{ siteUrl, startDate, endDate, kpis, trends, timeseries, topQueries, topPages }`

### 2. Quick Wins Board (`quick-wins.ts`)
- **Tool:** `quick_wins_board`
- **Shows:** Card grid of optimization opportunities with impact scores
- **Data needs:** `{ opportunities: [{ keyword, page, position, impressions, currentCtr, estimatedClickGain, impactScore }] }`
- **Interactive:** Position and impressions filters

### 3. URL Health Inspector (`url-health.ts`)
- **Tool:** `url_health_inspector`
- **Shows:** Traffic-light status cards with expandable inspection details
- **Data needs:** `{ results: [{ url, verdict, coverageState, lastCrawlTime, mobileUsability, richResults, ... }] }`
- **Interactive:** Click to expand full inspection details

### 4. Cannibalization Map (`cannibalization.ts`)
- **Tool:** `cannibalization_map`
- **Shows:** Expandable table rows with competing pages and impression distribution
- **Data needs:** `{ keywords: [{ keyword, pages: [...], totalImpressions, severity }] }`
- **Interactive:** Click to expand competing pages, sort by severity/impressions/pages

### 5. Content Decay Tracker (`content-decay.ts`)
- **Tool:** `content_decay_tracker`
- **Shows:** Declining pages with sparkline trends and actionable recommendations
- **Data needs:** `{ pages: [{ url, trend: [], peakClicks, recentClicks, percentDrop, daysSincePeak, severity, peakDate, absoluteLoss }] }`
- **Interactive:** Click to expand timeline details and recommendations, sort by multiple metrics

## How to Integrate

### Step 1: Register Apps as Resources

In your `server.ts`:

\`\`\`typescript
import { allApps } from './apps/index.js';

// Register each app's HTML as a resource
allApps.forEach(app => {
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
    return {
      resources: [{
        uri: app.resourceUri,
        name: app.toolName,
        description: app.description,
        mimeType: 'text/html',
      }]
    };
  });

  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
    if (request.params.uri === app.resourceUri) {
      return {
        contents: [{
          uri: app.resourceUri,
          mimeType: 'text/html',
          text: app.getHtml(),
        }]
      };
    }
  });
});
\`\`\`

### Step 2: Register Tools with UI Metadata

\`\`\`typescript
// Register tools that trigger apps
allApps.forEach(app => {
  server.setRequestHandler(ListToolsRequestSchema, async () => {
    return {
      tools: [{
        name: app.toolName,
        description: app.description,
        inputSchema: app.inputSchema,
        annotations: app.annotations,
        _meta: {
          ui: {
            resourceUri: app.resourceUri,
          }
        }
      }]
    };
  });
});
\`\`\`

### Step 3: Return Data via structuredContent

In your tool handler:

\`\`\`typescript
async function handlePerformanceDashboard(args: any) {
  // Fetch data from Google Search Console API
  const data = await fetchDashboardData(args.siteUrl, args.startDate, args.endDate);

  // Replace {{DATA}} placeholder with actual JSON
  const html = dashboardApp.getHtml().replace('{{DATA}}', JSON.stringify(data));

  return {
    content: [{
      type: 'resource',
      resource: {
        uri: dashboardApp.resourceUri,
        mimeType: 'text/html',
        text: html,
      }
    }],
    structuredContent: data, // Also send as structured data
  };
}
\`\`\`

## Design System

All apps use a consistent Google-inspired design:

- **Colors:**
  - Primary: `#1a73e8` (Google blue)
  - Success: `#34a853` (green)
  - Warning: `#fbbc04` (yellow)
  - Error: `#ea4335` (red)
  - Background: `#f8f9fa` (light) / `#1e1e1e` (dark)
  - Text: `#202124` (light) / `#e8eaed` (dark)

- **Typography:**
  - Font: `system-ui, -apple-system, sans-serif`

- **Spacing:**
  - Base unit: `8px`
  - Card padding: `16px`
  - Gap between elements: `12px`/`16px`/`24px`

- **Components:**
  - Border radius: `8px` (cards), `4px` (badges)
  - Box shadow: `0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08)`

## Key Implementation Notes

1. **No External Dependencies:** All apps are self-contained HTML strings with inline CSS and JS. No CDN links, no external libraries.

2. **Dark Mode:** Every app supports dark mode via `@media (prefers-color-scheme: dark)`.

3. **Responsive:** All apps work on narrow panels (mobile-friendly).

4. **Data Injection:** The `{{DATA}}` placeholder gets replaced with actual JSON data at serve time.

5. **Interactive Elements:** All interactivity (sorting, filtering, expanding) is handled by vanilla JavaScript in the HTML.

6. **SVG Sparklines:** Dashboard and content decay apps use inline SVG for sparkline charts (no chart libraries needed).

## Testing

To test an app in isolation, create a standalone HTML file:

\`\`\`typescript
import { dashboardApp } from './dashboard.js';

const testData = {
  siteUrl: 'https://example.com',
  startDate: '2026-01-01',
  endDate: '2026-01-28',
  kpis: { totalClicks: 1234, totalImpressions: 56789, avgCtr: 0.0217, avgPosition: 12.3 },
  trends: { clicks: 5.2, impressions: 3.1, ctr: 1.8, position: -2.1 },
  timeseries: { clicks: [100, 120, 115, 130, 140, 135, 145] },
  topQueries: [...],
  topPages: [...],
};

const html = dashboardApp.getHtml().replace('{{DATA}}', JSON.stringify(testData));
fs.writeFileSync('test-dashboard.html', html);
\`\`\`

Then open `test-dashboard.html` in a browser to verify it works correctly.
