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

217 lines
5.6 KiB
TypeScript

/**
* OAuth 2.0 authentication for Google Search Console
* Supports interactive browser flow with token persistence
*/
import { OAuth2Client } from 'google-auth-library';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
import open from 'open';
import { createServer } from 'http';
const SCOPES = [
'https://www.googleapis.com/auth/webmasters',
'https://www.googleapis.com/auth/webmasters.readonly',
'https://www.googleapis.com/auth/indexing'
];
const TOKEN_DIR = join(homedir(), '.gsc-mcp');
const TOKEN_PATH = join(TOKEN_DIR, 'oauth-token.json');
const REDIRECT_URI = 'http://localhost:3000/oauth2callback';
export interface OAuthConfig {
clientId: string;
clientSecret: string;
redirectUri?: string;
}
/**
* Load OAuth credentials from environment or file
*/
export function loadOAuthConfig(): OAuthConfig | null {
// Try environment variables first
if (process.env.GSC_OAUTH_CLIENT_ID && process.env.GSC_OAUTH_CLIENT_SECRET) {
return {
clientId: process.env.GSC_OAUTH_CLIENT_ID,
clientSecret: process.env.GSC_OAUTH_CLIENT_SECRET,
redirectUri: process.env.GSC_OAUTH_REDIRECT_URI || REDIRECT_URI
};
}
// Try loading from file
if (process.env.GSC_OAUTH_CLIENT_FILE) {
try {
const content = readFileSync(process.env.GSC_OAUTH_CLIENT_FILE, 'utf-8');
const data = JSON.parse(content);
// Support both installed app and web app formats
const credentials = data.installed || data.web;
if (!credentials) {
throw new Error('Invalid OAuth client file format');
}
return {
clientId: credentials.client_id,
clientSecret: credentials.client_secret,
redirectUri: credentials.redirect_uris?.[0] || REDIRECT_URI
};
} catch (error: any) {
throw new Error(`Failed to load OAuth config from file: ${error.message}`);
}
}
return null;
}
/**
* Load stored OAuth tokens from disk
*/
function loadStoredTokens(): any | null {
if (!existsSync(TOKEN_PATH)) {
return null;
}
try {
const content = readFileSync(TOKEN_PATH, 'utf-8');
return JSON.parse(content);
} catch (error) {
return null;
}
}
/**
* Save OAuth tokens to disk
*/
function saveTokens(tokens: any): void {
if (!existsSync(TOKEN_DIR)) {
mkdirSync(TOKEN_DIR, { recursive: true });
}
writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2));
}
/**
* Perform OAuth flow with browser-based consent
*/
async function performOAuthFlow(oauth2Client: OAuth2Client): Promise<void> {
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
prompt: 'consent' // Force consent to get refresh token
});
console.error('Opening browser for Google authentication...');
console.error('If browser does not open, visit this URL:');
console.error(authUrl);
// Create temporary HTTP server to capture callback
const server = createServer();
const port = 3000;
const tokenPromise = new Promise<any>((resolve, reject) => {
server.on('request', async (req, res) => {
try {
const url = new URL(req.url || '', `http://localhost:${port}`);
if (url.pathname === '/oauth2callback') {
const code = url.searchParams.get('code');
if (!code) {
res.writeHead(400);
res.end('Missing authorization code');
reject(new Error('Missing authorization code'));
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<html>
<body>
<h1>Authentication successful!</h1>
<p>You can close this window and return to the terminal.</p>
</body>
</html>
`);
// Exchange code for tokens
const { tokens } = await oauth2Client.getToken(code);
resolve(tokens);
server.close();
}
} catch (error) {
reject(error);
server.close();
}
});
server.on('error', (error) => {
reject(error);
});
});
server.listen(port);
// Open browser
await open(authUrl);
// Wait for tokens
const tokens = await tokenPromise;
oauth2Client.setCredentials(tokens);
saveTokens(tokens);
}
/**
* Create authenticated OAuth2 client
*/
export async function getOAuthClient(): Promise<OAuth2Client> {
const config = loadOAuthConfig();
if (!config) {
throw new Error(
'OAuth credentials not found. Set GSC_OAUTH_CLIENT_ID/SECRET or GSC_OAUTH_CLIENT_FILE'
);
}
const oauth2Client = new OAuth2Client(
config.clientId,
config.clientSecret,
config.redirectUri
);
// Try to load stored tokens
const storedTokens = loadStoredTokens();
if (storedTokens) {
oauth2Client.setCredentials(storedTokens);
// Set up automatic token refresh
oauth2Client.on('tokens', (tokens) => {
if (tokens.refresh_token) {
saveTokens({ ...storedTokens, ...tokens });
} else {
saveTokens({ ...storedTokens, access_token: tokens.access_token });
}
});
// Verify tokens are still valid
try {
await oauth2Client.getAccessToken();
return oauth2Client;
} catch (error) {
console.error('Stored tokens invalid, re-authenticating...');
}
}
// Perform interactive OAuth flow
await performOAuthFlow(oauth2Client);
// Set up automatic token refresh
oauth2Client.on('tokens', (tokens) => {
const current = loadStoredTokens() || {};
saveTokens({ ...current, ...tokens });
});
return oauth2Client;
}