Tripwire watches for buying signals — a new VP of Sales, a funding round, a hiring surge — and the moment one fires, Claude autonomously researches the account, finds the right decision maker, and drafts outreach tied to that exact event.
ICP: "B2B SaaS, 50-200 employees, US"
│
▼
[tripwire] ▶ find_buying_signals(icp={"industry":"B2B SaaS",...})
— 3 signals, top: exec_change(85) @ Acme Corp
[tripwire] ▶ research_account(acme.com)
— contact=Sarah Chen (VP of Sales), sales_jobs=5, posts=2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Acme Corp · 120 ppl · B2B SaaS · SF
Why now (85/100): New VP of Sales 1mo ago,
Series A 3wks ago, 5 open AE roles.
Contact: Sarah Chen — VP of Sales
Signals: exec_change(85) · funding(75) · hiring_surge(70)
> Hi Sarah — saw you joined Acme as VP of Sales
> 1 month ago, right as the Series A closed and
> the team started scaling. That 0→1 outbound
> phase with 5 AEs ramping is exactly when data
> quality decides whether the motion holds...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Crustdata's moat is real-time, event-driven data — not just "having data." But every MCP toolkit on top of Crustdata (Composio, Zapier, Merge) is purely request/response: search, enrich, fetch. None has a trigger primitive.
Tripwire is the missing layer: it diffs Crustdata's live data against a stored snapshot to detect change — and change is what buying intent actually looks like. Their data is the eyes; this is the reflexes.
Leave CRUSTDATA_API_TOKEN unset and Tripwire serves realistic sample data from
tripwire/fixtures/. Set the token (Crustdata free/self-serve tier) to run on live data —
nothing else changes.
git clone <repo> && cd crustdata
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# See the full pipeline output — no keys needed:
python -m tripwire.server --demoThat prints a ranked prospect card with signal scores, a qualified decision maker, and a
personalized email draft. The [tripwire] ▶ lines on stderr are the reasoning trace —
you can see exactly which tools fired and why.
Re-run anytime by deleting the working snapshot first (re-arms the demo):
rm -f tripwire/fixtures/snapshot_state.json && python -m tripwire.server --demoClaude Code — a project-scoped .mcp.json is already committed. Approve the
tripwire server when prompted (or restart Claude Code), then:
prospect B2B SaaS companies, 50-200 employees, US — find the buying signals and
draft outreach for the top account
Claude will call find_buying_signals → research_account → emit a prospect card with
the reasoning trace visible in the terminal.
Claude Desktop — add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"tripwire": {
"command": "/path/to/crustdata/.venv/bin/python",
"args": ["-m", "tripwire.server"],
"env": { "PYTHONPATH": "/path/to/crustdata" }
}
}
}# Get a self-serve token at crustdata.com → add it:
cp .env.example .env
# set CRUSTDATA_API_TOKEN=<your_token> in .env
export $(cat .env | xargs)
python -m tripwire.server --demo # same command, real datatripwire/
crustdata.py REST client — serves fixtures/ when no token is set (mock mode)
models.py Signal + CompanyState contracts
signals.py find_buying_signals: diffs live data vs. snapshot → ranked Signals
research.py research_account: composes 4 Crustdata calls into one account brief
server.py FastMCP server — 6 tools + reasoning trace on stderr
fixtures/ sample data, seeded prior snapshot, and saved example outputs
.claude/
skills/tripwire/SKILL.md the SDR playbook Claude follows when prospecting
| Signal | Score | Why |
|---|---|---|
exec_change — new sales leader |
85 | Authority + mandate + 90-day re-tool window — all at once |
funding |
75 | Budget unlocked; no named buyer yet |
hiring_surge |
50 + 4/role (cap 74) | Scaling pain; magnitude-scaled |
Stacking is the agent's job: when a company fires multiple signals, Claude sees the cluster and reasons about the compounding — the per-signal score stays honest and testable.
fixtures/snapshot_prev.json is a committed "last time we looked" state where Acme has
zero sales roles, no funding, no VP Sales. The live fixtures show 5 roles, a Series A, and
Sarah Chen. The diff fires all three signals on every fresh run — predictable, explainable,
impressive. Delete fixtures/snapshot_state.json to re-arm between demos.
tripwire/fixtures/examples/prospect_card_acme.txt — a pre-rendered prospect card from a
mock-mode run. Attach it to the outreach email so the reviewer sees output quality before
they run anything.
find_buying_signals is one "tick" — pull, diff, emit. To run it unattended on a schedule:
python -m tripwire.run_polling # default: every 12h
POLL_INTERVAL_SECONDS=60 python -m tripwire.run_polling # fast, for testing
python -m tripwire.run_polling --once # single tick, then exitEach tick diffs against the persisted snapshot, so a signal fires once — the first tick after the change appears — then goes quiet. 12h is deliberate: funding/exec/hiring events are daily-scale, so polling faster just burns credits detecting nothing. For true "the-moment-it-happens" latency, use the Watcher webhook below instead — polling is the zero-infra stand-in.
Tripwire also runs as a webhook receiver. Crustdata's Watcher API pushes a payload the
moment a subscribed condition fires; we parse it into the same Signal and run the same
loop — no polling, no cron.
# Start the receiver
uvicorn tripwire.webhook:app --port 8765
# Expose it publicly so Crustdata can reach it
ngrok http 8765
# Fire a synthetic event locally (no token needed)
curl -X POST http://localhost:8765/webhook/test?event_type=exec_changeTo wire a real Watcher subscription (once you have a CRUSTDATA_API_TOKEN):
curl -X POST https://api.crustdata.com/watcher/subscribe \
-H "Authorization: Bearer $CRUSTDATA_API_TOKEN" \
-H "x-api-version: 2025-11-01" \
-H "content-type: application/json" \
-d '{
"event_types": ["hiring_surge", "funding", "exec_change"],
"filters": {"industry": "B2B SaaS"},
"webhook_url": "https://<ngrok-id>.ngrok.io/webhook/crustdata"
}'Schema note: Crustdata's webhook payload fields aren't publicly documented. The schema in
tripwire/webhook.py(WatcherPayload) is inferred from their marketing docs and verified against our test suite. Adjust_parse_payload()once you observe a live payload.
python -m pytest tests/ -v # 47 tests, ~0.1s, no keys neededThe test suite encodes the product decisions, not just mechanics:
test_scoring.py— priority order (exec_change > funding > hiring_surge), magnitude cap, boundstest_signals.py— diff fires on change, silent on same state, cold-start behaviour, full clustertest_research.py— decision maker ranking, sales job filtering, post fallback logictest_client.py— mock/live mode switch, fixture shapes, title filtering
Full research, architecture, phases, and outreach plan in docs/.
