Skip to main content
Glama
Hassan220022

harvestapi-mcp

by Hassan220022
README.md
# harvestapi-mcp

[![CI](https://github.com/Hassan220022/harvestapi-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Hassan220022/harvestapi-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Node >=20](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
[![MCP](https://img.shields.io/badge/MCP-stdio-blue)](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

A3.7/5.0

Scored across 25 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count4/5

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.

Completeness4/5

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.

Maintenance

ActivitySlowing
ResponsivenessNo issues