scrapeloop-mcp 0.37.2 → 0.37.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,11 +73,16 @@ cleaners, strategy tags, vendors, scopes, and the ordered 7 setup steps) and
73
73
  | B2B saved collections | `get_lists`, `create_list`, `add_list_members` |
74
74
  | Campaign-ready Tables | `get_tables`, `create_table`, `get_table_columns`, `create_table_column`, `create_table_preset_column`, `set_table_cells`, `list_table_rows` |
75
75
 
76
- `import_leads`, `preview_import`, and `import_leads_csv` accept raw identities
77
- without placeholder emails. Each row needs an email, LinkedIn URL, external ID,
78
- or absolute source URL. The default match order is normalized email, canonical
79
- LinkedIn, then external/source identity, all scoped to the API key's workspace.
80
- A later import with a real email enriches the existing raw lead.
76
+ `import_leads`, `preview_import`, and `import_leads_csv` take leads that have no
77
+ email. No placeholder email is needed. Each row needs at least one of: email,
78
+ LinkedIn URL, external ID or an absolute source URL, so Scrapeloop can tell
79
+ which lead it is. The default match order is normalized email, canonical
80
+ LinkedIn, then external ID or source URL, all scoped to the API key's workspace.
81
+ A later import with a real email fills in the lead that had none.
82
+ A row that cannot be imported is skipped and comes back in `invalid_reasons`.
83
+ Each entry has a `reason` written for the user: show it as it is written.
84
+ One exception: a preview of sourcing rows can answer short codes joined by a
85
+ semicolon. Put those in your own plain words.
81
86
  Mutating imports generate one `operation_id` and reuse it across transient HTTP
82
87
  retries. The API returns the first committed result for the same key and
83
88
  payload, and rejects reuse with different inputs. If every response is lost,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrapeloop-mcp",
3
- "version": "0.37.2",
3
+ "version": "0.37.3",
4
4
  "description": "Scrapeloop MCP server - set up and run Scrapeloop end-to-end (integrations, scraping, lead database, verification, cleaners, enrichment, strategies, Instantly campaigns) from any MCP client (Claude Desktop, Cursor, ChatGPT, …).",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -1863,7 +1863,7 @@ const TOOLS = {
1863
1863
  import_leads: {
1864
1864
  def: {
1865
1865
  description:
1866
- 'Bring your own leads: atomically import externally-sourced records into a campaign-bindable Scrapeloop Table without scraping or lead credits. A generic record needs at least one stable identity: email, linkedin_url, external_id, or an absolute source_url. Email may be null. Canonical sourcing rows may opt into sourcing_contract and must include controlled source, source_state, niche, and a valid phone or email; phone-only local businesses receive a stable source-scoped fallback identity. Source and niche tags are generated automatically. Matching is workspace-scoped and deterministic: normalized email first, then local-business phone, canonical LinkedIn, and external/source identity. The legacy dedupe_by="external_id" mode keeps external identity authoritative. A later record with a real email merges into the earlier LinkedIn/source-only lead instead of duplicating it. No placeholder emails are created. verification_status and custom_fields are preserved, and custom_fields flow to Instantly as custom variables. Target list_id or pass list_name to create-or-get a Table. Batch up to 1000 records. One operation_id is reused across HTTP retries, so an uncertain response can be recovered without duplicating leads. You may pass a prior operation_id; reusing it with different inputs returns 409. Each record: {source?, source_state?, email?, linkedin_url?, source_url?, external_id?, first_name, last_name, business_name, phone, website, address, city, state_region, country, niche, rating, reviews_count, gbp_link?, facebook?, instagram?, verification_status ("verified"|"catchall_verified"|"unverified"|"risky"|"invalid"), strategy_tags:[], custom_fields:{}}. Returns {operation_id, list_id, inserted, updated, deduped, invalid, invalid_reasons}.',
1866
+ 'Bring your own leads: atomically import externally-sourced records into a campaign-bindable Scrapeloop Table without scraping or lead credits. A generic record needs at least one of: email, linkedin_url, external_id (your own id for the record) or an absolute source_url (the page the record came from), so Scrapeloop can tell which lead it is. Email may be null. Canonical sourcing rows may opt into sourcing_contract and must include controlled source, source_state, niche, and a valid phone or email; a local business with only a phone gets an id made from its source and its phone, so a later import finds the same lead. Source and niche tags are generated automatically. Matching is workspace-scoped and deterministic: normalized email first, then local-business phone, canonical LinkedIn, then external_id or source_url. The legacy dedupe_by="external_id" mode matches by external_id or source_url first. A later record with a real email merges into the earlier lead that only had a LinkedIn link, an external_id or a source_url, instead of duplicating it. No placeholder emails are created. verification_status and custom_fields are preserved, and custom_fields flow to Instantly as custom variables. Target list_id or pass list_name to create-or-get a Table. Batch up to 1000 records. One operation_id is reused across HTTP retries, so an uncertain response can be recovered without duplicating leads. You may pass a prior operation_id; reusing it with different inputs returns 409. Each record: {source?, source_state?, email?, linkedin_url?, source_url?, external_id?, first_name, last_name, business_name, phone, website, address, city, state_region, country, niche, rating, reviews_count, gbp_link?, facebook?, instagram?, verification_status ("verified"|"catchall_verified"|"unverified"|"risky"|"invalid"), strategy_tags:[], custom_fields:{}}. Returns {operation_id, list_id, inserted, updated, deduped, invalid, invalid_reasons}. Each entry of invalid_reasons has a reason written for the user: show it as it is written.',
1867
1867
  inputSchema: obj(
1868
1868
  {
1869
1869
  list_id: S,
@@ -2061,7 +2061,7 @@ const TOOLS = {
2061
2061
  import_leads_csv: {
2062
2062
  def: {
2063
2063
  description:
2064
- "Bring your own leads from CSV/TSV into a campaign-bindable Table. Rows may identify a lead by email, LinkedIn URL, external ID, or absolute source URL, so raw identities do not need placeholder emails. The parser auto-detects the delimiter, maps common headers (including linkedin_url and source_url), preserves unmapped columns as custom_fields, and normalizes verifier verdicts. A CSV containing source, source_state, and niche automatically enables the sourcing contract: every loaded row must also have a valid phone or email, source IDs are namespaced, phone-only local businesses get a stable fallback identity, and source/niche tags are generated. Matching is workspace-scoped: email first, then local-business phone, LinkedIn, and external/source identity. Set dry_run:true first to preview writes and column mapping. Mutating CSV imports are atomic and reuse one operation_id across HTTP retries. You may pass a prior operation_id; reusing it with changed CSV inputs returns 409. No scraping, lead credits, or re-verification. Target list_id or pass list_name. Up to 1000 rows per call.",
2064
+ "Bring your own leads from CSV/TSV into a campaign-bindable Table. Each row needs at least one of: email, LinkedIn URL, external ID or an absolute source URL, so Scrapeloop can tell which lead it is. A row with no email needs no placeholder email. The parser auto-detects the delimiter, maps common headers (including linkedin_url and source_url), preserves unmapped columns as custom_fields, and normalizes verifier verdicts. A CSV containing source, source_state, and niche automatically enables the sourcing contract: every loaded row must also have a valid phone or email, source IDs are namespaced, a local business with only a phone gets an id made from its source and its phone so a later import finds the same lead, and source/niche tags are generated. Matching is workspace-scoped: email first, then local-business phone, LinkedIn, then external ID or source URL. Set dry_run:true first to preview writes and column mapping. Mutating CSV imports are atomic and reuse one operation_id across HTTP retries. You may pass a prior operation_id; reusing it with changed CSV inputs returns 409. No scraping, lead credits, or re-verification. Target list_id or pass list_name. Up to 1000 rows per call. Skipped rows come back in invalid_reasons. Each entry has a reason written for the user: show it as it is written. One exception: in a dry run with the sourcing contract on, a reason can be short codes joined by a semicolon. Put those in your own plain words.",
2065
2065
  inputSchema: obj(
2066
2066
  {
2067
2067
  csv: S,
@@ -2117,7 +2117,7 @@ const TOOLS = {
2117
2117
  preview_import: {
2118
2118
  def: {
2119
2119
  description:
2120
- 'Dry-run for import_leads: report exactly what an import WOULD do ({would_insert, would_update, deduped, invalid, invalid_reasons, list_exists}) WITHOUT writing anything (no leads, no list, no tags created). Same inputs as import_leads. Call this first for a bring-your-own batch, show the user the plan (e.g. "adds 340 new, updates 12, skips 3 bad rows"), then confirm before import_leads.',
2120
+ 'Dry-run for import_leads: report exactly what an import WOULD do ({would_insert, would_update, deduped, invalid, invalid_reasons, list_exists}) WITHOUT writing anything (no leads, no list, no tags created). Same inputs as import_leads. Call this first for a bring-your-own batch, show the user the plan (e.g. "adds 340 new, updates 12, skips 3 bad rows"), then confirm before import_leads. Each entry of invalid_reasons has a reason written for the user: show it as it is written. One exception: with sourcing_contract on, a reason can be short codes joined by a semicolon. Put those in your own plain words.',
2121
2121
  inputSchema: obj(
2122
2122
  {
2123
2123
  list_id: S,