harvestapi-mcp
# harvestapi-mcp
[](https://github.com/Hassan220022/harvestapi-mcp/actions/workflows/ci.yml)
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
MCP server for [HarvestAPI](https://harvestapi.io) — LinkedIn profiles, companies, posts, jobs, leads, ads, groups, and platform account data over the Model Context Protocol (stdio).
All 25 tools are read-only. No message sending, no connection automation, no billing mutations.
## Legal / Ethical Notice
This server is a thin wrapper over the HarvestAPI LinkedIn-data API. You are responsible for complying with LinkedIn's Terms of Service, HarvestAPI's Terms, and applicable data-protection laws (GDPR, etc.) when using LinkedIn-sourced data. Do not use harvested data to spam, harass, or violate privacy. The maintainers provide no warranty and accept no liability for how you use the tool.
HarvestAPI is a third-party scraping/enrichment service — it is not affiliated with LinkedIn.
## Prerequisites
- Node.js 20+
- A HarvestAPI key — get one at https://harvestapi.io/admin/api-keys
## Install
```bash
git clone https://github.com/Hassan220022/harvestapi-mcp
cd harvestapi-mcp
npm ci
npm run build
```
Or run without cloning (after `npm publish` or via `npx` from the repo):
```bash
npx --yes github:Hassan220022/harvestapi-mcp
```
## Environment
| Variable | Required | Default | Description |
|---|---|---|---|
| `HARVESTAPI_KEY` | yes | — | Your HarvestAPI API key. Sent as `X-API-Key`. |
| `HARVESTAPI_BASE_URL` | no | `https://api.harvestapi.io` | Override base URL (rarely needed). |
| `HARVESTAPI_TIMEOUT_MS` | no | `30000` | Request timeout in ms. |
| `HARVESTAPI_MAX_RETRIES` | no | `2` | Max retries for transient 5xx / 429 / timeout. Never retried for 401/403/422. |
Copy `.env.example` to `.env` for local dev — but do not commit `.env`.
```bash
cp .env.example .env
# edit .env
```
## Configure
### Claude Desktop (`claude_desktop_config.json`)
```json
{
"mcpServers": {
"harvestapi": {
"command": "node",
"args": ["/absolute/path/to/harvestapi-mcp/dist/index.js"],
"env": {
"HARVESTAPI_KEY": "your_key_here"
}
}
}
}
```
### Generic MCP (any stdio client)
```json
{
"command": "node",
"args": ["/path/to/harvestapi-mcp/dist/index.js"],
"env": { "HARVESTAPI_KEY": "your_key_here" }
}
```
### Using `npx` (no local build)
```json
{
"mcpServers": {
"harvestapi": {
"command": "npx",
"args": ["-y", "github:Hassan220022/harvestapi-mcp"],
"env": { "HARVESTAPI_KEY": "your_key_here" }
}
}
}
```
## Tools
25 tools, all `harvest_*`. Every tool maps to a verified HarvestAPI endpoint — see [`docs/tool-mapping.md`](docs/tool-mapping.md) and [`docs/harvestapi-endpoint-inventory.json`](docs/harvestapi-endpoint-inventory.json).
| Tool | Description | Required params |
|---|---|---|
| `harvest_get_profile` | Get LinkedIn profile | one of `url`, `publicIdentifier`, `profileId` |
| `harvest_search_profiles` | Search LinkedIn profiles | — (all filters optional) |
| `harvest_get_profile_posts` | Get posts by a profile | one of `profile`, `profileId`, `profilePublicIdentifier` |
| `harvest_get_profile_comments` | Get comments by a profile | one of `profile`, `profileId`, `profilePublicIdentifier` |
| `harvest_get_profile_reactions` | Get reactions by a profile | one of `profile`, `profileId`, `profilePublicIdentifier` |
| `harvest_get_company` | Get LinkedIn company | one of `url`, `universalName`, `search` |
| `harvest_search_companies` | Search companies | — |
| `harvest_get_company_posts` | Get posts by a company | one of `company`, `companyId`, `companyUniversalName` |
| `harvest_get_group` | Get LinkedIn group | one of `url`, `groupId` |
| `harvest_search_groups` | Search groups | — |
| `harvest_search_geo_id` | Resolve LinkedIn Geo ID by text | `search` |
| `harvest_get_post` | Get a LinkedIn post | `url` |
| `harvest_search_posts` | Search LinkedIn posts | — |
| `harvest_get_post_comments` | Get comments on a post | `post` |
| `harvest_get_post_reactions` | Get reactions on a post | `post` |
| `harvest_get_comment_reactions` | Get reactions on a comment | `commentId` or `commentUrl` |
| `harvest_get_comment_replies` | Get replies to a comment | `commentId` or `commentUrl` |
| `harvest_get_job` | Get LinkedIn job | one of `jobId`, `url` |
| `harvest_search_jobs` | Search LinkedIn jobs | — |
| `harvest_search_leads` | Search leads (Sales Navigator) | — |
| `harvest_search_services` | Search LinkedIn services | `search` |
| `harvest_get_ad` | Get LinkedIn ad | one of `adId`, `url` |
| `harvest_search_ads` | Search LinkedIn ad library | — |
| `harvest_get_my_api_user` | Get your HarvestAPI user/account | — |
| `harvest_get_my_private_account_pools` | Get your private LinkedIn account pools | — |
Intentionally excluded (5 endpoints): `accept-connection`, `send-connection`, `send-message`, `received-connections`, `sent-connections` — they automate LinkedIn user actions. See [tool-mapping](docs/tool-mapping.md).
### Examples
**Profile lookup**
```json
{ "tool": "harvest_get_profile", "arguments": { "publicIdentifier": "williamhgates" } }
```
**Profile with enrichment flags**
```json
{ "tool": "harvest_get_profile", "arguments": { "url": "https://www.linkedin.com/in/williamhgates", "findEmail": "true", "skipSmtp": "true" } }
```
**Company**
```json
{ "tool": "harvest_get_company", "arguments": { "universalName": "google" } }
```
**Search profiles**
```json
{ "tool": "harvest_search_profiles", "arguments": { "search": "staff engineer", "currentCompany": "google", "page": "1" } }
```
**Post comments**
```json
{ "tool": "harvest_get_post_comments", "arguments": { "post": "https://www.linkedin.com/posts/.../..." } }
```
## Error & Rate-Limit Behavior
| HTTP | Behavior |
|---|---|
| 401/403 | `HarvestApiError` with hint — never retried. Check `HARVESTAPI_KEY`. |
| 404/422 | `HarvestApiError` — never retried. |
| 429 | `HarvestApiError` with `Retry-After` surfaced in message and `retryAfter` field. Retried with backoff respecting `Retry-After` up to `HARVESTAPI_MAX_RETRIES`. |
| 5xx | Retried with exponential backoff (400 ms × 2^attempt, max 5 s) up to `HARVESTAPI_MAX_RETRIES`. |
| timeout | `HarvestTimeoutError` — retried with backoff up to `HARVESTAPI_MAX_RETRIES`. |
HarvestAPI has no per-minute rate limit; concurrency limits are per plan (Free 1, Starter 5, Basic 10, Pro 20, Business 40) with queue size 10 — see https://docs.harvestapi.io/guides/concurrency.md. Exceeding queue returns an error; the client surfaces 429 with `Retry-After` when present.
Validation errors (missing required identifier, `requireAtLeastOne`) are local `HarvestValidationError` before any request.
Raw API payloads are preserved in responses. Email fields are only marked verified if the API says so.
## Development
```bash
npm ci
npm run lint # eslint
npm run typecheck # tsc --noEmit
npm run build # tsc
npm test # vitest
npm run test:coverage
npm run check # lint + typecheck + build + test
```
Needs Node 20+.
## Project Docs
- Endpoint inventory (30 endpoints): [`docs/harvestapi-endpoint-inventory.json`](docs/harvestapi-endpoint-inventory.json)
- Research notes & source URLs: [`docs/research-notes.md`](docs/research-notes.md)
- Tool mapping (Implemented / Intentionally Excluded): [`docs/tool-mapping.md`](docs/tool-mapping.md)
- Raw docs crawl: `docs/raw/*.md`
## Security
- `HARVESTAPI_KEY` is only read from env (`HARVESTAPI_KEY`). Never logged, never included in error bodies beyond redacted hints.
- Never commit `.env`. `.env.example` contains placeholders only. `.gitignore` covers `.env`, `node_modules`, `dist`, `coverage`.
- Report security issues via GitHub Issues (do not post keys in issues).
## Contributing
PRs welcome. Run `npm run check` before submitting. See `docs/research-notes.md` for how endpoints are sourced and validated.
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 25 tools
Each tool targets a distinct LinkedIn resource-action pair, with clear separation between searching and getting entities. Within sub-resources, getters for posts, comments, reactions, and replies are unambiguous.
All tools follow the harvest_<verb>_<resource> pattern, using only 'get' and 'search' as verbs. Multi-word resources are hyphenated consistently, and there are no mixed naming conventions.
25 tools is on the higher end but appropriate for the broad scope of LinkedIn entities covered (ads, companies, groups, jobs, leads, posts, profiles, services, and account info). Each tool adds a distinct capability.
The read-only harvesting scope is well covered, with both search and get operations for most entities. Minor gaps include the absence of get_lead and get_service endpoints, but search results likely satisfy most needs.