Skip to main content
Glama
README.md
# Spendict — the performance-marketing brain for AI agents

[![smithery badge](https://smithery.ai/badge/spendict/spendict)](https://smithery.ai/servers/spendict/spendict)

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](docs/PRD.md). 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](src/lib/prompt.ts) (versioned, server-side only, never exposed) plus the deterministic gating in [src/lib/contract.ts](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)

```bash
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:

```bash
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`.

```bash
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](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](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. **Convex** — `npx 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

| Endpoint | What |
|---|---|
| `POST /api/mcp` | MCP Streamable HTTP — tool `assess_ad_creative`; auth `Authorization: Bearer spd_live_…` |
| `POST /api/v1/assess` | REST front door, same pipeline |
| `GET /api/health` | liveness + prompt version |
| `POST /api/webhooks/polar` | billing webhook |
| `/dashboard` | signup, API keys, usage (the only human UI) |
| `/docs` | agent-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.ts` — **the 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.