# MCP

The Signals MCP server. One HTTP POST speaks the MCP streamable-HTTP protocol and answers with a server-sent event stream. GET, PUT, PATCH and DELETE answer **405** with `Allow: POST`, even when the key is missing and even while the API is closed. POST without a key, or with an invalid, revoked or `ejk_` key, is **401**. A valid `gtk_` key while the Signals API is closed answers **404**, the same body as an unknown path.

The Signals MCP server. One HTTP POST speaks the MCP streamable-HTTP protocol and answers with a server-sent event stream. GET, PUT, PATCH and DELETE answer **405** with `Allow: POST`, even when the key is missing and even while the API is closed. POST without a key, or with an invalid, revoked or `ejk_` key, is **401**. A valid `gtk_` key while the Signals API is closed answers **404**, the same body as an unknown path.

The assistant server at `/api/mcp` (key prefix `ejk_`) is a different server. Connect it from [Connect your assistant](/docs/getting-started/quickstart). An `ejk_` key cannot call this URL.

## Authentication

Send `Authorization: Bearer gtk_…`. Create the key under **API Access → API Keys**. A missing, invalid or revoked key is **401**, including while this API is closed. A valid key while the API is closed for your organization answers **404**, the same body as an unknown path. Every response includes `FounderMCP-Api-Version` and `FounderMCP-Request-Id`. The response is an SSE stream, not a single JSON body. Initialize, then call `tools/list` and `tools/call`.

## Response

- **`event stream`** `text/event-stream` · **required** — MCP JSON-RPC messages. A `tools/call` result carries the REST body plus `dashboard_url` where the tool has one.

## Credits

The HTTP call is free. A tool spends the credits of the REST endpoint it calls. `preview_cost`, `lookup_company`, `explain_lead`, `get_search_results` and `list_mentions` spend nothing.

## Errors

- **405** `-32000` — GET, PUT, PATCH and DELETE answer a JSON-RPC error and `Allow: POST`, before the key is checked and while the API is closed. POST without a key is 401.
- **401** `authentication_error` — The Authorization header is missing, or the key is unknown or revoked.
- **403** `permission_error` — API access is not part of your plan, or your account is suspended.
- **404** `not_found` — The endpoint does not exist.
- **429** `rate_limit_error` — More than 60 requests per minute, or more than 3 at the same time, on one key. Wait for the Retry-After header.
- **500** `internal_error` — Something went wrong on our side.

## Example

### cURL

```bash
curl -X POST "https://foundermcp.dev/api/v1/mcp" \
  -H "Authorization: Bearer $FOUNDERMCP_API_KEY"
```

### JavaScript

```javascript
const response = await fetch('https://foundermcp.dev/api/v1/mcp', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FOUNDERMCP_API_KEY}`
  }
});
const body = await response.json();
```

### Python

```python
import json
import os
import urllib.request

request = urllib.request.Request(
    "https://foundermcp.dev/api/v1/mcp",
    headers={
    "Authorization": f"Bearer {os.environ['FOUNDERMCP_API_KEY']}"
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    body = json.load(response)
```

### Example response

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "lookup_company"
      },
      {
        "name": "find_leads"
      },
      {
        "name": "explain_lead"
      },
      {
        "name": "watch_signal"
      },
      {
        "name": "enrich"
      },
      {
        "name": "preview_cost"
      },
      {
        "name": "search_signals"
      },
      {
        "name": "get_search_results"
      },
      {
        "name": "company_leads"
      },
      {
        "name": "create_monitor"
      },
      {
        "name": "list_mentions"
      }
    ]
  }
}
```
## preview_cost and max_credits

`preview_cost` is free. It does not call a source. Send `endpoint` (`searches`, `leads`, `person.enrich` or `monitors`) and `body`, the JSON the real call would use. The result is a `cost_preview`: `credits_per_row`, `max_rows`, `max_credits` and `period` (`call` or `day`). The REST equivalents are `POST /searches/preview`, `POST /leads/preview`, `POST /person/enrich/preview` and `POST /monitors/preview`.

`max_credits` is an integer cap on the real call. The call stops before the next row would pass it. That row is left out (`truncated_by` `max_credits`) and is not charged. Below the cost of one row the call is `402` `max_credits_exceeded` and nothing runs. On people search and people enrich the field is accepted only when the Signals API is open; otherwise it is an unknown field. A monitor uses `max_credits_per_day` instead.

## Tools

The server name is `foundermcp`. `tools/list` returns these eleven tools. Each one calls the REST endpoint below with your `gtk_` key. A result that has `dashboard_url` opens the same search, watch, monitor or leads in FounderMCP (`/en/gtm/leads` or `/en/gtm/signals`).

### lookup_company

Free. Input: `name` (the company name, or a LinkedIn company page URL). Output: `companies` whose name matches, or an empty list and a message. REST: `GET /companies/lookup`.

### find_leads

Screened leads: FounderMCP’s own AI checks every person against `buyer_profile` (and, for listening and events, every post against `about`). For the same signals without AI, use `search_signals`. **20 credits** per exact lead, **5** per lead just outside. A search that finds nobody costs nothing. `tech_adoption` is not here: it has no screened mode. Input: `signal`, `buyer_profile`, optional `exclude_companies`, `max_leads`, `just_outside`, `max_credits`. To continue, send `search_id` instead of `signal`, and `event_id` when the user picks an event. Output: leads with `lead_id`, name, title, company, `match`, `why_fit`, `why_now`, `evidence_url` and `dashboard_url`. Still running: `search_id` and a poll hint. An event name with several matches: `requires_action` and the candidates. REST: `POST /searches` with `mode` `screened`, then `GET /searches/{search_id}/leads`.

### explain_lead

Free. Input: `lead_id` (`lead_` from `find_leads` or `watch_signal`, not a `per_` id). Output: the person, `fit`, `why_now` and the evidence, plus `dashboard_url`. REST: `GET /leads/{lead_id}`.

### watch_signal

A run charges like `find_leads`, up to `max_credits_per_month`. Creating, reading and pausing are free. Input: `action` `create`, `status`, `pause` or `resume`. Create needs `signal`, `schedule` (except an event, which can follow its dates), and `max_credits_per_month`. Optional: `name`, `buyer_profile`, `exclude_companies`, `max_leads_per_run`, `just_outside`. Status, pause and resume need `watch_id` (`wtch_`). Output: the watch and `dashboard_url`. Status of one watch also includes `latest_leads`. REST: `POST /watches`, `GET /watches`, `GET /watches/{id}`, `PATCH /watches/{id}`.

### enrich

A profile is **2 credits** when one is found. `include_email: true` adds **10** when an address is found. A company is **1 credit** when one is found. A miss is free. There is no phone field. Input: exactly one of `profile_urls`, `person_ids`, `lead_id`, or company `domains` / `linkedin_urls` / `names`. With `lead_id`, `target` is `person` (default) or `company`. Optional: `include_email`, `fields`, `exact_match`, `max_credits`. Output: the enrich body for that person or company. REST: `POST /person/enrich` or `POST /company/enrich`.

### preview_cost

Free. Input: `endpoint` and `body`, optional `max_credits`. Output: `cost_preview`. REST: the `/preview` route for that endpoint.

### search_signals

Raw rows: what the source shows, with no AI judging them. A person from engagement, listening, an event or a new role is **5 credits**, a funding company **1 credit**, a technology company **20**. Input: `signal` (every type; `tech_adoption` is raw only), optional `limit`, `cursor` (the `result.next_cursor` of an earlier answer, with the same signal), `exclude_companies` and `max_credits`. Output: `result` (`rows`, `has_more`, `next_cursor`), the first 25 rows in `results`, `next_page_cursor` for the rest, `notes`, `funnel` and `dashboard_url`. A LinkedIn row is a person with their headline and up to 3 things they did, each with the post: the assistant decides which rows fit, then enriches those with `person_ids`. `about` and `drop_intents` only work in `find_leads`. An event is never looked up: send its exact name with the year, its hashtags or its organizer page. REST: `POST /searches` (mode `raw` is the default), then `GET /searches/{search_id}/results`.

### get_search_results

Free. Input: `search_id` (`srch_` from `search_signals`), optional `page_cursor` (the `next_page_cursor` of the previous page). Output: 25 rows of that raw search per page, `next_page_cursor` and `dashboard_url`. Also reads a search that was still running when `search_signals` answered. REST: `GET /searches/{search_id}/results`.

### company_leads

**5 credits** per person. A company that does not match costs nothing. Input: `companies`, `filters` (`roles`, `seniority`, `departments`, `countries`), optional `limit` and `max_credits`. Output: `per_` rows and `dashboard_url`. Do not pass those ids to `explain_lead`. REST: `POST /leads`.

### create_monitor

Creating it is free. Each new mention on a run is **5 credits**. Input: `name`, `keywords`, optional `schedule` (`daily`; `hourly` stays off unless that deployment has turned it on), `min_relevance`, `tags`, `max_credits_per_day`. Output: the monitor and `dashboard_url`. REST: `POST /monitors`.

### list_mentions

Free. Input: optional `monitor_id` (`mon_`) and `min_relevance`. Output: mentions already delivered. REST: `GET /mentions`.

