Enrichment
БесплатноНе проверенA depth-first lead discovery server that finds and qualifies custom webshops, resolves the single best decision-maker contact via Prospeo, and stores qualified
Описание
A depth-first lead discovery server that finds and qualifies custom webshops, resolves the single best decision-maker contact via Prospeo, and stores qualified leads for human follow-up.
README
Project Overview
Selling a penetration-testing / bug-bounty service is a high-consideration, low-volume sale. Spraying hundreds of generic emails is the wrong shape for it -- the win is a few right companies, each reached through the right person, not a big funnel of weak contacts. Most lead-gen tooling optimises for the opposite: discover everything, enrich everything, auto-send. That produces volume and burns goodwill.
enrichment-mcp inverts that. It is a depth-first lead finder: per run it surfaces a handful of candidate custom-built B2B / high-blast-radius commerce operators (distributors, wholesalers, B2B webshops, marketplaces, trade platforms holding valuable/sensitive customer data -- not low-stakes B2C consumer-hobby shops), qualifies them hard, and for each keeper resolves the single best decision-maker. The deliverable stops at a stored, qualified lead carrying lean context -- enough to recognise the company, one verified contact, and a one-line "why". A human decides what to do next; the system never reaches out and never tests anything.
The design splits cleanly across two execution contexts. Claude (in claude.ai or Claude Code) runs discovery and qualification in-session using its own web_search / web_fetch -- no third-party search API, no LLM key pool. A thin FastMCP server does only the two things the chat sandbox cannot: paid contact resolution through Prospeo (an outbound API the sandbox blocks) and a durable lead store that remembers state across sessions. This is a ground-up rebuild of an older volume-funnel pipeline (clay-enrichment); roughly 5,000 lines of key-pool and worker machinery were dropped, four assets salvaged.
How It Works
For each run, Claude drives the loop and calls the server only when it must:
Claude session (lead-finder skill)
web_search / web_fetch ── discovery, hard qualification, NO_MATCH fallback mining
│ tool calls over HTTPS (static bearer)
▼
https://enrichment-mcp-<num>.<region>.run.app/mcp (Cloud Run — auto-HTTPS, scales to zero)
│
Cloud Run container (GCP) — env from Secret Manager
FastMCP server ── 7 tools, bearer auth, Streamable HTTP at /mcp
│
▼
Supabase Postgres Prospeo enrich-person MyEmailVerifier
(leads + prospeo_usage) (X-KEY multi-key pool) (validate_single)
The qualification loop is deliberately discard-heavy:
- Discover -- run one to three angles via
web_searchto surface a handful of candidates (not a funnel). - Qualify hard --
web_fetcheach site and score it 0-10 against the ICP: is it a custom (non-platform) stack, is it receptive (no existing public bounty program, no in-house AppSec), is its breach blast-radius high (B2B / distributor / marketplace holding valuable/sensitive customer data -- a gated criterion; low-stakes B2C hobby shops are out), is it right-sized to pay and stay unwatched, is there a reachable technical decision-maker. - Keep only winners -- discard anything under the
>=7gate immediately and explicitly. Quality over count. - Resolve one contact -- name the single best decision-maker, then call
resolve_contact; the server runs the Prospeo pool and returns one verified email + LinkedIn + title. - Fallback on miss -- on Prospeo
NO_MATCH, mine the site's team/about pages in-session, guess the email pattern, and confirm withverify_email; accept only aValidresult. - Store lean -- write the lead via
add_qualified_lead. If no contact resolves, the company is still stored (flagged), never thrown away.
Data-flow invariant: web tools run in the session; Prospeo and state run on the server. The server holds no loop, no scheduler, no autonomous discovery -- it only acts when Claude calls a tool.
Targeting Scope
| Dimension | In scope | Out of scope |
|---|---|---|
| Stack | Custom / bespoke webshops (own checkout, APIs, accounts) | Templated SaaS stores whose security is the platform's problem |
| Size | Mid-market: big enough to pay a bounty | Large enterprises with in-house security teams |
| Security posture | No existing public bounty program; signs of receptiveness | Companies already on HackerOne / Bugcrowd / Intigriti |
| Contact | One reachable decision-maker (CTO / lead dev / founder) | Bulk people enumeration; every employee |
The qualification rubric scores five criteria 0-2 each (custom-stack confidence, receptiveness, attack surface, ability to pay, reachable decision-maker) and sums to a 0-10 bounty_fit_score. The keep-gate is >=7 and custom-stack >=1 and receptiveness >=1, so a clear platform store or an existing bounty program is an automatic discard regardless of total.
Quick Start
Requirements: Python 3.10+ and a Postgres instance (the design targets Supabase; any Postgres works).
macOS / Linux
# 1. Create a virtualenv and install dependencies
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
# 2. Configure environment
cp .env.example .env
# Edit .env and fill in the values (see the table below)
# 3. Apply the database schema (SUPABASE_DB_URL must be set) -- run EVERY numbered
# file in order; each is idempotent, so re-running is safe.
psql "$SUPABASE_DB_URL" -f schema/001_leads.sql
psql "$SUPABASE_DB_URL" -f schema/002_engagement_statuses.sql
psql "$SUPABASE_DB_URL" -f schema/003_nudge_channels.sql
psql "$SUPABASE_DB_URL" -f schema/004_project_partition.sql
# 4. Run the server
.venv/bin/python -m src.mcp_server
# Serves Streamable HTTP at http://MCP_HOST:MCP_PORT/mcp (default 0.0.0.0:8000)
Environment variables (.env.example is the authoritative list):
| Variable | Purpose |
|---|---|
SUPABASE_DB_URL |
Full Postgres DSN to the lead store (required) |
PROSPEO_API_KEYS |
Comma-separated Prospeo keys (free-tier pool, rotated round-robin) |
PROSPEO_ENRICH_MOBILE |
false = email-only (1 credit); true = include mobile (10 credits) |
MYEMAILVERIFIER_API_KEY |
MyEmailVerifier key for the guess-and-verify fallback |
MCP_BEARER_TOKEN |
Static bearer the server requires on every request |
MCP_HOST / MCP_PORT |
Bind address / port (default 0.0.0.0 / 8000) |
Usage
Connecting Claude
Claude Code (default path -- static bearer):
claude mcp add --transport http enrichment-mcp \
https://enrichment-mcp-<num>.<region>.run.app/mcp \
--header "Authorization: Bearer <MCP_BEARER_TOKEN>"
Use the live Cloud Run service URL (printed by the deploy) with /mcp appended, and the MCP_BEARER_TOKEN from Secret Manager.
claude.ai web connector (OAuth). The web app cannot send a bearer header, so it uses OAuth. The server supports it via MCP_OAUTH_PROVIDER (see auth.py):
workos(recommended) -- WorkOS AuthKit via the fullWorkOSProvider: FastMCP serves the OAuth + DCR endpoints to claude.ai itself and proxies the login to AuthKit with a pre-registered client (so it works even though AuthKit's metadata omits a registration endpoint). SetMCP_OAUTH_PROVIDER=workos,WORKOS_AUTHKIT_DOMAIN=https://<name>.authkit.app,WORKOS_CLIENT_ID,WORKOS_CLIENT_SECRET(the WorkOS API key), andMCP_BASE_URL=<public run.app URL>, then redeploy. Add<MCP_BASE_URL>/auth/callbackto the WorkOS application's redirect URIs. claude.ai (Settings -> Connectors -> Add custom connector -> paste<base>/mcp) then drives the login.oidc-- any OIDC provider (Descope, Auth0, Google, ...) viaOIDCProxy: setMCP_OIDC_CONFIG_URL,MCP_OIDC_CLIENT_ID(+ secret), andMCP_BASE_URL.supabase-- reuse the project's own Supabase Auth, but only if that project advertises an OAuth registration endpoint (many do not -- preferworkos).
Switching modes is pure config -- no code change. Bearer mode (empty MCP_OAUTH_PROVIDER) stays available for Claude Code.
Running a lead hunt
The skills/lead-finder/ skill drives the depth-first workflow end-to-end. In a Claude session with the connector attached, give it a brief ("independent EU streetwear shops on custom stacks") and it runs discovery, qualifies, resolves contacts, and stores keepers -- calling the tools below. Across sessions, get_uncontacted is the durable memory of who is qualified but not yet acted on.
Tools
Lead store (CRM / state)
| Tool | Purpose |
|---|---|
add_qualified_lead |
Upsert a qualified lead within a project; domain is the key. Status never regresses; a domain owned by another project can't be taken. |
list_leads |
List leads (COMPACT rows) for a project, optionally filtered by status / min_score, paginated via offset. |
get_lead |
Fetch one lead (full row) by domain within a project. |
update_lead_status |
Advance the status enum for a domain within a project (validated). |
get_uncontacted |
Leads (compact) at qualified or contact_resolved in a project -- the cross-session backlog. |
Contact resolution (server-side network)
| Tool | Purpose |
|---|---|
resolve_contact |
Run the Prospeo enrich-person pool for a Claude-named person; returns one verified contact or found:false. Write-free. |
verify_email |
Verify a guessed address via MyEmailVerifier; the fallback accepts only Valid. |
Status flow: qualified --> contact_resolved --> contacted --> replied --> agreement_sent --> signed --> authorized_ready --> running --> reported --> closed / rejected.
Project partition. The leads table is shared by more than one outreach
pipeline (pentest / bug-bounty and the Avelero licensing pipeline). Every row
carries a project tag (pentest | avelero) and every lead-store tool is scoped
to it: reads filter by it, writes stamp it, and a domain owned by one project can't
be taken by the other. The scope is the LEADS_PROJECT env default (pentest),
overridable per call via the tools' project argument. list_leads /
get_uncontacted return a COMPACT projection (scan fields + a truncated summary) so
large lists stay under the MCP output cap; get_lead returns the full row.
Technical Details
Module breakdown
| Path | Responsibility |
|---|---|
src/mcp_server/server.py |
FastMCP app, pluggable bearer auth (_build_auth), run() over Streamable HTTP |
src/mcp_server/config.py |
Typed config -- plain @dataclass + python-dotenv, no pydantic |
src/mcp_server/db/pool.py |
asyncpg pool built from SUPABASE_DB_URL |
src/mcp_server/db/leads.py |
The five lead-store operations (parametrized, dict returns) |
src/mcp_server/contacts/prospeo.py + prospeo_pool.py |
Multi-key Prospeo enrich-person client with round-robin rotation |
src/mcp_server/contacts/verifier.py |
MyEmailVerifier client (inlined VerifyResult) |
src/mcp_server/contacts/resolve.py |
Write-free resolve_contact core: Prospeo primary -> verify |
src/mcp_server/tools/ |
@mcp.tool wrappers over the db and contacts layers |
skills/lead-finder/ |
The Claude-side skill: SKILL.md + references/icp.md + references/angles.md |
The lead record (intentionally lean)
One leads table, keyed on domain:
| Column | Notes |
|---|---|
domain |
Primary key |
company_name, summary, location |
Enough to recognise the lead |
webshop_platform |
custom / shopify / woocommerce / unknown (judged by Claude) |
bounty_fit_score |
The 0-10 qualification score |
why |
One-line rationale (receptiveness evidence folded in) |
status |
The funnel enum above |
contact_name/role/email/linkedin/email_verified |
The one best contact -- all nullable |
created_at, updated_at |
updated_at maintained by trigger |
A second table, prospeo_usage, meters credit burn across the key pool.
Credit model
| Call | Cost on hit | Cost on miss |
|---|---|---|
Prospeo enrich-person (email) |
1 credit | NO_MATCH is free |
Prospeo enrich-person (mobile) |
10 credits | free |
| Prospeo duplicate within ~90 days | free | -- |
MyEmailVerifier validate_single |
1 credit | 1 credit (still returns a status) |
Depth-first targeting keeps these low by design -- one resolution per kept lead, not per candidate.
Hosting
The server runs on Google Cloud Run as a container: automatic HTTPS, a public *.run.app URL, and scale-to-zero (no cost when idle). Secrets live in Secret Manager and are injected as env vars; the app-layer bearer is what actually protects the public endpoint (--allow-unauthenticated at the IAM layer so claude.ai, which carries no Google identity, can reach it).
deploy/deploy-cloudrun.sh is the reproducible runbook (project, APIs, secrets, deploy). The Dockerfile builds the image; Cloud Run injects $PORT, which config.py honors. One-liner to redeploy after a code change:
gcloud run deploy enrichment-mcp --source . --region <region> \
--allow-unauthenticated --max-instances 1 \
--set-secrets=MCP_BEARER_TOKEN=MCP_BEARER_TOKEN:latest,SUPABASE_DB_URL=SUPABASE_DB_URL:latest,PROSPEO_API_KEYS=PROSPEO_API_KEYS:latest,MYEMAILVERIFIER_API_KEY=MYEMAILVERIFIER_API_KEY:latest
Cloud Run pins secret versions per revision, so after updating a secret value, redeploy (or roll a new revision) to pick it up.
Roadmap
- Platform fingerprint tool -- an optional server-side check (
cdn.shopify.com,/wp-content/, platform headers) if Claude's page-judgment of custom-vs-platform proves unreliable. Deliberately deferred; Claude judges from the page for now. - OAuth path for the claude.ai web connector -- FastMCP
OIDCProxy+ a hosted IdP, swappable into the existing auth layer. domain-searchassist -- fall back to Prospeo's domain search when naming the decision-maker is hard (currently out of scope to preserve the one-best principle).
Disclaimer
This is a sales-qualification tool, not a reconnaissance or testing tool. It discovers publicly listed companies, judges their fit for a security-research engagement, and identifies one business contact for a human to approach. It does not contact prospects, does not send email, and does not run or trigger any security test -- those remain separate, human-gated actions. Process contact data in line with GDPR and applicable anti-spam rules, and only pursue testing against a target with explicit written authorization. Built for the author's own bug-bounty offering; not licensed for external distribution.
Установка Enrichment
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/tr4m0ryp/enrichment-mcpFAQ
Enrichment MCP бесплатный?
Да, Enrichment MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Enrichment?
Нет, Enrichment работает без API-ключей и переменных окружения.
Enrichment — hosted или self-hosted?
Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.
Как установить Enrichment в Claude Desktop, Claude Code или Cursor?
Открой Enrichment на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Enrichment with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
