feat(saas): SquareMCP v2 — multi-tenant MCP platform complete
Steps 0–10 of the v2 plan, 194 tests passing. Core infrastructure - Shared Redis client (src/redis.ts); all four Redis consumers migrated - Vitest test harness with vitest.config.ts and npm test/test:watch scripts Billing & invoicing (Steps 1–2) - Monthly invoice generation with idempotency (MySQL uq_customer_period unique key) - Cron job with Redis distributed lock (Lua compare-delete, 1-hr TTL) - Invoice emailer via nodemailer (FETCHERPAY SMTP) - Billing middleware: checkLimit gate in handleToolCall; platform attribution fix Email multi-tenancy (Step 3) - EmailCtx = Account | EmailCredentials; imap.ts + smtp.ts accept both - resolveEmailCtx helper in tools.ts; all email tools use customer credentials Analytics + platform health (Steps 4–5) - Chart.js bar charts for platform breakdown and daily activity - Token expiry check in getCredential with dynamic import refresh - platform-health.ts: per-platform health probe with 10-min Redis cache - GET /api/health/platforms; "Token expired" amber badge in dashboard Tool schema filtering (Step 6) - stripAccountParam deep-clones tool schemas; multi-tenant sessions never see the internal account enum OAuth hardening (Step 7) - Atomic auth code consumption: UPDATE SET used=TRUE, check affectedRows - customer_id threaded through oauth_auth_codes → oauth_tokens - getTokenCustomer(); requireAuth resolves req.customer from Bearer token - Consent page requires authenticated session; redirect_uri validated against registered URIs; http://localhost:* loopback wildcard DCR browser flow (Step 8) - ensureOAuthAppRegistered() upserts pre-registered SquareMCP OAuth app on startup with redirect URIs for mcp-callback, localhost:*, claude-desktop, opencode - GET /oauth/connect-mcp → server-side redirect (client_id off frontend) - GET /oauth/mcp-callback → exchanges code, renders config snippet page with copy buttons for Claude Desktop and Codex CLI Webhooks (Step 9) - webhook_url + webhook_secret columns on customers - deliverWebhook(): HMAC-SHA256 signing, 3× exponential retry (1s/4s/16s), Redis DLQ with 7-day TTL on total failure - isValidWebhookUrl(): SSRF protection (blocks RFC-1918, localhost, .local) - POST /api/webhooks/config (secret returned once), GET, DELETE - GET /api/admin/webhooks/dlq/:customerId - WhatsApp POST route uses express.raw() for raw body preservation - Dashboard Webhooks tab with secret-once display and copy button Developer docs (Step 10) - docs/ static HTML site (GitHub Pages, no build pipeline) - index.html: landing page with client + platform overview - getting-started.html: tabbed MCP config for Claude Desktop, Codex CLI, opencode - platforms.html: LinkedIn, TikTok, WhatsApp, Instagram, Twitter, Telegram guides - agent-tutorial.html: complete Node.js agent (Anthropic SDK + MCP SDK), LinkedIn posting loop, extensions for multi-platform + inbound webhook reaction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -80,7 +80,9 @@
|
||||
<div class="header-right">
|
||||
<nav class="header-nav">
|
||||
<button class="nav-link active" data-view="platforms">Platforms</button>
|
||||
<button class="nav-link" data-view="analytics">Analytics</button>
|
||||
<button class="nav-link" data-view="invoices">Invoices</button>
|
||||
<button class="nav-link" data-view="webhooks">Webhooks</button>
|
||||
<button class="nav-link hidden" data-view="admin" id="admin-nav">Admin</button>
|
||||
</nav>
|
||||
<span id="user-email" class="user-email"></span>
|
||||
@@ -92,6 +94,7 @@
|
||||
<section class="welcome">
|
||||
<h2>Connect your platforms</h2>
|
||||
<p>Link your social accounts to publish, analyze, and manage content from one place.</p>
|
||||
<button id="connect-mcp-btn" class="btn btn-primary" style="margin-top:16px;">Connect MCP Client</button>
|
||||
</section>
|
||||
|
||||
<section class="usage-bar" id="usage-bar">
|
||||
@@ -203,6 +206,24 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="analytics-section hidden" id="analytics-section">
|
||||
<h3 class="section-title">Analytics</h3>
|
||||
<p class="section-subtitle">Tool calls this month, by platform and day</p>
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card">
|
||||
<h4>By platform</h4>
|
||||
<div class="chart-container"><canvas id="platform-chart"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h4>Daily activity</h4>
|
||||
<div class="chart-container"><canvas id="daily-chart"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="analytics-empty" class="analytics-empty hidden">
|
||||
<p>No activity yet this month. Make your first tool call to see data here.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="invoices-section hidden" id="invoices-section">
|
||||
<h3>Invoices</h3>
|
||||
<div id="invoices-list" class="invoices-list"></div>
|
||||
@@ -212,6 +233,29 @@
|
||||
<h3>Admin Panel</h3>
|
||||
<div id="admin-customers" class="admin-customers"></div>
|
||||
</section>
|
||||
|
||||
<section class="webhooks-section hidden" id="webhooks-section">
|
||||
<h3 class="section-title">Webhook</h3>
|
||||
<p class="section-subtitle">Receive real-time events when messages arrive on your connected platforms.</p>
|
||||
<div class="webhook-card" id="webhook-card">
|
||||
<div id="webhook-status-row" class="webhook-status-row">
|
||||
<span id="webhook-url-display" class="webhook-url-display">No webhook configured</span>
|
||||
<button id="webhook-delete-btn" class="btn btn-ghost hidden">Remove</button>
|
||||
</div>
|
||||
<form id="webhook-form" class="webhook-form">
|
||||
<input type="url" id="webhook-url-input" placeholder="https://your-server.com/webhook" required>
|
||||
<button type="submit" class="btn btn-primary">Save & generate secret</button>
|
||||
</form>
|
||||
<div id="webhook-secret-box" class="webhook-secret-box hidden">
|
||||
<p class="webhook-secret-label">Signing secret — copy now, not shown again:</p>
|
||||
<div class="webhook-secret-value" id="webhook-secret-value"></div>
|
||||
<button class="btn btn-ghost" onclick="copyWebhookSecret()">Copy secret</button>
|
||||
</div>
|
||||
<div class="webhook-instructions">
|
||||
<p>Verify each request by computing <code>sha256=HMAC-SHA256(secret, rawBody)</code> and comparing to the <code>X-SquareMCP-Signature</code> header.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -225,6 +269,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user