Jake Shore f3c4cd817b Add all MCP servers + factory infra to MCPEngine — 2026-02-06
=== 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.
2026-02-06 06:32:29 -05:00

460 lines
11 KiB
TypeScript

import type { AppDefinition } from './types.js';
export const quickWinsApp: AppDefinition = {
toolName: 'quick_wins_board',
resourceUri: 'gsc://apps/quick-wins',
description: 'Interactive board showing optimization opportunities ranked by potential impact',
annotations: {
readOnlyHint: true,
idempotentHint: true,
},
inputSchema: {
type: 'object',
properties: {
siteUrl: {
type: 'string',
description: 'The site URL (must be verified in Google Search Console)',
},
startDate: {
type: 'string',
description: 'Start date (YYYY-MM-DD)',
},
endDate: {
type: 'string',
description: 'End date (YYYY-MM-DD)',
},
days: {
type: 'number',
description: 'Number of days to look back',
},
},
required: ['siteUrl'],
},
getHtml: () => `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quick Wins Board</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f8f9fa;
color: #202124;
padding: 16px;
line-height: 1.5;
}
.header {
margin-bottom: 24px;
}
.header h1 {
font-size: 24px;
font-weight: 500;
margin-bottom: 8px;
}
.summary {
display: flex;
gap: 24px;
margin-bottom: 16px;
font-size: 14px;
color: #5f6368;
}
.summary-item strong {
color: #1a73e8;
font-weight: 600;
}
.filters {
display: flex;
gap: 12px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.filter-group {
display: flex;
align-items: center;
gap: 8px;
}
.filter-group label {
font-size: 13px;
font-weight: 500;
color: #5f6368;
}
select, input {
padding: 6px 12px;
border: 1px solid #dadce0;
border-radius: 4px;
font-size: 13px;
background: white;
color: #202124;
}
select:focus, input:focus {
outline: none;
border-color: #1a73e8;
}
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.opportunity-card {
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08);
transition: box-shadow 0.2s;
display: flex;
flex-direction: column;
gap: 12px;
}
.opportunity-card:hover {
box-shadow: 0 4px 6px rgba(0,0,0,0.15), 0 2px 4px rgba(0,0,0,0.1);
}
.card-keyword {
font-size: 16px;
font-weight: 600;
color: #202124;
word-break: break-word;
}
.card-url {
font-size: 12px;
color: #1a73e8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-metrics {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 13px;
}
.metric-item {
display: flex;
flex-direction: column;
gap: 2px;
}
.metric-label {
font-size: 11px;
text-transform: uppercase;
color: #5f6368;
font-weight: 500;
letter-spacing: 0.5px;
}
.metric-value {
font-size: 15px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.position-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 13px;
font-weight: 600;
}
.position-badge.green {
background: #e6f4ea;
color: #137333;
}
.position-badge.yellow {
background: #fef7e0;
color: #b06000;
}
.position-badge.orange {
background: #fce8e6;
color: #c5221f;
}
.card-highlight {
background: #e8f0fe;
padding: 12px;
border-radius: 4px;
border-left: 3px solid #1a73e8;
}
.card-highlight .label {
font-size: 11px;
text-transform: uppercase;
color: #1967d2;
font-weight: 600;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
.card-highlight .value {
font-size: 20px;
font-weight: 600;
color: #1a73e8;
}
.impact-bar {
height: 6px;
background: #e8eaed;
border-radius: 3px;
overflow: hidden;
margin-top: 8px;
}
.impact-bar-fill {
height: 100%;
background: linear-gradient(90deg, #34a853, #fbbc04, #ea4335);
transition: width 0.3s;
}
.empty-state {
text-align: center;
padding: 48px;
color: #5f6368;
font-size: 14px;
}
@media (prefers-color-scheme: dark) {
body {
background: #1e1e1e;
color: #e8eaed;
}
select, input {
background: #2d2d2d;
color: #e8eaed;
border-color: #5f6368;
}
.opportunity-card {
background: #2d2d2d;
}
.card-keyword {
color: #e8eaed;
}
.card-url {
color: #8ab4f8;
}
.metric-label {
color: #9aa0a6;
}
.position-badge.green {
background: #0d652d;
color: #81c995;
}
.position-badge.yellow {
background: #7c4a03;
color: #fdd663;
}
.position-badge.orange {
background: #8a1a18;
color: #f28b82;
}
.card-highlight {
background: #1a3a52;
border-left-color: #8ab4f8;
}
.card-highlight .label {
color: #aecbfa;
}
.card-highlight .value {
color: #8ab4f8;
}
.impact-bar {
background: #3c4043;
}
}
@media (max-width: 600px) {
.cards-grid {
grid-template-columns: 1fr;
}
.summary {
flex-direction: column;
gap: 8px;
}
}
</style>
</head>
<body>
<div class="header">
<h1>Quick Wins Board</h1>
<div class="summary">
<div class="summary-item">
<strong id="total-opportunities">0</strong> opportunities
</div>
<div class="summary-item">
Est. <strong id="total-clicks-gain">0</strong> additional clicks/month
</div>
</div>
</div>
<div class="filters">
<div class="filter-group">
<label for="position-filter">Position:</label>
<select id="position-filter">
<option value="all">All</option>
<option value="4-10">4-10</option>
<option value="11-20">11-20</option>
</select>
</div>
<div class="filter-group">
<label for="impressions-filter">Min Impressions:</label>
<input type="number" id="impressions-filter" value="0" min="0" step="100">
</div>
</div>
<div class="cards-grid" id="cards-container"></div>
<div class="empty-state" id="empty-state" style="display: none;">
No opportunities match your filters
</div>
<script>
const DATA = {{DATA}};
let filteredData = [...DATA.opportunities];
function formatNumber(num) {
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
return num.toLocaleString();
}
function formatPercent(num) {
return (num * 100).toFixed(1) + '%';
}
function getPositionBadgeClass(position) {
if (position <= 10) return 'green';
if (position <= 14) return 'yellow';
return 'orange';
}
function renderCard(opp) {
const badgeClass = getPositionBadgeClass(opp.position);
const impactPercent = (opp.impactScore / 100) * 100;
return \`
<div class="opportunity-card">
<div class="card-keyword">\${opp.keyword}</div>
<div class="card-url" title="\${opp.page}">\${opp.page}</div>
<div class="card-metrics">
<div class="metric-item">
<div class="metric-label">Position</div>
<div class="metric-value">
<span class="position-badge \${badgeClass}">#\${opp.position.toFixed(1)}</span>
</div>
</div>
<div class="metric-item">
<div class="metric-label">Impressions</div>
<div class="metric-value">\${formatNumber(opp.impressions)}</div>
</div>
<div class="metric-item">
<div class="metric-label">Current CTR</div>
<div class="metric-value">\${formatPercent(opp.currentCtr)}</div>
</div>
<div class="metric-item">
<div class="metric-label">Impact Score</div>
<div class="metric-value">\${opp.impactScore.toFixed(0)}</div>
</div>
</div>
<div class="card-highlight">
<div class="label">Estimated Click Gain</div>
<div class="value">+\${formatNumber(opp.estimatedClickGain)} clicks/mo</div>
</div>
<div class="impact-bar">
<div class="impact-bar-fill" style="width: \${impactPercent}%"></div>
</div>
</div>
\`;
}
function renderCards() {
const container = document.getElementById('cards-container');
const emptyState = document.getElementById('empty-state');
if (filteredData.length === 0) {
container.innerHTML = '';
emptyState.style.display = 'block';
return;
}
emptyState.style.display = 'none';
container.innerHTML = filteredData.map(renderCard).join('');
}
function updateSummary() {
document.getElementById('total-opportunities').textContent = filteredData.length;
const totalGain = filteredData.reduce((sum, opp) => sum + opp.estimatedClickGain, 0);
document.getElementById('total-clicks-gain').textContent = formatNumber(totalGain);
}
function applyFilters() {
const positionFilter = document.getElementById('position-filter').value;
const minImpressions = parseInt(document.getElementById('impressions-filter').value) || 0;
filteredData = DATA.opportunities.filter(opp => {
// Position filter
if (positionFilter === '4-10' && (opp.position < 4 || opp.position > 10)) return false;
if (positionFilter === '11-20' && (opp.position < 11 || opp.position > 20)) return false;
// Impressions filter
if (opp.impressions < minImpressions) return false;
return true;
});
renderCards();
updateSummary();
}
// Event listeners
document.getElementById('position-filter').addEventListener('change', applyFilters);
document.getElementById('impressions-filter').addEventListener('input', applyFilters);
// Initialize
function init() {
renderCards();
updateSummary();
}
init();
</script>
</body>
</html>`,
};