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

168 lines
4.5 KiB
TypeScript

/**
* Token bucket rate limiter for Google Search Console API quotas
* Implements per-API-type tracking with automatic token refill
*/
export type APIType = 'gsc' | 'inspection' | 'indexing';
interface BucketConfig {
capacity: number;
refillRate: number; // tokens per millisecond
refillInterval: number; // interval in ms
}
interface TokenBucket {
tokens: number;
lastRefill: number;
config: BucketConfig;
queue: Array<{ resolve: () => void; tokens: number }>;
}
export interface RateLimitConfig {
/** GSC API: 1200 queries/minute (default) */
gscQueries?: number;
/** URL Inspection: 600/day per property, 2000/day total */
inspectionQueries?: number;
/** Indexing API: 200/day */
indexingQueries?: number;
}
export class RateLimiter {
private buckets = new Map<APIType, TokenBucket>();
constructor(config: RateLimitConfig = {}) {
// GSC API: 1200 queries/minute
const gscQueriesPerMin = config.gscQueries ?? 1200;
this.buckets.set('gsc', {
tokens: gscQueriesPerMin,
lastRefill: Date.now(),
config: {
capacity: gscQueriesPerMin,
refillRate: gscQueriesPerMin / (60 * 1000), // per ms
refillInterval: 60 * 1000 // 1 minute
},
queue: []
});
// URL Inspection: 600/day (simplified - not tracking per-property)
const inspectionQueriesPerDay = config.inspectionQueries ?? 600;
this.buckets.set('inspection', {
tokens: inspectionQueriesPerDay,
lastRefill: Date.now(),
config: {
capacity: inspectionQueriesPerDay,
refillRate: inspectionQueriesPerDay / (24 * 60 * 60 * 1000), // per ms
refillInterval: 24 * 60 * 60 * 1000 // 1 day
},
queue: []
});
// Indexing API: 200/day
const indexingQueriesPerDay = config.indexingQueries ?? 200;
this.buckets.set('indexing', {
tokens: indexingQueriesPerDay,
lastRefill: Date.now(),
config: {
capacity: indexingQueriesPerDay,
refillRate: indexingQueriesPerDay / (24 * 60 * 60 * 1000), // per ms
refillInterval: 24 * 60 * 60 * 1000 // 1 day
},
queue: []
});
}
/**
* Refill tokens in a bucket based on elapsed time
*/
private refillBucket(bucket: TokenBucket): void {
const now = Date.now();
const elapsed = now - bucket.lastRefill;
const tokensToAdd = elapsed * bucket.config.refillRate;
bucket.tokens = Math.min(
bucket.config.capacity,
bucket.tokens + tokensToAdd
);
bucket.lastRefill = now;
}
/**
* Process queued requests if tokens available
*/
private processQueue(apiType: APIType): void {
const bucket = this.buckets.get(apiType);
if (!bucket) return;
this.refillBucket(bucket);
while (bucket.queue.length > 0 && bucket.tokens >= bucket.queue[0].tokens) {
const item = bucket.queue.shift()!;
bucket.tokens -= item.tokens;
item.resolve();
}
}
/**
* Acquire tokens for an API call
* Waits if insufficient tokens are available
*/
async acquire(apiType: APIType, tokens: number = 1): Promise<void> {
const bucket = this.buckets.get(apiType);
if (!bucket) {
throw new Error(`Unknown API type: ${apiType}`);
}
this.refillBucket(bucket);
// If enough tokens available, consume immediately
if (bucket.tokens >= tokens) {
bucket.tokens -= tokens;
return;
}
// Otherwise, queue the request
return new Promise<void>((resolve) => {
bucket.queue.push({ resolve, tokens });
// Set up periodic processing
const interval = setInterval(() => {
this.processQueue(apiType);
// Stop if this request was processed
if (!bucket.queue.find(item => item.resolve === resolve)) {
clearInterval(interval);
}
}, 100); // Check every 100ms
});
}
/**
* Get current token counts for monitoring
*/
getStatus(): Record<APIType, { tokens: number; capacity: number; queued: number }> {
const status = {} as Record<APIType, { tokens: number; capacity: number; queued: number }>;
for (const [apiType, bucket] of this.buckets.entries()) {
this.refillBucket(bucket);
status[apiType] = {
tokens: Math.floor(bucket.tokens),
capacity: bucket.config.capacity,
queued: bucket.queue.length
};
}
return status;
}
/**
* Reset all buckets (useful for testing)
*/
reset(): void {
for (const bucket of this.buckets.values()) {
bucket.tokens = bucket.config.capacity;
bucket.lastRefill = Date.now();
bucket.queue = [];
}
}
}