=== NEW SERVERS ADDED (7) === - servers/closebot — 119 tools, 14 modules, 4,656 lines TS (Stage 7) - servers/google-console — Google Search Console MCP (Stage 7) - servers/meta-ads — Meta/Facebook Ads MCP (Stage 8) - servers/twilio — Twilio communications MCP (Stage 8) - servers/competitor-research — Competitive intel MCP (Stage 6) - servers/n8n-apps — n8n workflow MCP apps (Stage 6) - servers/reonomy — Commercial real estate MCP (Stage 1) === FACTORY INFRASTRUCTURE ADDED === - infra/factory-tools — mcp-jest, mcp-validator, mcp-add, MCP Inspector - 60 test configs, 702 auto-generated test cases - All 30 servers score 100/100 protocol compliance - infra/command-center — Pipeline state, operator playbook, dashboard config - infra/factory-reviews — Automated eval reports === DOCS ADDED === - docs/MCP-FACTORY.md — Factory overview - docs/reports/ — 5 pipeline evaluation reports - docs/research/ — Browser MCP research === RULES ESTABLISHED === - CONTRIBUTING.md — All MCP work MUST go in this repo - README.md — Full inventory of 37 servers + infra docs - .gitignore — Updated for Python venvs TOTAL: 37 MCP servers + full factory pipeline in one repo. This is now the single source of truth for all MCP work.
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)
- Primary:
-
Typography:
- Font:
system-ui, -apple-system, sans-serif
- Font:
-
Spacing:
- Base unit:
8px - Card padding:
16px - Gap between elements:
12px/16px/24px
- Base unit:
-
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)
- Border radius:
Key Implementation Notes
-
No External Dependencies: All apps are self-contained HTML strings with inline CSS and JS. No CDN links, no external libraries.
-
Dark Mode: Every app supports dark mode via
@media (prefers-color-scheme: dark). -
Responsive: All apps work on narrow panels (mobile-friendly).
-
Data Injection: The
{{DATA}}placeholder gets replaced with actual JSON data at serve time. -
Interactive Elements: All interactivity (sorting, filtering, expanding) is handled by vanilla JavaScript in the HTML.
-
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.