PULSEby Finsi Labs

Open source MCP server · MIT

Daily budget moves for your own ad accounts, proposed and never applied

PULSE reads Meta and Google Ads, compares your campaigns and writes a budget plan with its evidence. You read it in Claude, ChatGPT or Grok, and you make the change yourself.

Today's play, synthetic healthy account

Meta · USD · window 2026-09-01 to 09-30

applied: false
CampaignDaily budgetProposedROAS 30d
example-aDamped marginal-ROAS allocation, capped at the 20% move limit $100$120 ↑4.00
example-bDamped marginal-ROAS allocation, capped at the 20% move limit $100$80 ↓1.80
Total$200$200conf. 0.35

Both moves: damped marginal-ROAS allocation, capped at the 20% move limit. Confidence 0.35.

Real output from examples/healthy.json. The proposal expires after 48 hours.

It cannot touch your spend

There is no pause, mutate or apply tool. A proposal is a set of instructions you can carry out in Ads Manager or Google Ads. Recording a move as applied records your report; it does not perform the action.

What you get

An MCP server with seven tools. Your AI client explains the numbers; PULSE itself makes no model calls.

  • Every move with its evidenceObserved window, conversions, CPA, ROAS, confidence and the reason, for each campaign.
  • Local or remoteClaude Code and Claude Desktop over stdio; Claude connectors, ChatGPT and Grok over self-hosted Streamable HTTP.
  • Read-only grantsMeta ads_read. Google Ads restricted to tested read endpoints and fixed GAQL queries.
  • Your machine, your keysCredentials stay under ~/.config/pulse with owner-only permissions.
  • Nothing else to sign up forNo Finsi account, database, warehouse or model-provider key.

Why not just ask Claude to read the API?

You can. The difference is what decides the number.

With PULSE, a tested, deterministic core computes every move and your AI client explains it. Asked freeform, the model computes the move itself, and can differ from one run to the next.

  • Same input, same answerNo model calls in the core, and an explicit seed and clock, so a play can be re-run and audited.
  • A fixed, honest window30 days ending two days before today, so late conversions do not make yesterday look broken. Missing numbers stay missing; they never become zeros.
  • Capped movesCurve moves are damped by confidence and capped at 20%. Proposals never exceed your total budget, and rounding never adds money.
  • It knows when not to moveLearning campaigns, shared or lifetime budgets, thin data and protected campaigns are held, with the reason stated.
  • Exact moneyEach amount carries its currency, Meta budget offsets follow Meta's published table, and thresholds use your account currency.

Four synthetic accounts report the same 30-day ROAS for their two campaigns, 4.00 and 1.80, and PULSE gives four different answers: a capped move, a learning hold, a shared-budget hold and a thin-data hold. On the same five fixtures, 10 freeform Claude runs each, 40 of 100 budget decisions moved a budget by more than 20%. Read the benchmark, its method and its caveats.

Try it with synthetic data

Node 22 or later. No credentials needed for the demo.

git clone https://github.com/finsilabs/pulse.git
cd pulse
npm ci
npm run build
node dist/index.js smoke --demo

Then connect your own accounts with node dist/index.js login meta or login google, following platform credentials and the client guides.

Tools

ToolWhat it returns
pulse.accountsAccounts your grant can see, with currency and timezone
pulse.snapshotValidated daily campaign metrics and observed budgets
pulse.todays_playScale, shift, hold, watching and warnings
pulse.explain_moveEvidence, window, target, cooldown and clamps for one campaign
pulse.propose_moveAn expiring action plan, always applied: false
pulse.configBounded targets, caps, protected campaigns and ceilings
pulse.record_decisionYour reported applied or dismissed decision, for cooldowns

What has been verified

The offline gate passes: rules and invariants, replayed synthetic Meta and Google responses, all seven tools, real stdio and Streamable HTTP transports, and OAuth with PKCE. A read-only live smoke against five real Meta ad accounts passed on 2026-11-06 and nothing was changed in them; it also caught and fixed a Meta pagination bug. Google Ads live checks and native-client screenshots are still to come.

These are test results, not a revenue-lift claim. Validate every recommendation against your own business before you act on it. See the verification record.