{
  "markdown": "## Generect API MCP Server\n\nB2B lead and company data for AI agents — search, preview, enrich, email and phone\nlookup over the Generect API.\n\nBuilt so an agent can work without burning a customer's balance: sizing an\naudience is **free**, every tool says up front whether it costs money, and every\nresponse reports what was actually charged.\n\n### Get Your API Key\n\nSign up and get your API key at [https://beta.generect.com](https://beta.generect.com)\n\n### Remote MCP Server (OAuth - Recommended)\n\nThis MCP server implements OAuth 2.1 authorization as specified by the Model Context Protocol. \n\nUse our hosted MCP server with any OAuth-compliant MCP client:\n\n```json\n{\n  \"mcpServers\": {\n    \"generect\": {\n      \"url\": \"https://mcp.generect.com/mcp\",\n      \"type\": \"http\"\n    }\n  }\n}\n```\n\nWhen you first connect, the client will initiate an OAuth flow:\n1. You'll be redirected to the authorization page\n2. Enter your Generect API token from [beta.generect.com](https://beta.generect.com)\n3. Authorize the client to access your API\n4. The client receives an access token and can now use the MCP tools\n\n### OAuth Endpoints\n\n| Endpoint | Description |\n|----------|-------------|\n| `/.well-known/oauth-protected-resource` | Protected Resource Metadata (RFC 9728) |\n| `/.well-known/oauth-authorization-server` | Authorization Server Metadata (RFC 8414) |\n| `/.well-known/jwks.json` | JSON Web Key Set for token verification |\n| `/oauth/authorize` | Authorization endpoint (login + consent) |\n| `/oauth/token` | Token endpoint |\n| `/oauth/register` | Dynamic Client Registration (RFC 7591) |\n\n### Direct API key (no OAuth)\n\nIf your MCP client cannot complete the OAuth flow, you can pass the API key directly via the `Authorization` header. The server accepts any of:\n\n```\nAuthorization: YOUR_API_KEY\nAuthorization: Bearer YOUR_API_KEY\nAuthorization: Token YOUR_API_KEY\nAuthorization: Bearer Token YOUR_API_KEY   (legacy)\n```\n\nExample for `mcp-remote`:\n\n```json\n{\n  \"mcpServers\": {\n    \"generect\": {\n      \"command\": \"mcp-remote\",\n      \"args\": [\n        \"https://mcp.generect.com/mcp\",\n        \"--header\",\n        \"Authorization: Bearer YOUR_API_KEY\"\n      ]\n    }\n  }\n}\n```\n\n### Local Installation (Alternative)\n\nFor local development or when OAuth is not needed:\n\n1) Requirements: Node >= 18\n\n2) Configure environment:\n\n```bash\nGENERECT_API_BASE=https://api.generect.com\nGENERECT_API_KEY=Token <api-key>\nGENERECT_TIMEOUT_MS=300000\nJWT_SIGNING_KEY=<your-secret-key-for-jwt-signing>\nTOKEN_ENCRYPTION_KEY=<32-byte-hex-key-for-token-encryption>\n```\n\n3) Local dev (optional)\n\n```bash\nnpm install\nnpm run dev:http\n```\n\n4) Build and start (stdio server)\n\n```bash\nnpm run build && npm start\n```\n\n### Logging\n\nThe server emits one structured JSON log line per event to **stderr** (stdout is reserved for the MCP stdio protocol). Metadata logging is **on by default**; set `MCP_LOG=0` to disable it entirely.\n\n**Privacy — payloads are redacted by default.** Request/response payloads can contain personal data of prospects (names, company domains, generated emails). By default these values are **not** logged verbatim: each is reduced to a non-identifying shape marker (e.g. `\"first_name\": \"<str:4>\"`), so you can see *which* fields were sent without recording the data itself. Set `MCP_LOG_PAYLOADS=1` to log payloads verbatim — intended for short-lived debugging, with the data owner's consent.\n\nEvents:\n\n| `event` | When | Key fields |\n|---------|------|------------|\n| `tool_call` | LLM invokes a tool | `reqId`, `tool`, `input` (redacted unless `MCP_LOG_PAYLOADS=1`) |\n| `api_request` | Outbound call to Generect API | `url`, `method`, `body` (redacted unless `MCP_LOG_PAYLOADS=1`; never the token) |\n| `api_response` | Generect API responded | `url`, `status`, `ms` |\n| `tool_result` | Result returned to the LLM | `reqId`, `tool`, `ms`, `output` (redacted unless `MCP_LOG_PAYLOADS=1`) |\n| `tool_error` / `api_error` | Failure | `reqId`/`url`, `error`, `ms` |\n\n`reqId` correlates a `tool_call` with its `tool_result`. Set `MCP_DEBUG=1` for additional verbose output.\n\nThe hosted server runs under **PM2** (not Docker). View logs on the host with:\n\n```bash\npm2 logs generect-mcp                                # live\npm2 logs generect-mcp --err                          # errors only\ngrep tool_call ~/.pm2/logs/generect-mcp-out.log      # only LLM tool inputs\n```\n\n### Tools\n\nEvery tool states in its own description whether it is free or billable, and every\nresponse carries a `cost` block with the amount the API actually charged. Tools\naccept `timeout_ms`.\n\n**Free — start here**\n\n| Tool | What it does |\n|------|--------------|\n| `count_leads` | How many leads match an ICP + what the next step costs at *your* rates. Run before `search_leads`. |\n| `count_companies` | Same, for companies. |\n| `get_balance` | Balance, month-to-date usage, and this account's real per-operation prices. |\n| `get_bulk_job` | Poll a bulk job (the work was billed at submit time). |\n| `manage_webhooks` | List/create/update/delete/test webhook endpoints. |\n| `health` | Liveness + credential check against a free endpoint. Safe for monitors. |\n\n**Billable**\n\n| Tool | Billed |\n|------|--------|\n| `search_leads` | per returned row |\n| `search_companies` | per returned row |\n| `preview_leads` | per returned row (cheapest way to see real people) |\n| `enrich_lead` / `get_lead_by_url` | per record found |\n| `resolve_profile` | per **resolved** profile — the cheapest call here; an unresolvable reference is free |\n| `enrich_company` | per record found |\n| `generate_email` | per **valid** email found |\n| `validate_email` | per email submitted — every address, whatever the verdict |\n| `find_phone` | per phone found — the most expensive operation here |\n| `start_bulk_job` | per record, **reserved at submit time** |\n\n#### database vs realtime\n\nEvery search/enrich runs against either the cached database (sub-second, cheaper,\n**free counts**) or a live LinkedIn lookup (5–60s, pricier, billable counts, every\nfilter). Tools take `mode: \"auto\" | \"database\" | \"realtime\"`:\n\n- `auto` (default) tries the cheap path and escalates only if the API says a\n  filter you passed does not exist there. The escalation is reported in the\n  response, never silent.\n- `database` never escalates: if a filter is unsupported you get an error, not a\n  bigger bill.\n- Counting is the exception — a realtime count costs money, so `count_leads` /\n  `count_companies` refuse to run one unless you ask for `mode: \"realtime\"`\n  explicitly. They tell you which filters forced the choice instead.\n\n#### Budget-safe flow\n\n```\ncount_leads (free)  →  preview_leads (cheap)  →  search_leads (per row)\n                                              →  generate_email on the ids you kept\n```\n\n`get_balance` before and after a batch gives you an exact spend figure to report.\n\n### Agent skill\n\nTools give an agent the ability to call Generect; a skill gives it the procedure.\n`skills/generect-lead-workflows` documents the flows above so an autonomous agent\nfollows them without being told each time:\n\n```bash\nnpx skills add generect/generect_mcp --skill generect-lead-workflows\n```\n\nSee [skills/README.md](skills/README.md). Release process and the full list of\nplaces a version has to land: [RELEASING.md](RELEASING.md).\n\n### Cursor integration (settings.json excerpt)\n\n```json\n{\n  \"mcpServers\": {\n    \"generect-liveapi\": {\n      \"command\": \"node\",\n      \"args\": [\"./node_modules/tsx/dist/cli.mjs\", \"src/server.ts\"],\n      \"env\": {\n        \"GENERECT_API_BASE\": \"https://api.generect.com\",\n        \"GENERECT_API_KEY\": \"Token YOUR_API_KEY\",\n        \"GENERECT_TIMEOUT_MS\": \"300000\"\n      }\n    }\n  }\n}\n```\n\n### Claude Desktop (MCP) setup\n\nAdd to `~/.claude/claude_desktop_config.json` (or via UI → MCP Servers). Recommended: run via npx so users don't install anything globally.\n\n```json\n{\n  \"mcpServers\": {\n    \"generect-api\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"generect-ultimate-mcp@latest\"],\n      \"env\": {\n        \"GENERECT_API_BASE\": \"https://api.generect.com\",\n        \"GENERECT_API_KEY\": \"Token YOUR_API_KEY\",\n        \"GENERECT_TIMEOUT_MS\": \"300000\",\n        \"MCP_DEBUG\": \"0\"\n      }\n    }\n  }\n}\n```\n\nmacOS note: If Claude shows \"spawn npx ENOENT\" or launches an older Node via nvm, set `command` to the absolute npx path and/or override PATH:\n\n```json\n{\n  \"command\": \"/usr/local/bin/npx\",\n  \"env\": { \"PATH\": \"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin\" }\n}\n```\n\nAlternative without npx:\n\n```bash\nnpm i -g generect-ultimate-mcp\n```\n\nThen use:\n\n```json\n{ \"command\": \"/usr/local/bin/generect-mcp\", \"args\": [] }\n```\n\n### Deployment (production, PM2)\n\nThe hosted server (`https://mcp.generect.com`) runs under **PM2** on the host, fronted by nginx (TLS). The process is defined by [`ecosystem.config.js`](./ecosystem.config.js):\n\n```bash\nnpm ci && npm run build\npm2 start ecosystem.config.js      # or: pm2 reload ecosystem.config.js\npm2 save                           # persist the process list for reboot\n# once, as root, so it survives reboots:\n#   pm2 startup systemd -u mcp_user --hp /home/mcp_user\n```\n\n**Single instance only.** OAuth state (registered clients, auth codes) and MCP sessions are held in memory, so the server must run as one instance. Scaling horizontally requires a shared store (e.g. Redis) first — see `ecosystem.config.js`.\n\n**Required secrets (fail-closed).** In production (`NODE_ENV=production`) the server refuses to start unless `JWT_SIGNING_KEY` is set to a strong, non-default value; it never falls back to a hardcoded default or an ephemeral key. `TOKEN_ENCRYPTION_KEY`, if set, must be exactly 64 hex characters (32 bytes).\n\n### Docker\n\nDocker is supported for local/alternative runs. Build locally:\n\n```bash\ndocker build -t ghcr.io/generect/generect_mcp:local .\n```\n\nRun the server in a container (note: the same production secrets are required — an\ninsecure default will cause the container to exit at startup):\n\n```bash\ndocker run --rm \\\n  -e NODE_ENV=production \\\n  -e GENERECT_API_BASE=https://api.generect.com \\\n  -e GENERECT_API_KEY=\"Token YOUR_API_KEY\" \\\n  -e JWT_SIGNING_KEY=\"a-strong-random-secret\" \\\n  -e TOKEN_ENCRYPTION_KEY=\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\" \\\n  -e OAUTH_BASE_URL=https://your-domain.com \\\n  -p 3000:3000 \\\n  ghcr.io/generect/generect_mcp:local\n```\n\n### Remote over SSH (advanced)\n\nSome MCP clients allow spawning the server via SSH, using stdio over the SSH session. Example config:\n\n```json\n{\n  \"mcpServers\": {\n    \"generect-remote\": {\n      \"command\": \"ssh\",\n      \"args\": [\n        \"user@remote-host\",\n        \"-T\",\n        \"node\",\n        \"/opt/generect_mcp/dist/server.js\"\n      ],\n      \"env\": {\n        \"GENERECT_API_BASE\": \"https://api.generect.com\",\n        \"GENERECT_API_KEY\": \"Token YOUR_API_KEY\",\n        \"GENERECT_TIMEOUT_MS\": \"300000\"\n      }\n    }\n  }\n}\n```\n\n### Local testing helpers\n\nAll three default to **free** API calls only — a smoke test should never quietly\nbill whoever runs it.\n\n- Health check (account, price book, free cached count):\n\n```bash\nnpm run health -- <api-key>\n```\n\n- Which filters the free cached index supports right now (free counts only):\n\n```bash\nnpm run probe -- <api-key>\n```\n\n- Call tools via a local MCP client. Free tools by default; `--paid` adds one\n  3-row search and one email lookup, and the run prints what it spent:\n\n```bash\nnpm run mcp:client -- <api-key>\nnpm run mcp:client -- <api-key> --paid\n```\n\n### Security Notes\n\n- **OAuth tokens** are JWTs signed by the server and contain your encrypted API token\n- **Token encryption** uses AES-256-GCM with a key from `TOKEN_ENCRYPTION_KEY` (or derived from `JWT_SIGNING_KEY`)\n- **Fail-closed secrets** — in production the server refuses to start with a missing or well-known-default `JWT_SIGNING_KEY`, and never publishes symmetric key material in the JWKS\n- **Bounded, refreshable tokens** — access tokens expire (default 30 days, `ACCESS_TOKEN_TTL_SECONDS`) and are renewed via a `refresh_token` grant; refresh tokens are rotated on use and revocable at `POST /oauth/revoke` (RFC 7009). Tokens issued before this change remain valid (no forced re-auth)\n- **PKCE** is required for all authorization code flows (S256 method), and re-checked on the consent POST as well as the initial redirect — a code intercepted by a rogue app that claims the same URI scheme is useless without the verifier\n- **Dynamic Client Registration** allows any MCP client to self-register, but is now **rate-limited per IP** (`MCP_REGISTER_RATE_MAX`, default 60/hour) and the client store is **capped** (`MCP_MAX_CLIENTS`, default 5000, LRU eviction that never drops an in-use client)\n- **Redirect URIs: open by default, so any client can connect** (`MCP_REDIRECT_POLICY=open`). Accepted: any `https` URL, `http` only on loopback/private addresses, and an app's own private-use URI scheme (`cursor://…`, `vscode://…`, `com.example.app:/cb` — RFC 8252 §7.1). Refused regardless of policy: cleartext `http` to a public host, `#fragments`, embedded credentials, over-long URIs, and browser-executable schemes (`javascript:`, `data:`, `file:`, …) — that URI is navigated to from our own origin, so those would be XSS. Loopback callbacks match on everything but the port (RFC 8252 §7.3), since a native app's listener gets an ephemeral one. Set `MCP_REDIRECT_POLICY=strict` to fall back to the first-party allowlist (`*.generect.com`, `claude.ai`, `linear.app`, plus `MCP_ALLOWED_REDIRECT_DOMAINS` / `MCP_ALLOWED_REDIRECT_SCHEMES`)\n- **SSRF-guarded metadata fetches** — the client-id-metadata-document flow (`MCP_ENABLE_CIMD`, default on) fetches only `https` URLs that resolve exclusively to public IPs, with no redirect following, a hard timeout, and a response-size cap (blocks loopback / RFC1918 / link-local / cloud-metadata targets)\n- **Token validation fails closed** — if Generect cannot confirm a token during login (upstream error), the server declines to mint an access token instead of assuming validity\n- **Audience + algorithm pinning** ensures tokens are only used with this MCP server and only via the expected signing algorithm\n\n#### Configuration (security-relevant env vars)\n\n| Var | Default | Effect |\n|-----|---------|--------|\n| `ACCESS_TOKEN_TTL_SECONDS` | `2592000` (30d) | Access-token lifetime |\n| `REFRESH_TOKEN_TTL_SECONDS` | `7776000` (90d) | Refresh-token lifetime |\n| `MCP_MAX_CLIENTS` | `5000` | Cap on the in-memory DCR client store |\n| `MCP_REGISTER_RATE_MAX` | `60` | Max `/oauth/register` calls per IP per window |\n| `MCP_REGISTER_RATE_WINDOW_MS` | `3600000` (1h) | Rate-limit window |\n| `MCP_ENABLE_CIMD` | `true` | Allow client-id-as-metadata-URL (SSRF-guarded) |\n| `MCP_REDIRECT_POLICY` | `open` | `open` = any client may register its callback; `strict` = first-party allowlist only |\n| `MCP_ALLOWED_REDIRECT_DOMAINS` | — | Extra allowed redirect hostnames, `strict` only (comma-separated) |\n| `MCP_ALLOWED_REDIRECT_SCHEMES` | — | Extra allowed private-use URI schemes, `strict` only (comma-separated, e.g. `cursor,vscode`) |\n| `MCP_ALLOW_ANY_HTTPS_REDIRECT` | — | Legacy: opens https callbacks under `strict` (implied by `open`) |\n- **Log privacy** — prospect payloads are redacted from logs by default (`MCP_LOG_PAYLOADS=1` to opt in)\n\n## Brokered consent: which product UI approves the connection\n\n`/oauth/authorize` does not ask for a password. It hands off to a page in the\nproduct where the user is already signed in, and that page posts a freshly\nminted API token back to `/oauth/broker`. Two env vars decide which page that is,\nand **they must be changed together**:\n\n| Var | Effect |\n|-----|--------|\n| `MCP_CONSENT_URL` | Where `/oauth/authorize` redirects the user (`…/authorize/mcp?handoff=…&mcp=…`) |\n| `MCP_CONSENT_ORIGIN` | The only `Origin` allowed to call `/oauth/broker`. Defaults to the origin of `MCP_CONSENT_URL` — **but production sets it explicitly in `.env`**, so the default does not save you |\n\nMoving consent from one host to the other by editing only `MCP_CONSENT_URL`\nleaves the broker refusing the new page with\n`403 {\"error\":\"forbidden\",\"error_description\":\"Origin not allowed to broker consent.\"}`,\n*after* the user has already clicked Approve. Change both lines, then prove it:\n\n```bash\n# expect 400 invalid_handoff (origin accepted), NOT 403 forbidden\ncurl -s -X POST https://mcp.generect.com/oauth/broker \\\n  -H 'Content-Type: application/json' -H \"Origin: <the new consent origin>\" \\\n  -d '{\"handoff\":\"nonexistent-probe\",\"deny\":true}'\n```\n\nCORS is not the control here — the server reflects any `Origin` (bearer auth,\nno cookies), so a working preflight proves nothing about the broker.\n\n## Deploying to production\n\n`mcp.generect.com` runs **pm2, not Docker** (`.github/workflows/deploy-prod.yml`\nis the unused Docker path). Single instance, always: OAuth state and MCP sessions\nlive in memory, so a second worker split-brains auth.\n\n```bash\nssh root@chronos                      # 65.21.69.164\nsu - mcp_user && source ~/.nvm/nvm.sh # node via nvm\ncd ~/generect_mcp\ncp -r dist dist.bak.$(date +%H%M%S)   # what previous deploys did; keeps a rollback\ngit pull && npm ci && npm run build\n$EDITOR .env                          # consent vars, redirect policy\npm2 reload generect-mcp && pm2 list   # version column should show the new one\n```\n\nThen verify from outside the box — `pm2 list` showing `online` is not evidence\nthat the new behaviour is live:\n\n```bash\ncurl -s https://mcp.generect.com/health\ncurl -s -o /dev/null -w '%{redirect_url}\\n' \\\n  \"https://mcp.generect.com/oauth/authorize?client_id=<id>&redirect_uri=…&response_type=code&code_challenge=…&code_challenge_method=S256\"\n```\n",
  "bytes": 17595,
  "sha": "02ec76b46160ed71248acc856b0f8145ea22e656101e8e6f979267ef6f431a23",
  "repo_slug": "generect/generect_mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_com_generect_generect_mcp_984e9ff0/readme"
}