Spendict

Four MCP tools, one performance marketer's judgment — design targeting, audit a campaign build, gate an ad creative before spend, and diagnose a live campaign. Deterministic, server-verified verdicts your agent can act on.

Documentation

Spendict — the performance-marketing brain for AI agents

smithery badge

An MCP server (and REST API) that gives AI agents a performance marketer's judgment on demand. One tool, done exceptionally: assess_ad_creative tells an agent whether an ad creative will actually work, why not, and how to fix it — and returns a deterministic run / fix_first / kill verdict the agent can use to gate its own ad spend, before a cent is spent.

Built per the PRD. v1 sells to the humans building agents (developers, growth engineers, agencies); autonomous agents paying is the narrative upside, not the dependency.

Architecture

MCP client (Claude/Cursor/n8n/…) ──► /api/mcp        (Streamable HTTP, mcp-handler)
REST caller                       ──► /api/v1/assess  (same pipeline)
                                        │
                       validate input → quota consume (BEFORE inference)
                                        │
                              OpenRouter (framework system prompt, low temp)
                                        │
                    server recomputes score + recommendation (tamper-proof gate)
                                        │
                       log usage_event + assessment (the compounding dataset)
  • The IP is the system prompt in src/lib/prompt.ts (versioned, server-side only, never exposed) plus the deterministic gating in src/lib/contract.ts. The model provides judgment; the server derives the authoritative verdict, and model-vs-server disagreement is logged as a calibration signal.
  • Stack: Next.js (Vercel) · Convex (keys, metering, assessments dataset) · Clerk (dashboard auth) · Polar (billing, MoR) · OpenRouter (inference).
  • Metering discipline: quota check happens before inference; failed assessments are refunded; every call logs cost_cents so margin is observable from day one.

Local development (zero accounts needed)

pnpm install
cp .env.example .env.local
# in .env.local set:
#   SPENDICT_MOCK_MODEL=1          → deterministic heuristic verdicts, no inference cost
#   SPENDICT_DEV_API_KEY=test_key  → this string works as an API key locally
pnpm dev

Smoke it:

curl -s -X POST http://localhost:3000/api/v1/assess \
  -H "Authorization: Bearer test_key" -H "Content-Type: application/json" \
  -d '{"ad_copy":{"primary_text":"Still paying $200/mo for a gym you visit twice?"},"platform":"meta","product_context":"home fitness app"}' | jq

Real inference locally: set OPENROUTER_API_KEY, remove SPENDICT_MOCK_MODEL.

pnpm test        # unit tests (gating matrix, parsing, mock engine)
pnpm typecheck
pnpm calibrate   # run the calibration set through the engine (see below)

Calibration — the launch bar, not optional

The product is the judgment. Before launch (PRD §13):

  1. Replace calibration/calibration-set.json (10 starter examples) with 30–50 real ads you can personally judge, each with your own run/fix_first/kill call written before running the engine.
  2. OPENROUTER_API_KEY=… pnpm calibrate
  3. Where the server diverges from you, the prompt is wrong — tighten src/lib/prompt.ts (bump PROMPT_VERSION) until agreement ≥80% and there are zero false greenlights (server says run where you'd say kill).
  4. Re-run on every prompt edit — it's the regression test for the IP. pnpm calibrate -- --strict exits non-zero below the bar (CI-able).

Model choice: bench 2–3 OpenRouter models on the set via SPENDICT_MODEL=… pnpm calibrate; pick on judgment-quality-per-cost.

Going live (checklist)

  1. Convexnpx convex dev (creates deployment; regenerates convex/_generated), then npx convex deploy for prod. Set NEXT_PUBLIC_CONVEX_URL. Generate a secret (openssl rand -hex 32) and set it as SPENDICT_INTERNAL_KEY in both the Convex dashboard env and the Next.js env.
  2. Clerk — create app, set NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY + CLERK_SECRET_KEY. Hosted sign-in works out of the box; /dashboard is the only gated area.
  3. OpenRouter — set OPENROUTER_API_KEY, pick SPENDICT_MODEL (+ SPENDICT_VISION_MODEL for creative_url calls) from calibration. Ensure SPENDICT_MOCK_MODEL is unset.
  4. Polar — create Starter ($19) and Pro ($49) subscription products; set POLAR_PRODUCT_ID_*, checkout links as POLAR_CHECKOUT_URL_*, and a webhook to https://<domain>/api/webhooks/polar with POLAR_WEBHOOK_SECRET (events: subscription.active, subscription.updated, subscription.canceled, subscription.revoked). Checkout email must match the Clerk signup email (or pass customer_external_id = Clerk user id).
  5. Vercel — deploy, set all env vars, set NEXT_PUBLIC_APP_URL to the real domain.
  6. Do not set SPENDICT_DEV_API_KEY in production.
  7. Distribution — list on the MCP registry, mcp.so, MCP Market, Smithery; the tool description in src/app/api/[transport]/route.ts is the listing copy.

Tools

  • assess_ad_creative — Gate an ad creative before spend. Returns a launch recommendation (run/fix_first/kill) with scores across hook, angle, clarity, audience fit, platform fit, CTA and compliance safety, the single most likely reason it will underperform, and prioritized fixes.
  • audit_campaign_structure — Audit a paid-ads campaign build (Meta, Google, TikTok) before budget flows — judged by the right platform's rulebook. Returns a verdict (sound/fix_first/restructure), scored dimensions, the biggest budget leak, and fixes.
  • analyze_campaign_performance — Diagnose a running campaign's real metrics against benchmarks, walk the funnel to name the ONE bottleneck, and return a verdict (healthy/fix_first/kill_or_rebuild/insufficient_data). Optionally reconciles what Spendict predicted vs. what happened.
  • strategize_targeting — Design a paid-ads targeting strategy for a new campaign: audience approach, segmentation, exclusions, budget split with a learning-phase check, platform setup, and the single biggest risk.

Every tool returns a server-recomputed, deterministic verdict — the model proposes scores, the server decides. Mirrored one-for-one on the REST API.

Endpoints

EndpointWhat
POST /api/mcpMCP Streamable HTTP — tool assess_ad_creative; auth Authorization: Bearer spd_live_…
POST /api/v1/assessREST front door, same pipeline
GET /api/healthliveness + prompt version
POST /api/webhooks/polarbilling webhook
/dashboardsignup, API keys, usage (the only human UI)
/docsagent-builder quickstart

Quota errors come back structured ({"error":"quota_exceeded","upgrade_url":…}), never as crashes, so agents can handle them.

Repo map

  • src/lib/contract.ts — input/output schemas, deterministic scoring + spend gate
  • src/lib/prompt.tsthe crown jewel: versioned framework system prompt
  • src/lib/engine.ts — OpenRouter call, retry, parse, mock mode
  • src/lib/assess-service.ts — shared pipeline (validate → meter → infer → log)
  • src/lib/store.ts — Convex-backed key/quota/logging facade + dev fallback
  • convex/ — schema + functions (gateway, dashboard, billing)
  • scripts/calibrate.ts + calibration/ — the quality bar
  • src/app/ — landing, docs, dashboard, API routes

© Space Cadet d.o.o.