DiscoLike MCP server
2026년 11월 13일 커밋8,000만 개 이상의 글로벌 비즈니스 웹사이트 인덱스를 활용하여 AI 클라이언트가 타겟 계정을 발굴하고, 기업 데이터를 보강하며, 연락처를 찾을 수 있도록 지원합니다.
다른 이름 discolike/discolike-python
- 스타
- 9
- Tools
- —
- 다운로드, 30일
- —
- 최신 버전
- —
- 마지막 커밋
- 2026년 11월 13일
- 라이선스
- MIT
DiscoLike MCP란?
DiscoLike은(는) MCP server입니다. 8,000만 개 이상의 글로벌 비즈니스 웹사이트 인덱스를 활용하여 AI 클라이언트가 타겟 계정을 발굴하고, 기업 데이터를 보강하며, 연락처를 찾을 수 있도록 지원합니다.
프로젝트 README에서 생성된 요약
DiscoLike MCP server 정보
이 도구를 사용하면 AI 클라이언트 내부에서 타겟 계정을 발굴하고 기업 데이터를 보강할 수 있습니다. 50개 언어로 분석된 8,000만 개 이상의 글로벌 비즈니스 웹사이트 인덱스에 접근할 수 있어, LinkedIn에 의존하는 데이터베이스보다 훨씬 넓은 커버리지를 제공합니다. 개발자는 48개의 도구가 포함된 hosted MCP server를 streamable HTTP 연결을 통해 사용하여 복잡한 영업 기회 발굴 작업을 수행할 수 있습니다.
사용자는 Python SDK 또는 CLI를 통해 서비스와 상호작용할 수 있습니다. 시스템은 API key, OAuth, 그리고 코딩 에이전트용으로 설계된 특수 가입 프로세스를 포함한 다양한 인증 방식을 지원합니다. 연결이 완료되면 discover 도구로 이상적인 고객 프로필(ICP) 텍스트를 기반으로 유사 기업을 찾거나, count 도구로 시장 세그먼트 규모를 측정하고, match 도구로 불분명한 기업명을 특정 도메인과 연결할 수 있습니다. 또한 기업의 투자 현황 및 인원 성장세를 심층 조사할 수 있는 DiscoGen 기능도 제공합니다.
활용 사례
- 1유사 기업 발굴이상적인 고객 프로필을 일반 텍스트로 설명하여 새로운 타겟 계정을 찾습니다.
- 2기업명과 도메인 매칭기업명 리스트를 입력하면 해당하는 웹 도메인을 자동으로 찾아 리스트를 정리합니다.
- 3심층 기업 조사 수행최근 투자 라운드나 인원 성장세와 같은 세부 정보를 찾기 위해 자동화된 조사 작업을 실행합니다.
프로젝트의 README를 바탕으로 작성되었습니다. 모든 활용 사례는 해당 내용의 인용문과 대조하여 확인합니다.
구성
DISCOLIKE_API_KEY
| 이름 | 설명 | 유형 |
|---|---|---|
| DISCOLIKE_API_KEY | — |
자동 검사
2026년 11월 14일에 실행됨전체 보고서 →아직 인증되지 않았습니다. 서버는 80점 이상의 점수, 심각한 결함 없음, 그리고 검사에 충분한 데이터를 갖추어야 합니다. 검사 방식 · 점수를 높이기 위해 수정할 항목
- 소스 저장소 연결 및 접근 가능discolike/discolike-python is public and reachablegithub.com/discolike/discolike-python ↗통과
- 패키지가 이 저장소를 소스로 명시검사되지 않음
- 빌드 출처(Sigstore 증명)검사되지 않음
- 오픈 소스 라이선스 (OSI)MITspdx.org/licenses/MIT.html ↗통과
- 최근 1년 내 커밋 또는 릴리스Last commit or release 2026-11-13github.com/discolike/discolike-python ↗통과
- OpenSSF Scorecard검사되지 않음
- 패키지 이름이 인기 패키지와 유사하지 않음검사되지 않음
- 패키지 및 해당 종속성에 중요한 보안 권고 없음검사되지 않음
- 모델에 대한 숨겨진 지침 없음0 tool descriptions and the README read; no override phrases, credential paths or hidden characters통과
- 파괴적인 도구에 라벨 표시됨검사되지 않음
- 원격 엔드포인트가 MCP 핸드셰이크에 응답검사되지 않음
README
GitHub에서 보기DiscoLike indexes 80M+ business websites worldwide, analyzed in 50 languages — roughly 3x the coverage of LinkedIn-dependent databases. This repo gives you that index from Python or your terminal, as two packages: discover lookalike companies, size segments, enrich domain lists, match messy company names to domains, and find the right contacts.
If you're a coding agent: you can go from no account to first call without a human at a browser —
discolike.signup()(ordiscolike signup) opens an account from an email and name, the owner confirms by email, anddiscolike auth loginmints a credential. The fastest integration once authenticated is the hosted MCP server —https://api.discolike.com/v1/mcp(streamable-http, OAuth; 48 tools). For scripting and pipelines usepip install discolikeas a library; for the terminal usepip install discolike-clioruvx --from discolike-cli discolike, auth viaDISCOLIKE_API_KEY. Machine-readable API index:https://docs.discolike.com/llms.txt.
Installation
This repo ships two packages: discolike (the SDK) and discolike-cli (the discolike command, depends on the SDK).
pip install discolike # SDK only, for use as a library
pip install discolike-cli # CLI — installs discolike as a dependency
pip install "discolike[cli]" # same thing, extras spelling
Or with uv:
uv add discolike # as a library
uv tool install discolike-cli # CLI only
Or run the CLI without installing:
uvx --from discolike-cli discolike --help
Requires Python 3.11+.
Authentication
Three ways in, in the order an agent hits them: open an account, log in, or use an API key.
Signup — no browser, no credential
discolike signup --email jane@acme.com --first-name Jane --last-name Doe --agent my-agent
from discolike import signup
result = signup(email="jane@acme.com", first_name="Jane", last_name="Doe", agent="my-agent")
print(result.next_step) # relay this to the account owner
async_signup() is the async twin. Nothing is returned that authenticates you: the account owner confirms by email and logs in at app.discolike.com. SignupResult carries status, email, org_domain, org_status, and the next_step text to relay. Names are validated locally (1-40 characters, at least one letter, no angle brackets or control characters) before the request is sent.
--agent / agent= records which agent or framework opened the account; it defaults to discolike-python/<version>. The email of the last signup from this machine is remembered — signing up a different one needs --yes on the CLI or allow_new_email=True in the SDK, so a looping agent cannot quietly open accounts in a stream of names.
Login
discolike auth login # browser, PKCE authorization code, loopback redirect
discolike auth login --no-browser # print the URL instead of opening one — headless boxes
discolike auth login --port 8765 # pin the loopback port for SSH forwarding
discolike auth login --method api_key # paste an API key instead
discolike auth status # method, expiry, key source
auth login asks first whether you already have an account, and offers signup if not. The registered OAuth client is remembered per machine, so the consent screen appears once; auth logout drops the credential and keeps the registration.
API key
Create one at app.discolike.com/account/management/keys, then use any of:
export DISCOLIKE_API_KEY="dl_..." # environment variable
discolike auth login --api-key dl_... # or store it via the CLI
from discolike import ApiKeyCredential, Discolike
client = Discolike() # env var, then CLI config file
client = Discolike(api_key="dl_...") # explicit key
client = Discolike(auth=ApiKeyCredential(api_key="dl_..."))
client = Discolike(auth=oauth_credential) # an OAuthCredential — Bearer, refreshed proactively
auth= wins over api_key=, the environment, and the config file. OAuth credentials refresh within 60s of expiry and once more after a 401; a refresh that fails raises AuthenticationError("OAuth session expired; run discolike auth login").
Quickstart
from discolike import Discolike
from discolike.requests import DiscoverParams
client = Discolike()
companies = client.discover(
DiscoverParams(
icp_text="Cybersecurity for SMBs, managed IT services, endpoint protection",
country=["US"],
max_records=25,
)
)
for company in companies:
print(company.domain, company.name, company.similarity)
Narrow to a sub-industry within a radius of a point:
from discolike import Discolike
from discolike.requests import DiscoverParams
client = Discolike()
roofers = client.discover(
DiscoverParams(
sub_industry=["ROOFING"],
lat=30.2672,
lon=-97.7431,
radius="50mi",
max_records=25,
)
)
for company in roofers:
print(company.domain, company.name, company.sub_industry)
A bare ROOFING resolves to CONSTRUCTION/ROOFING and adds CONSTRUCTION to the category filter
server-side; pass the qualified form yourself if you would rather be explicit.
One query can cover several areas at once. geo takes lat,lon or lat,lon,radius and bbox takes
min_lat,min_lon,max_lat,max_lon; both are lists, and every circle, every box and the lat/lon
centre are OR'd together, up to 10 shapes:
austin_dallas_and_houston = client.discover(
DiscoverParams(
sub_industry=["ROOFING"],
geo=["30.2672,-97.7431,30mi", "32.7767,-96.797,30mi"],
bbox=["29.6,-95.7,30.1,-95.0"],
max_records=25,
)
)
Run DiscoGen research over a set of domains and wait for the result:
from discolike.requests import DiscoGenProcessRequest
job = client.discogen.process(
DiscoGenProcessRequest(
query="Recent funding rounds and headcount growth",
domains=["stripe.com", "adyen.com"],
web_search=True,
)
)
result = job.wait()
print(result.results)
Size a segment before pulling it:
from discolike.requests import CountParams
total = client.count(CountParams(phrase_match=["book a demo"], country=["US"]))
print(total.count)
Pull a full company profile:
from discolike.requests import CompaniesDataParams
profile = client.companies.data(CompaniesDataParams(domain="stripe.com"))
The client is a context manager if you want deterministic cleanup:
with Discolike() as client:
...
Async
Every resource has an async twin on AsyncDiscolike:
import asyncio
from discolike import AsyncDiscolike
from discolike.requests import DiscoverParams
async def main() -> None:
async with AsyncDiscolike() as client:
companies = await client.discover(DiscoverParams(icp_text="B2B SaaS for logistics", max_records=10))
print([c.domain for c in companies])
asyncio.run(main())
Examples
The examples/ folder has runnable scripts for common workflows — matching a CRM contact export to DiscoLike persona IDs (with checkpointing and resume), bulk-finding work emails from a CSV, and discovering companies by ICP then enriching them with DiscoGen. Each is stdlib-plus-SDK only:
export DISCOLIKE_API_KEY="dl_..."
python examples/match_crm_contacts.py --help
CLI
The same API from your terminal, with --help on every command:
discolike auth login
discolike discover --icp-prompt "managed IT services for SMBs" --country US --max-records 25
discolike match "Stripe Inc" --city "San Francisco"
discolike match --file companies.csv --name-column company_name --wait
discolike count --phrase-match "book a demo" --country US
discolike company data stripe.com
discolike extract https://stripe.com/enterprise
discolike signup --email you@company.com --first-name You --last-name Person
Top-level commands: discover, count, match, extract, validate-icp, append, segment, signup — plus auth, company, contacts, discogen, queries, account, search-providers, and llm-providers command groups.
CLI conventions
- Results print as JSON to stdout; errors print as JSON (
error,message,status_code) to stderr. - Pass
--format tablefor a human-readable table — used automatically when stdout is a TTY. - Async endpoints (
match --file,discogen run,discogen run-personas,segment,validate-icp) take--waitto block until the job finishes. Without it, you get atask_idback to poll withdiscolike discogen status <task_id> --family <family>.appendis synchronous — it returns enriched rows directly (or writes CSV bytes to--output).
| Exit code | Meaning |
|---|---|
| 0 | Success |
| 1 | Server error or unexpected failure |
| 2 | Validation error |
| 3 | Authentication or plan-access error |
| 4 | Rate limited |
| 5 | Network error |
| 6 | Not found |
| 7 | Needs input: prospecting wait reached a checkpoint with no terminal to ask (or --no-input) |
What's in the box
| Surface | What it does |
|---|---|
client.discover() / client.count() |
Find lookalike companies by ICP text, phrases, tech stack, geo, and 40+ other filters |
client.companies |
Company profiles: firmographics, scores, growth, redirects, vendors, subsidiaries |
client.contacts |
Search, look up, match, and discover contacts at target companies |
client.contacts_run |
Automatically find and deliver validated contacts under a spending cap |
client.match |
Match company names (plus phone/city/state) to domains — single or bulk CSV |
client.append() |
Enrich a CSV of domains with DiscoLike datasets |
client.segment() / client.segment_file() |
Auto-segment a list of domains (comma-separated string or CSV upload) |
client.validate_icp() |
Validate a domain list against an ICP definition |
client.queries |
Saved inclusion/exclusion lists for reusable targeting |
client.search_providers / client.llm_providers |
Manage BYOK search and LLM provider integrations for DiscoGen |
client.account |
Usage and quota |
discolike.signup() / async_signup() |
Open an account from an email and name, no credential required |
discolike.requests |
Request models for every call — generated from the platform OpenAPI spec, validated locally before the request is sent |
All responses are typed Pydantic models.
Long-running jobs
Bulk operations (match.bulk, segment, validate_icp, contacts.bulk_match) return a Job handle instead of blocking:
from discolike.requests import SegmentParams
job = client.segment(SegmentParams(domains="stripe.com,adyen.com,checkout.com"))
result = job.wait()
Job.status() polls without blocking, Job.cancel() aborts, and wait() raises JobFailedError / JobTimeoutError on failure. On DiscoGen-family jobs the returned JobStatus also carries warnings, estimated_cost and cost_metadata (per-model usage plus a search_provider entry when a BYOS search provider ran; search_calls only counts the model's built-in search).
JobTimeoutError is a client-side wait limit only — the task keeps running server-side (large DiscoGen runs can take hours), so call wait() again to resume or fetch status() later. Cancelled tasks still return results for every item that finished before cancellation. Send one job per list (up to 10,000 domains) rather than splitting into parallel jobs — concurrent DiscoGen jobs share your LLM provider key and slow each other down.
Contacts runs
client.contacts_run automatically finds and validates contacts, using database candidates before web fallback. max_companies counts companies delivering at least one validated contact; per_company caps validated contacts at each company. Set people.fit to describe the people you want and optionally email_cap_usd to cap delivery spend.
from discolike.requests import ContactsRunRequest, ContactsRunEventsParams
run = client.contacts_run.create(
ContactsRunRequest.model_validate({
"companies": {"source": "prompt", "icp_text": "Independent dental clinics in Ohio"},
"people": {"fit": "Practice owners and office managers"},
"max_companies": 50,
"per_company": 3,
"email_cap_usd": 20,
})
)
page = client.contacts_run.events(run.id, ContactsRunEventsParams(after=0))
run = client.contacts_run.wait(run.id) # Returns on done, failed or cancelled.
rows = client.contacts_run.rows(run.id) # Pages company domains, keeping their contacts together.
print(run.summary)
companies accepts a prompt (icp_text and/or filters), domains (source="domains", domains=[...]) or lookalike seeds (source="lookalike", persona_ids=[...]). max_companies is a top-level field, default 500. Responses include companies, companies_total, rows_total and summary totals (companies_delivered, validated_contacts, spent_usd). Domains may be masked as Company N; treat them as display labels. Delivered email labels are Validated and Validated (catch-all resolved). Events include next_cursor for the next call's after.
Runs start automatically. cancel() stops unfinished work; wait() only polls and a timeout stops local polling. continue_() remains available for companies runs and returns 409 for contacts runs. The separate title_preview() route remains live, but contacts runs take free-text people fit rather than title chips. A run idle for 30 days answers 410. A creation request exceeding the remaining balance raises InsufficientBalanceError (a PlanAccessError subclass).
CLI: discolike contacts run create|list|get|events|cancel|wait|continue|title-preview. Create reads --params-file plus optional --email-cap-usd; wait takes --timeout; get --all-rows fetches all contacts. get --rows-offset and --rows-limit count company domains for contacts runs. continue only advances companies runs and accepts --generation and --redo.
contacts.search and contacts.generate are deprecated in favor of contacts runs; their endpoints sunset on 2026-12-31.
Contact generation without an LLM key
contacts.generate runs on your own search provider plus either your own LLM or DiscoLike Groove, DiscoLike's native extractor. Pass NATIVE_ENGINE to skip the LLM entirely:
from discolike import NATIVE_ENGINE
from discolike.requests import ContactGenerateRequest
job = client.contacts.generate(
ContactGenerateRequest(
icp_text="VPs or Directors of Marketing at B2B SaaS",
domains=["gusto.com", "rippling.com"],
integration_id=NATIVE_ENGINE,
)
)
result = job.wait()
print(result.title_validation) # "none" on the native engine, "llm" on a BYOK run
The native engine returns every person the search surfaces with a title and does not validate titles against icp_text, so filter them yourself when that matters. Omit integration_id to use your default LLM integration, or native when you have none. A search provider is required either way.
ICP validation without an LLM key
validate_icp runs your ICP text against each domain on your own LLM provider key, or on DiscoLike's own ICP-fit model. Pass NATIVE_ICP_ENGINE for the latter — no LLM key, no LLM cost, and no web search on that run:
from discolike import NATIVE_ICP_ENGINE
from discolike.requests import ValidateIcpRequest
job = client.validate_icp(
ValidateIcpRequest(
icp_text="Cybersecurity for SMBs in North America, 50-500 employees",
domains=["gusto.com", "rippling.com"],
integration_id=NATIVE_ICP_ENGINE,
)
)
print(job.column_name) # ["ICP Fit", "ICP Score", "Reasoning"]
result = job.wait()
The engine decides the result columns, so read job.column_name instead of hardcoding them: an LLM run returns Fit / Confidence / Reasoning, the native model ICP Fit / ICP Score / Reasoning (always null). ICP Fit is Yes or No at a 0.50 threshold on ICP Score, the calibrated probability as a 0.00-1.00 string. The native model returns only that score, so Reasoning is always null — the column is there to keep the set the same shape as an LLM run, not to carry an explanation. integration_id="native-icp" also works on discogen.process for a prompt that already carries the validation structure.
Two errors are specific to the native engine: a 400 ValidationError when the ICP text does not yield a Mandatory / Reject if / Nice-to-have prompt, and a 503 ServerError when no ICP-fit engine is available. Task lifecycle, polling and statuses are the same either way.
Error handling
All errors inherit from DiscolikeError:
from discolike import Discolike, RateLimitError, ValidationError
from discolike.requests import DiscoverParams
try:
companies = Discolike().discover(DiscoverParams(icp_text="fintech infrastructure"))
except RateLimitError as err:
...
except ValidationError as err:
...
AuthenticationError, PlanAccessError, NotFoundError, ServerError, and APIConnectionError cover the rest. A request the account's remaining balance cannot cover raises InsufficientBalanceError (a PlanAccessError), whose max_affordable_records is the largest size that would fit, counted in the request's own unit (the message names it: records, contacts, domains, rows, queries or personas). Transient failures are retried automatically (3 attempts by default).
import pydantic
from discolike.requests import MatchCompanyParams
try:
params = MatchCompanyParams(name="Acme", min_match_confidence=10)
except pydantic.ValidationError as err:
... # raised locally: min_match_confidence must be 50-100
Request models validate before anything is sent, so a bad enum value or out-of-range number never costs a round trip. Unknown fields pass through untouched.
Configuration
| Option | Default | |
|---|---|---|
api_key |
DISCOLIKE_API_KEY env var, then CLI config file |
|
base_url |
https://api.discolike.com/v1 |
|
timeout |
60.0 seconds |
|
max_retries |
3 |
|
http_client |
— | Bring your own httpx2.Client / httpx2.AsyncClient |
A provided http_client is mutated in place (the auth header is stamped on it, and base_url is set if it's unset) — use a client dedicated to DiscoLike, not one shared across other services.
Development
This is a uv workspace with two members: packages/discolike (the SDK) and packages/discolike-cli (the CLI).
uv sync --all-packages
uv run pytest packages/discolike/tests
uv run pytest packages/discolike-cli/tests
uv run ruff check .
uv run python scripts/gen_requests.py --spec-url https://api.dev.discolike.com/v1/openapi.json # regenerate request models
uv run python scripts/gen_requests.py --check # fail on drift (CI)
Committed request models track the dev spec (--spec-url https://api.dev.discolike.com/v1/openapi.json); the prod spec lags behind, so --check without a --spec-url (or against prod) stays red until the platform deploys — don't regenerate against prod to "fix" it.
Support & contact
- API documentation: docs.discolike.com
- Sign up: discolike.com/signup
- Book a demo: calendly.com/discolike/introductory-call
- LinkedIn: linkedin.com/company/discolike
- Issues with this SDK: GitHub issues
License
Managed prospecting
REST starts in drafting, then waits at proposed for approval. Review the plan and approve its exact version before research starts.
from discolike.requests import (
ProspectingApproveRequest, ProspectingBrief, ProspectingGetParams,
ProspectingListParams, ProspectingMessageRequest, ProspectingPlanSettings, ProspectingRunUpdate,
)
run = client.prospecting.start(
ProspectingBrief(brief="Find 100 US logistics companies and 3 operations directors each"),
idempotency_key="logistics-search-2026-09-26",
)
run = client.prospecting.wait(run.run_id)
print(run.status, run.plan, run.messages) # Review before approving.
# After reviewing a proposed plan:
# client.prospecting.approve(run.run_id, ProspectingApproveRequest(plan_version=run.plan_version))
# run = client.prospecting.wait(run.run_id)
recent = client.prospecting.list(ProspectingListParams(limit=20))
message = client.prospecting.message(
run.run_id, ProspectingMessageRequest(text="Make it 250 companies"),
idempotency_key="logistics-target-edit-1",
)
page = client.prospecting.get(
run.run_id,
ProspectingGetParams(offset=0, limit=100, events_after=run.next_event_seq, messages_after=run.next_message_seq),
)
# Pick engines or a credit limit from the plan card's options before approving; approve the returned plan_version.
# run = client.prospecting.update_plan(run.run_id, ProspectingPlanSettings(plan_version=run.plan_version, max_spend_usd=25))
# client.prospecting.rename(run.run_id, ProspectingRunUpdate(title="Logistics ops leaders")) # Any status.
# client.prospecting.rename(run.run_id, ProspectingRunUpdate(checkpoints="auto")) # Stop pausing; answers an open checkpoint.
# client.prospecting.cancel(run.run_id) # Stop the run; it and its results stay readable.
DiscoLike MCP 질문
브라우저 없이 계정을 생성할 수 있나요?
discolike.signup() 메서드나 discolike signup CLI 명령어를 사용하여 이메일로 계정을 생성할 수 있습니다.
DiscoLike로 어떤 데이터를 찾을 수 있나요?
유사 기업 발굴, 시장 세그먼트 규모 측정, 도메인 리스트 보강, 기업명과 도메인 매칭, 연락처 정보 찾기 등이 가능합니다.