prospectapis-mcp 0.2.0
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/CHANGELOG.md +49 -0
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/package.json +46 -0
- package/src/index.js +29 -0
- package/src/server.js +107 -0
- package/src/tools.js +268 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `prospectapis-mcp` are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the package uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html), independently of the API's version.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.2.0] - 2026-10-02
|
|
10
|
+
|
|
11
|
+
First release published to npm (0.1.0 was never published).
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Licensed MIT; `private` removed; homepage https://prospectapis.com/mcp. The README configures the server
|
|
16
|
+
with `npx -y prospectapis-mcp`.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `watchlist_create`, `watchlist_list`, `watchlist_deliveries`, `watchlist_test` and `watchlist_delete`
|
|
21
|
+
(`/v1/watchlists`) create funding watchlists that post matching rounds to your webhook, list them, read their
|
|
22
|
+
delivery log, send a test delivery and delete them.
|
|
23
|
+
- `feedback_send` (`POST /v1/feedback`) reports a bug, a wrong field or a missing capability to the team.
|
|
24
|
+
- `watchlist_get` (`GET /v1/watchlists/:id`) reads one watchlist, without its secret, so every watchlist route has a
|
|
25
|
+
tool.
|
|
26
|
+
- `pricing_get` (`GET /pricing`, no key) returns the live price list, so an agent can quote a cost before a call.
|
|
27
|
+
- `research_submit` (`POST /v1/research`, the arguments sent as a JSON body) starts a prospect research brief and
|
|
28
|
+
returns its `research_id`; `research_get` (`GET /v1/research/:id`) polls it. `research_submit` is the package's
|
|
29
|
+
first tool that is not read-only, and its `purpose` is `account_research`, `pre_call` or `outreach`.
|
|
30
|
+
- A `503` from the API carries a hint, for research that is not priced yet.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- `funding_search` bounds `min_amount_usd` and `max_amount_usd` (0 to 999999999999999) and sends them as
|
|
35
|
+
fixed decimals with at most 4 places, so a float such as `0.1 + 0.2` no longer reaches the API as a value it
|
|
36
|
+
rejects.
|
|
37
|
+
|
|
38
|
+
## [0.1.0] - 2026-10-01
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- stdio MCP server built on `@modelcontextprotocol/sdk`, configured by `PROSPECTAPIS_API_KEY` and
|
|
43
|
+
`PROSPECTAPIS_BASE_URL` (default `https://api.prospectapis.com`).
|
|
44
|
+
- `funding_search` (`GET /v1/funding`, every filter plus cursor paging), `funding_get`
|
|
45
|
+
(`GET /v1/funding/:id`), `account_balance` (`GET /v1/account`) and `api_version` (`GET /v1/version`), all
|
|
46
|
+
read-only.
|
|
47
|
+
- API errors reach the model with the HTTP status, a short hint and the API's own body, so a rejected
|
|
48
|
+
filter is named and a 402 carries its top-up link.
|
|
49
|
+
- `createServer` exported from `prospectapis-mcp/server` for hosts that supply their own fetch or auth.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ProspectAPIs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# prospectapis-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for the ProspectAPIs funding data and research API. It gives an MCP client (Claude Desktop, Claude
|
|
4
|
+
Code, Cursor and others) fourteen tools over `https://api.prospectapis.com`, one per customer endpoint (the
|
|
5
|
+
liveness probe `GET /health` has none; `api_version` answers the same question). Five tools change state:
|
|
6
|
+
`research_submit` (starts a paid research job), `watchlist_create`, `watchlist_test`, `watchlist_delete` and
|
|
7
|
+
`feedback_send`; every other tool only reads.
|
|
8
|
+
|
|
9
|
+
| Tool | Endpoint | Cost |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `funding_search` | `GET /v1/funding` | $0.02 per record returned, after 100 free records a month; no match is free |
|
|
12
|
+
| `funding_get` | `GET /v1/funding/:id` | $0.02 when found; a missing id is free |
|
|
13
|
+
| `research_submit` | `POST /v1/research` | one research at `research_price_usd` (see `account_balance`), charged only when the brief completes; refunded on failure |
|
|
14
|
+
| `research_get` | `GET /v1/research/:id` | free; poll until `status` is `completed` or `failed` |
|
|
15
|
+
| `watchlist_create`, `watchlist_list`, `watchlist_get`, `watchlist_deliveries`, `watchlist_test`, `watchlist_delete` | `/v1/watchlists` | free to manage; a delivered event costs one record, only when your webhook answers 2xx |
|
|
16
|
+
| `account_balance` | `GET /v1/account` | free |
|
|
17
|
+
| `feedback_send` | `POST /v1/feedback` | free; reports a bug, wrong field or missing capability to the team (10 an hour per key) |
|
|
18
|
+
| `pricing_get` | `GET /pricing` | free, no key needed; the live price list |
|
|
19
|
+
| `api_version` | `GET /v1/version` | free, no key needed |
|
|
20
|
+
|
|
21
|
+
`funding_search` filters: `since`, `until` (YYYY-MM-DD), `round` (comma list such as `seed,series_a`),
|
|
22
|
+
`min_amount_usd`, `max_amount_usd`, `company`, `domain`, `investor`, `country`, `limit` (1 to 100) and
|
|
23
|
+
`cursor` (the previous page's `next_cursor`, passed back unchanged).
|
|
24
|
+
|
|
25
|
+
`research_submit` takes exactly one prospect identifier (`domain`, `website_url`, `company_name`,
|
|
26
|
+
`company_linkedin_url`, a work `email` or `linkedin_url`), or `person_name` with `company_name`, plus an optional
|
|
27
|
+
`context` (your own product, at most 1,000 characters) and `purpose` (`account_research`, `pre_call` or
|
|
28
|
+
`outreach`). It returns a `research_id` at once; a brief takes 20 to 90 seconds.
|
|
29
|
+
|
|
30
|
+
## Configure
|
|
31
|
+
|
|
32
|
+
| Variable | Required | Default |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `PROSPECTAPIS_API_KEY` | yes (all tools except `pricing_get` and `api_version`) | none |
|
|
35
|
+
| `PROSPECTAPIS_BASE_URL` | no | `https://api.prospectapis.com` |
|
|
36
|
+
|
|
37
|
+
Client config (for example `claude_desktop_config.json`):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"prospectapis": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "prospectapis-mcp"],
|
|
45
|
+
"env": { "PROSPECTAPIS_API_KEY": "pa_live_..." }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Claude Code: `claude mcp add prospectapis -e PROSPECTAPIS_API_KEY=pa_live_... -- npx -y prospectapis-mcp`.
|
|
52
|
+
|
|
53
|
+
## Develop
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd mcp
|
|
57
|
+
npm install
|
|
58
|
+
npm test # mocked fetch over an in-memory MCP transport, no network
|
|
59
|
+
npm run check
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The package is versioned independently of the API (SemVer, `CHANGELOG.md` in this folder). It is marked
|
|
63
|
+
`private` so it cannot be published by accident; publishing is done only from the product's own npm identity.
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "prospectapis-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for ProspectAPIs: search funding rounds, run paid prospect research briefs, manage funding watchlists, read your balance and the live price list.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"prospectapis-mcp": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.js",
|
|
12
|
+
"./server": "./src/server.js",
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"start": "node src/index.js",
|
|
26
|
+
"check": "node --check src/index.js && node --check src/server.js && node --check src/tools.js",
|
|
27
|
+
"test": "node --test \"test/**/*.test.js\""
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
|
+
"zod": "^3.25.0 || ^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"mcp",
|
|
35
|
+
"model-context-protocol",
|
|
36
|
+
"funding",
|
|
37
|
+
"venture-capital",
|
|
38
|
+
"prospecting",
|
|
39
|
+
"prospectapis",
|
|
40
|
+
"sales-research",
|
|
41
|
+
"agents"
|
|
42
|
+
],
|
|
43
|
+
"homepage": "https://prospectapis.com/mcp",
|
|
44
|
+
"author": "ProspectAPIs",
|
|
45
|
+
"license": "MIT"
|
|
46
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// stdio entry point. Configuration comes from the MCP client's environment:
|
|
3
|
+
// PROSPECTAPIS_API_KEY required for every tool except api_version
|
|
4
|
+
// PROSPECTAPIS_BASE_URL optional, default https://api.prospectapis.com
|
|
5
|
+
// Logs go to stderr: stdout is the protocol channel.
|
|
6
|
+
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { TOOLS } from "./tools.js";
|
|
9
|
+
import { createServer, DEFAULT_BASE_URL } from "./server.js";
|
|
10
|
+
|
|
11
|
+
const API_KEY = process.env.PROSPECTAPIS_API_KEY;
|
|
12
|
+
const BASE_URL = process.env.PROSPECTAPIS_BASE_URL || DEFAULT_BASE_URL;
|
|
13
|
+
|
|
14
|
+
if (!API_KEY) {
|
|
15
|
+
console.error(
|
|
16
|
+
"[prospectapis-mcp] PROSPECTAPIS_API_KEY is not set. Tools are registered, but every call except api_version will fail until it is.",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
const { server, baseUrl } = createServer({ apiKey: API_KEY, baseUrl: BASE_URL });
|
|
22
|
+
await server.connect(new StdioServerTransport());
|
|
23
|
+
console.error(`[prospectapis-mcp] ready, ${TOOLS.length} tools, base ${baseUrl}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
main().catch((err) => {
|
|
27
|
+
console.error("[prospectapis-mcp] fatal:", err);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
package/src/server.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// MCP server wiring: one registered tool per catalog entry, each a single
|
|
2
|
+
// authenticated request (a GET, or a POST with a JSON body for research_submit)
|
|
3
|
+
// against the ProspectAPIs REST API. Exported as a factory
|
|
4
|
+
// (createServer) so tests inject a fake fetch and a host could inject auth.
|
|
5
|
+
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { TOOLS, buildUrl, buildPath, buildBody } from "./tools.js";
|
|
9
|
+
|
|
10
|
+
export const { version: VERSION } = createRequire(import.meta.url)("../package.json");
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_BASE_URL = "https://api.prospectapis.com";
|
|
13
|
+
export const DEFAULT_TIMEOUT_MS = 30000;
|
|
14
|
+
export const SIGNUP_URL = "https://prospectapis.com/signup?utm_source=mcp";
|
|
15
|
+
export const TOP_UP_URL = "https://prospectapis.com/dashboard/credits?utm_source=mcp";
|
|
16
|
+
|
|
17
|
+
export const INSTRUCTIONS =
|
|
18
|
+
"ProspectAPIs MCP server: funding rounds data and prospect research briefs. funding_search and funding_get cost $0.02 per record returned, " +
|
|
19
|
+
"after 100 free records per calendar month; a search that matches nothing costs nothing. account_balance, pricing_get and api_version are free. " +
|
|
20
|
+
"Use filters (dates, round, amount, domain, investor, country) to keep result sets small, and call account_balance before a large paged search. " +
|
|
21
|
+
"research_submit starts a cited brief on one prospect and returns a research_id; poll research_get (free) until it is completed or failed. " +
|
|
22
|
+
"A research is charged only when it completes, at the research_price_usd account_balance reports. " +
|
|
23
|
+
"If a call fails in a way the user has to work around, a field comes back wrong or empty, or a capability is missing, " +
|
|
24
|
+
"offer to report it with feedback_send (free); send only when the user agrees.";
|
|
25
|
+
|
|
26
|
+
export function hintFor(status) {
|
|
27
|
+
if (status === 401) return " (missing or invalid API key: set PROSPECTAPIS_API_KEY to a key from the dashboard)";
|
|
28
|
+
// research never draws on free records, so the hint does not promise any (PR #23 review, L4)
|
|
29
|
+
if (status === 402) return ` (not enough credit for this call; the body says what it costs: top up at ${TOP_UP_URL})`;
|
|
30
|
+
if (status === 400) return " (an argument was rejected: the body says which one)";
|
|
31
|
+
if (status === 404) return " (not found: the id may be wrong, or the record is not published)";
|
|
32
|
+
if (status === 429) return " (rate limited: wait a few seconds and retry)";
|
|
33
|
+
if (status === 503) return " (unavailable: the body says why, for example research that is not priced yet)";
|
|
34
|
+
if (status >= 500) return " (server error: retry in a moment)";
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createServer({
|
|
39
|
+
apiKey,
|
|
40
|
+
baseUrl = DEFAULT_BASE_URL,
|
|
41
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
42
|
+
fetchImpl = fetch,
|
|
43
|
+
authHeaders = null,
|
|
44
|
+
} = {}) {
|
|
45
|
+
const BASE_URL = String(baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
46
|
+
const TIMEOUT = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
47
|
+
|
|
48
|
+
async function callEndpoint(tool, args) {
|
|
49
|
+
const needsKey = tool.auth !== false;
|
|
50
|
+
if (needsKey && !apiKey && !authHeaders) {
|
|
51
|
+
return {
|
|
52
|
+
isError: true,
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: `Missing PROSPECTAPIS_API_KEY. Create a key at ${SIGNUP_URL}, set it in the MCP client config, then retry this call.`,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const url = buildUrl(BASE_URL, tool, args);
|
|
62
|
+
const body = buildBody(tool, args);
|
|
63
|
+
const headers = {
|
|
64
|
+
accept: "application/json",
|
|
65
|
+
"user-agent": `prospectapis-mcp/${VERSION}`,
|
|
66
|
+
...(body !== null ? { "content-type": "application/json" } : {}),
|
|
67
|
+
...(needsKey ? authHeaders || { authorization: `Bearer ${apiKey}` } : {}),
|
|
68
|
+
};
|
|
69
|
+
const ctrl = new AbortController();
|
|
70
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetchImpl(url, {
|
|
73
|
+
method: tool.method || "GET",
|
|
74
|
+
headers,
|
|
75
|
+
...(body !== null ? { body } : {}),
|
|
76
|
+
signal: ctrl.signal,
|
|
77
|
+
});
|
|
78
|
+
const text = await res.text();
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
// The API's own error body is passed through: it names the bad filter,
|
|
81
|
+
// and a 402 carries top_up_url for the user.
|
|
82
|
+
return { isError: true, content: [{ type: "text", text: `HTTP ${res.status}${hintFor(res.status)}: ${text.slice(0, 1500)}` }] };
|
|
83
|
+
}
|
|
84
|
+
return { content: [{ type: "text", text }] };
|
|
85
|
+
} catch (err) {
|
|
86
|
+
const msg = err?.name === "AbortError" ? `timed out after ${TIMEOUT}ms` : err?.message || String(err);
|
|
87
|
+
return { isError: true, content: [{ type: "text", text: `Request failed: ${msg}` }] };
|
|
88
|
+
} finally {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const server = new McpServer({ name: "prospectapis", version: VERSION }, { instructions: INSTRUCTIONS });
|
|
94
|
+
for (const tool of TOOLS) {
|
|
95
|
+
server.registerTool(
|
|
96
|
+
tool.name,
|
|
97
|
+
{
|
|
98
|
+
description: tool.description,
|
|
99
|
+
inputSchema: tool.shape,
|
|
100
|
+
annotations: { title: tool.name, readOnlyHint: !tool.write, destructiveHint: false, openWorldHint: true },
|
|
101
|
+
},
|
|
102
|
+
(args) => callEndpoint(tool, args),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { server, callEndpoint, baseUrl: BASE_URL, timeoutMs: TIMEOUT, buildPath };
|
|
107
|
+
}
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Tool catalog and pure request builders. Kept apart from the server wiring so
|
|
2
|
+
// the catalog is unit-tested without a transport or a network.
|
|
3
|
+
//
|
|
4
|
+
// One tool per REST endpoint. Each entry is { name, path, method, shape, write }:
|
|
5
|
+
// - `shape` is the zod input schema; its keys map 1:1 to query parameters,
|
|
6
|
+
// except `{name}` placeholders in `path`, which are interpolated into the
|
|
7
|
+
// URL and removed from the query string;
|
|
8
|
+
// - `body: true` sends the arguments as a JSON body instead of a query
|
|
9
|
+
// string (research_submit, a POST);
|
|
10
|
+
// - `write: true` marks a tool that changes state or spends credits
|
|
11
|
+
// (research_submit starts a paid job); the server turns `write` into MCP's
|
|
12
|
+
// readOnlyHint, so every other tool is read-only;
|
|
13
|
+
// - `auth: false` marks the tools the API serves without a key (pricing_get, api_version).
|
|
14
|
+
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
// The API accepts an amount of at most 15 integer digits and 4 decimals
|
|
18
|
+
// (AMOUNT_RE in apps/api/src/services/funding.js). Numbers are bounded here and
|
|
19
|
+
// written as fixed decimals, so 0.1 + 0.2 or 1e21 can never reach the API as
|
|
20
|
+
// "0.30000000000000004" or "1e+21" and come back as a 400.
|
|
21
|
+
export const MAX_AMOUNT_USD = 999999999999999;
|
|
22
|
+
export function formatAmount(v) {
|
|
23
|
+
return Number(v).toFixed(4).replace(/\.?0+$/, "");
|
|
24
|
+
}
|
|
25
|
+
const AMOUNT = z.number().nonnegative().max(MAX_AMOUNT_USD).optional();
|
|
26
|
+
|
|
27
|
+
// The research purposes the API accepts (apps/api/src/services/research.js).
|
|
28
|
+
export const RESEARCH_PURPOSES = ["account_research", "pre_call", "outreach"];
|
|
29
|
+
const URL_ARG = z.string().min(1).max(2048);
|
|
30
|
+
|
|
31
|
+
const DATE = z
|
|
32
|
+
.string()
|
|
33
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, "YYYY-MM-DD")
|
|
34
|
+
.optional();
|
|
35
|
+
|
|
36
|
+
export const TOOLS = [
|
|
37
|
+
{
|
|
38
|
+
name: "funding_search",
|
|
39
|
+
method: "GET",
|
|
40
|
+
path: "/v1/funding",
|
|
41
|
+
write: false,
|
|
42
|
+
format: { min_amount_usd: formatAmount, max_amount_usd: formatAmount },
|
|
43
|
+
description:
|
|
44
|
+
"Search funding rounds (venture rounds, debt, grants, IPOs), newest announcement first. Costs $0.02 per record returned, after 100 free records a month; a search that matches nothing is free. " +
|
|
45
|
+
"Page with `cursor`: pass back `next_cursor` exactly as returned; a null `next_cursor` means there are no more matches. Narrow with filters before raising `limit`.",
|
|
46
|
+
shape: {
|
|
47
|
+
since: DATE.describe("Earliest announcement date, inclusive (YYYY-MM-DD)."),
|
|
48
|
+
until: DATE.describe("Latest announcement date, inclusive (YYYY-MM-DD)."),
|
|
49
|
+
round: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Comma-separated round stages, for example `seed,series_a,series_b`."),
|
|
53
|
+
min_amount_usd: AMOUNT.describe("Smallest round size in USD."),
|
|
54
|
+
max_amount_usd: AMOUNT.describe("Largest round size in USD."),
|
|
55
|
+
company: z.string().min(1).max(100).optional().describe("Company name contains this text (case-insensitive)."),
|
|
56
|
+
domain: z.string().min(3).max(253).optional().describe("Exact company domain, for example `example.com`."),
|
|
57
|
+
investor: z.string().min(1).max(100).optional().describe("Exact investor name, case-insensitive."),
|
|
58
|
+
country: z.string().min(1).max(60).optional().describe("Headquarters country, case-insensitive."),
|
|
59
|
+
limit: z.number().int().min(1).max(100).optional().describe("Records per page, 1 to 100 (default 25). Each record returned is billed."),
|
|
60
|
+
cursor: z.string().max(200).optional().describe("Opaque cursor from the previous page's `next_cursor`."),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "funding_get",
|
|
65
|
+
method: "GET",
|
|
66
|
+
path: "/v1/funding/{id}",
|
|
67
|
+
write: false,
|
|
68
|
+
description: "Read one funding record by id. Costs $0.02 when found (free records are used first); a missing id is free.",
|
|
69
|
+
shape: {
|
|
70
|
+
id: z.string().uuid().describe("The funding record id from a `funding_search` result."),
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "research_submit",
|
|
75
|
+
method: "POST",
|
|
76
|
+
path: "/v1/research",
|
|
77
|
+
write: true,
|
|
78
|
+
body: true,
|
|
79
|
+
description:
|
|
80
|
+
"Start a cited pre-call research brief on one prospect: company, funding, traction, competitors, news, people, risks, regions, " +
|
|
81
|
+
"plus a `fit` section when you pass `context`. Give exactly ONE identifier (domain, website_url, company_name, company_linkedin_url, " +
|
|
82
|
+
"a work email, or linkedin_url), or person_name together with company_name. Returns `research_id` at once; the brief takes 20 to 90 seconds, " +
|
|
83
|
+
"so poll research_get every 10 to 15 seconds until `status` is `completed` or `failed`. " +
|
|
84
|
+
"Costs one research at the price account_balance reports as `research_price_usd`, held when you submit, charged only when the brief " +
|
|
85
|
+
"completes and refunded in full if it fails. Free records do not apply.",
|
|
86
|
+
shape: {
|
|
87
|
+
domain: z.string().min(3).max(253).optional().describe("The company's domain, for example `acme.com`."),
|
|
88
|
+
website_url: URL_ARG.optional().describe("Any http(s) URL on the company's website."),
|
|
89
|
+
company_name: z.string().min(1).max(200).optional().describe("The company's name. With person_name, the company that person works at."),
|
|
90
|
+
company_linkedin_url: URL_ARG.optional().describe("The company's linkedin.com/company/... URL, used as a name hint only."),
|
|
91
|
+
email: z.string().min(3).max(254).optional().describe("A work email; its domain identifies the company. Free-mail addresses are refused."),
|
|
92
|
+
person_name: z.string().min(1).max(200).optional().describe("A person's name; needs company_name as well."),
|
|
93
|
+
linkedin_url: URL_ARG.optional().describe("A person's linkedin.com/in/... URL, used as a name hint only."),
|
|
94
|
+
context: z.string().min(1).max(1000).optional().describe("Your own product, at most 1,000 characters. Turns on the `fit` section."),
|
|
95
|
+
purpose: z
|
|
96
|
+
.enum(RESEARCH_PURPOSES)
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("What the brief is for: `account_research` (default), `pre_call` (objections, discovery questions) or `outreach` (sourced hooks with draft first lines)."),
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "research_get",
|
|
103
|
+
method: "GET",
|
|
104
|
+
path: "/v1/research/{id}",
|
|
105
|
+
write: false,
|
|
106
|
+
description:
|
|
107
|
+
"Read a research job by the `research_id` research_submit returned: `status` (queued, running, completed, failed), `input`, " +
|
|
108
|
+
"`result` (the brief, once completed) and `error` (once failed). Free; only your own account's jobs are visible.",
|
|
109
|
+
shape: {
|
|
110
|
+
id: z.string().uuid().describe("The `research_id` from research_submit."),
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "watchlist_create",
|
|
115
|
+
method: "POST",
|
|
116
|
+
path: "/v1/watchlists",
|
|
117
|
+
body: true,
|
|
118
|
+
write: true,
|
|
119
|
+
description:
|
|
120
|
+
"Save a funding-signal alert: a set of funding_search filters plus an https webhook. Each NEW funding event that " +
|
|
121
|
+
"matches is POSTed to the webhook, signed with the returned `secret` (header Prospect-Signature: t=..,v1=HMAC-SHA256). " +
|
|
122
|
+
"Creating is free; each delivered event costs one record ($0.02, free records first), charged only when the webhook " +
|
|
123
|
+
"answers 2xx. The `secret` is shown once. Max 20 active per account.",
|
|
124
|
+
shape: {
|
|
125
|
+
name: z.string().min(1).max(100).describe("A label, such as 'Series A robotics'."),
|
|
126
|
+
filters: z
|
|
127
|
+
.object({
|
|
128
|
+
round: z.string().optional(),
|
|
129
|
+
min_amount_usd: z.number().nonnegative().optional(),
|
|
130
|
+
max_amount_usd: z.number().nonnegative().optional(),
|
|
131
|
+
company: z.string().optional(),
|
|
132
|
+
domain: z.string().optional(),
|
|
133
|
+
investor: z.string().optional(),
|
|
134
|
+
country: z.string().optional(),
|
|
135
|
+
})
|
|
136
|
+
.describe("At least one funding_search filter; date windows and paging do not apply to alerts."),
|
|
137
|
+
webhook_url: z.string().url().max(2048).describe("Your https endpoint on a public host."),
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "watchlist_list",
|
|
142
|
+
method: "GET",
|
|
143
|
+
path: "/v1/watchlists",
|
|
144
|
+
write: false,
|
|
145
|
+
description: "Your funding-signal alerts (without their secrets). Free.",
|
|
146
|
+
shape: {},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "watchlist_get",
|
|
150
|
+
method: "GET",
|
|
151
|
+
path: "/v1/watchlists/{id}",
|
|
152
|
+
write: false,
|
|
153
|
+
description: "One funding-signal alert: its name, filters, webhook URL, whether it is active and why it was paused (never its secret). Free.",
|
|
154
|
+
shape: { id: z.string().uuid().describe("The watchlist id.") },
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "watchlist_deliveries",
|
|
158
|
+
method: "GET",
|
|
159
|
+
path: "/v1/watchlists/{id}/deliveries",
|
|
160
|
+
write: false,
|
|
161
|
+
description: "The last 50 deliveries of one alert and their outcome (delivered, retry, failed, no_credit). Free.",
|
|
162
|
+
shape: { id: z.string().uuid().describe("The watchlist id.") },
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "watchlist_test",
|
|
166
|
+
method: "POST",
|
|
167
|
+
path: "/v1/watchlists/{id}/test",
|
|
168
|
+
body: true,
|
|
169
|
+
write: true,
|
|
170
|
+
description: "Send a signed sample event to the alert's webhook now, to build and verify the receiver. Free; 5 a minute.",
|
|
171
|
+
shape: { id: z.string().uuid().describe("The watchlist id.") },
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "watchlist_delete",
|
|
175
|
+
method: "DELETE",
|
|
176
|
+
path: "/v1/watchlists/{id}",
|
|
177
|
+
write: true,
|
|
178
|
+
description: "Delete a funding-signal alert and its delivery history. Free.",
|
|
179
|
+
shape: { id: z.string().uuid().describe("The watchlist id.") },
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "account_balance",
|
|
183
|
+
method: "GET",
|
|
184
|
+
path: "/v1/account",
|
|
185
|
+
write: false,
|
|
186
|
+
description: "Your credit balance and the free records left this month. Free. Check it before a large paged search.",
|
|
187
|
+
shape: {},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "feedback_send",
|
|
191
|
+
method: "POST",
|
|
192
|
+
path: "/v1/feedback",
|
|
193
|
+
body: true,
|
|
194
|
+
write: true,
|
|
195
|
+
description:
|
|
196
|
+
"Report a bug, a wrong or empty field, a missing capability or a question about ProspectAPIs to the team. Free; " +
|
|
197
|
+
"limited to 10 delivered reports an hour per account (a 429 means wait, do not retry in a loop). Before reporting that a field is wrong or a parameter is ignored, re-run the " +
|
|
198
|
+
"call with a value that could only match if it were honoured. Put the exact call, what came back and what you " +
|
|
199
|
+
"expected in `details`; never include secrets or API keys. Send only when the user agrees.",
|
|
200
|
+
shape: {
|
|
201
|
+
kind: z.enum(["bug", "feature", "question"]).optional().describe("bug (default), feature or question."),
|
|
202
|
+
title: z.string().min(1).max(200).describe("One line: what is wrong or missing."),
|
|
203
|
+
details: z.string().min(1).max(8000).describe("The exact call, what came back, what you expected, and ids such as research_id."),
|
|
204
|
+
tool: z.string().regex(/^[a-z][a-z0-9_]{0,59}$/).optional().describe("The tool the report is about, such as research_get."),
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "pricing_get",
|
|
209
|
+
method: "GET",
|
|
210
|
+
path: "/pricing",
|
|
211
|
+
write: false,
|
|
212
|
+
auth: false,
|
|
213
|
+
description:
|
|
214
|
+
"The live price list: funding records (price per record and free records a month), research (whether it is " +
|
|
215
|
+
"available and its price per brief) and watchlist deliveries. Free, needs no key. Use it to quote a cost before a large call.",
|
|
216
|
+
shape: {},
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "api_version",
|
|
220
|
+
method: "GET",
|
|
221
|
+
path: "/v1/version",
|
|
222
|
+
write: false,
|
|
223
|
+
auth: false,
|
|
224
|
+
description: "The API's version and deployed build. Free, needs no key.",
|
|
225
|
+
shape: {},
|
|
226
|
+
},
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
export function buildQuery(args) {
|
|
230
|
+
const qs = new URLSearchParams();
|
|
231
|
+
for (const [k, v] of Object.entries(args || {})) {
|
|
232
|
+
if (v !== undefined && v !== null && String(v).length > 0) qs.set(k, String(v));
|
|
233
|
+
}
|
|
234
|
+
return qs.toString();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function buildPath(template, args) {
|
|
238
|
+
const used = new Set();
|
|
239
|
+
const path = String(template).replace(/\{(\w+)\}/g, (_, k) => {
|
|
240
|
+
used.add(k);
|
|
241
|
+
const v = args?.[k];
|
|
242
|
+
return encodeURIComponent(v == null ? "" : String(v));
|
|
243
|
+
});
|
|
244
|
+
const rest = {};
|
|
245
|
+
for (const [k, v] of Object.entries(args || {})) if (!used.has(k)) rest[k] = v;
|
|
246
|
+
return { path, rest };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Full request URL for a tool call. A `body` tool sends no query string. */
|
|
250
|
+
export function buildUrl(baseUrl, tool, args) {
|
|
251
|
+
const { path, rest } = buildPath(tool.path, args);
|
|
252
|
+
for (const [k, fmt] of Object.entries(tool.format || {})) {
|
|
253
|
+
if (rest[k] !== undefined && rest[k] !== null) rest[k] = fmt(rest[k]);
|
|
254
|
+
}
|
|
255
|
+
const q = tool.body ? "" : buildQuery(rest);
|
|
256
|
+
return `${String(baseUrl).replace(/\/+$/, "")}${path}${q ? `?${q}` : ""}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** The JSON body for a `body` tool (null for every other tool): the arguments not used in the path, empty ones dropped. */
|
|
260
|
+
export function buildBody(tool, args) {
|
|
261
|
+
if (!tool.body) return null;
|
|
262
|
+
const { rest } = buildPath(tool.path, args);
|
|
263
|
+
const out = {};
|
|
264
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
265
|
+
if (v !== undefined && v !== null && !(typeof v === "string" && v.length === 0)) out[k] = v;
|
|
266
|
+
}
|
|
267
|
+
return JSON.stringify(out);
|
|
268
|
+
}
|