prospectapis-mcp 0.2.4 → 0.2.5

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 CHANGED
@@ -6,6 +6,17 @@ All notable changes to `prospectapis-mcp` are documented here. The format follow
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.5] - 2026-10-20
10
+
11
+ ### Added
12
+
13
+ - Four playbooks, served as MCP resources (resources/list, resources/read) and as one prompt each (prompts/list, prompts/get), so a client can offer them as slash commands: newly funded prospects, pre-call brief, funding alerts to a webhook, and an investor's recent rounds. A prompt hands its playbook over as an embedded resource, so the recipe matches what resources/read serves, and a client that omits the optional `arguments` still gets it. Tests hold every name a playbook uses to the tool catalog.
14
+
15
+ ### Changed
16
+
17
+ - The README's first sentence now names the product URL (https://prospectapis.com) and who it is for, because directory cards and search snippets copy that sentence alone and were linking GitHub only. A test pins both.
18
+ - Added CODE_OF_CONDUCT.md (Contributor Covenant 2.1), which open-source programs ask for.
19
+
9
20
  ## [0.2.4] - unreleased
10
21
 
11
22
  ### Fixed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # prospectapis-mcp
2
2
 
3
- prospectapis-mcp is the MCP server for ProspectAPIs, built for sales teams and their AI agents in Claude Code, Claude Desktop, Cursor and other MCP clients. It gives your agent fourteen tools over the ProspectAPIs API: search funding rounds by date, round, amount, company, investor and country, get new matching rounds pushed to your webhook, and order a cited research brief on any company or person. Funding records carry the public announcement URL when there is one. Records cost $0.02 each after 100 free a month, a completed brief costs $0.15, and there are no seats, no subscription and no card to sign up. It needs Node.js 22 or later.
3
+ prospectapis-mcp is the MCP server for ProspectAPIs (https://prospectapis.com), for sales and prospecting teams and the AI agents they run in Claude Code, Claude Desktop, Cursor and other MCP clients. It gives your agent fourteen tools over the ProspectAPIs API: search funding rounds by date, round, amount, company, investor and country, get new matching rounds pushed to your webhook, and order a cited research brief on any company or person. Funding records carry the public announcement URL when there is one. Records cost $0.02 each after 100 free a month, a completed brief costs $0.15, and there are no seats, no subscription and no card to sign up. It needs Node.js 22 or later.
4
4
 
5
5
  Install (Claude Code): `claude mcp add prospectapis -e PROSPECTAPIS_API_KEY=pa_live_... -- npx -y prospectapis-mcp`
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prospectapis-mcp",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "mcpName": "io.github.ProspectAPIs/prospectapis-mcp",
5
5
  "description": "MCP server for ProspectAPIs: search funding rounds, run paid prospect research briefs, manage funding watchlists, read your balance and the live price list.",
6
6
  "type": "module",
@@ -0,0 +1,113 @@
1
+ // Playbooks: short recipes an agent runs with the tools in src/tools.js, served over the MCP RESOURCE primitive
2
+ // (resources/list, resources/read) and offered as one PROMPT each (prompts/list, prompts/get), so a client can show
3
+ // them as slash commands.
4
+ //
5
+ // WHY RESOURCES AND PROMPTS, NOT TOOLS. Every tool in src/tools.js is one REST endpoint (test/api-parity.test.js
6
+ // holds the catalog to the API). A recipe wraps no endpoint; it is read-only context an agent fetches when it needs
7
+ // it, which is what resources are for, and a prompt is how a client offers that recipe to a user.
8
+ //
9
+ // A DESCRIPTION IS LISTED, A BODY IS FETCHED. Clients show the title and description beside every tool, so those
10
+ // state what the recipe does in plain product terms and never instruct the model. The body is returned only for a
11
+ // URI or prompt the caller asked for by name, so it names tools freely.
12
+ //
13
+ // Every figure in a body (prices, page size, alert limit, poll interval) is the one the tool descriptions in
14
+ // src/tools.js state, checked by review, not by a test. test/playbooks.test.js fails if a body names a tool,
15
+ // parameter, value or response field the catalog does not have.
16
+
17
+ export const PLAYBOOK_MIME = "text/markdown";
18
+
19
+ const NEWLY_FUNDED = `# Newly funded prospects
20
+
21
+ Build a short list of companies that just raised, worth reaching out to while the round is fresh.
22
+
23
+ 1. \`account_balance\` first. Each funding record returned costs $0.02 after 100 free a month, so know what is left
24
+ before you pull pages.
25
+ 2. \`funding_search\` with a tight window and the filters that describe the buyer: \`since\` (for example the last 30
26
+ days), \`round\` (such as \`seed,series_a\`), \`country\`, and \`min_amount_usd\` or \`max_amount_usd\`. Start with
27
+ \`limit\` 10 to 25. A search that matches nothing costs nothing, so narrow before you widen.
28
+ 3. Read the results. Each record has the company, its domain, the round, the amount, the investors and, when there is
29
+ one, the URL of the public announcement. Drop companies without a domain or outside the buyer profile.
30
+ 4. Page with \`cursor\` only if the first page is all good fits; pass \`next_cursor\` back exactly as returned. A null
31
+ \`next_cursor\` means there are no more matches.
32
+ 5. For the two or three best fits, run the pre-call brief playbook (\`research_submit\` with \`domain\`).
33
+ 6. Present the list as company, domain, round, amount, date, lead investors and the announcement URL where there is
34
+ one, so every line can be checked.
35
+ `;
36
+
37
+ const PRE_CALL_BRIEF = `# Pre-call brief
38
+
39
+ A cited brief on one company or person before a call or a first email.
40
+
41
+ 1. \`research_submit\` with exactly ONE identifier: \`domain\` is best; otherwise \`website_url\`, \`company_name\`, a work
42
+ \`email\`, or \`person_name\` together with \`company_name\`. Set \`purpose\` to \`pre_call\` for objections and discovery
43
+ questions, or \`outreach\` for sourced hooks with draft first lines. Add \`context\` (your own product, up to 1,000
44
+ characters) to get a \`fit\` section.
45
+ 2. It returns \`research_id\` at once. The brief takes 20 to 90 seconds: call \`research_get\` with that id every 10 to 15
46
+ seconds until \`status\` is \`completed\` or \`failed\`. Polling is free.
47
+ 3. A brief is charged only when it completes, at the \`research_price_usd\` that \`account_balance\` reports, and refunded
48
+ in full if it fails. Free records do not apply to research.
49
+ 4. Every field in \`result\` is \`{value, sources, confidence}\`. Quote the sources with the claims you use, and say when
50
+ a field came back empty rather than filling the gap.
51
+ `;
52
+
53
+ const FUNDING_ALERTS = `# Funding alerts to a webhook
54
+
55
+ Get every new funding round that matches a profile pushed to your own endpoint, instead of searching again.
56
+
57
+ 1. Decide the filters with \`funding_search\` first (\`round\`, \`country\`, \`min_amount_usd\`, \`max_amount_usd\`,
58
+ \`investor\`, \`company\` or \`domain\`), and check the results look right. Date windows and paging do not apply to an
59
+ alert.
60
+ 2. \`watchlist_create\` with a \`name\`, those \`filters\` and an https \`webhook_url\` on a public host. Save the \`secret\`
61
+ it returns: it is shown once, and every delivery is signed with it in the Prospect-Signature header
62
+ (t=..,v1=HMAC-SHA256). Creating is free; each delivered event costs one record, charged only when your webhook
63
+ answers 2xx. An account can have 20 active alerts.
64
+ 3. \`watchlist_test\` sends a signed sample event now, so the receiver can be built and checked before a real round
65
+ arrives. Free, 5 a minute.
66
+ 4. Later, \`watchlist_deliveries\` shows the last 50 deliveries and their outcome (delivered, retry, failed,
67
+ no_credit), and \`watchlist_get\` shows whether the alert is active and why it was paused.
68
+ `;
69
+
70
+ const INVESTOR_ROUNDS = `# An investor's recent rounds
71
+
72
+ See which companies an investor has backed lately, to find warm introductions or a portfolio to sell into.
73
+
74
+ 1. \`funding_search\` with \`investor\` set to the investor's exact name (case-insensitive) and a \`since\` date, newest
75
+ first. Add \`round\` to keep to one stage.
76
+ 2. Each record lists every investor named and the lead investors, so you can tell a lead from a participation.
77
+ 3. Group the results by round and by country, and give the announcement URL with each line that has one.
78
+ 4. To follow this investor from now on, run the funding alerts playbook with \`investor\` as the filter.
79
+ `;
80
+
81
+ export const PLAYBOOKS = Object.freeze([
82
+ {
83
+ name: "newly-funded-prospects",
84
+ uri: "prospectapis://playbooks/newly-funded-prospects",
85
+ title: "Newly funded prospects",
86
+ description: "Build a short list of companies that raised recently, filtered by round, country and amount, with the announcement for each.",
87
+ text: NEWLY_FUNDED,
88
+ prompt: { arg: "profile", describe: "Who you sell to, for example 'seed and Series A fintech in the US'.", ask: (v) => `Run this playbook for: ${v}` },
89
+ },
90
+ {
91
+ name: "pre-call-brief",
92
+ uri: "prospectapis://playbooks/pre-call-brief",
93
+ title: "Pre-call brief",
94
+ description: "A cited research brief on one company or person before a call or a first email.",
95
+ text: PRE_CALL_BRIEF,
96
+ prompt: { arg: "prospect", describe: "The company domain, company name, or person and company.", ask: (v) => `Run this playbook for: ${v}` },
97
+ },
98
+ {
99
+ name: "funding-alerts",
100
+ uri: "prospectapis://playbooks/funding-alerts",
101
+ title: "Funding alerts to a webhook",
102
+ description: "Push every new funding round that matches a profile to your own https endpoint, signed, instead of searching again.",
103
+ text: FUNDING_ALERTS,
104
+ },
105
+ {
106
+ name: "investor-rounds",
107
+ uri: "prospectapis://playbooks/investor-rounds",
108
+ title: "An investor's recent rounds",
109
+ description: "The companies one investor has backed lately, split into lead and participation, with sources.",
110
+ text: INVESTOR_ROUNDS,
111
+ prompt: { arg: "investor", describe: "The investor's name, for example 'Example Ventures'.", ask: (v) => `Run this playbook for: ${v}` },
112
+ },
113
+ ]);
package/src/server.js CHANGED
@@ -6,6 +6,9 @@
6
6
  import { createRequire } from "node:module";
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { TOOLS, buildUrl, buildPath, buildBody, annotationsFor } from "./tools.js";
9
+ import { z } from "zod";
10
+ import { GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
11
+ import { PLAYBOOKS, PLAYBOOK_MIME } from "./playbooks.js";
9
12
 
10
13
  export const { version: VERSION } = createRequire(import.meta.url)("../package.json");
11
14
 
@@ -91,6 +94,52 @@ export function createServer({
91
94
  }
92
95
 
93
96
  const server = new McpServer({ name: "prospectapis", version: VERSION }, { instructions: INSTRUCTIONS });
97
+
98
+ // Playbooks (src/playbooks.js): each one a resource (resources/list, resources/read) and a prompt (prompts/list,
99
+ // prompts/get). The prompt hands the playbook over as an embedded resource, so the recipe a user picks is exactly
100
+ // the one resources/read serves. Registering either declares that capability, which a server with none does not
101
+ // advertise.
102
+ for (const p of PLAYBOOKS) {
103
+ server.registerResource(
104
+ p.name,
105
+ p.uri,
106
+ { title: p.title, description: p.description, mimeType: PLAYBOOK_MIME },
107
+ async (uri) => ({ contents: [{ uri: uri.href, mimeType: PLAYBOOK_MIME, text: p.text }] }),
108
+ );
109
+ }
110
+ // `arguments` is optional on prompts/get (MCP spec), but the SDK validates a prompt that declares arguments against
111
+ // a required object, so a client that leaves it out would get -32602 for an argument it was told is optional. The
112
+ // SDK installs its prompts/get handler through the low-level Server the first time a prompt is registered; wrapping
113
+ // setRequestHandler before that defaults a missing `arguments` to {} and leaves every other request untouched.
114
+ {
115
+ const low = server.server;
116
+ const setRequestHandler = low.setRequestHandler.bind(low);
117
+ low.setRequestHandler = (schema, handler) =>
118
+ setRequestHandler(
119
+ schema,
120
+ schema === GetPromptRequestSchema
121
+ ? (request, extra) => handler({ ...request, params: { ...request.params, arguments: request.params?.arguments ?? {} } }, extra)
122
+ : handler,
123
+ );
124
+ }
125
+ for (const p of PLAYBOOKS) {
126
+ const argsSchema = p.prompt ? { [p.prompt.arg]: z.string().max(200).optional().describe(p.prompt.describe) } : undefined;
127
+ server.registerPrompt(
128
+ p.name,
129
+ { title: p.title, description: p.description, ...(argsSchema ? { argsSchema } : {}) },
130
+ async (args = {}) => {
131
+ const v = p.prompt ? String(args[p.prompt.arg] ?? "").trim() : "";
132
+ return {
133
+ description: p.description,
134
+ messages: [
135
+ { role: "user", content: { type: "resource", resource: { uri: p.uri, mimeType: PLAYBOOK_MIME, text: p.text } } },
136
+ { role: "user", content: { type: "text", text: p.prompt && v ? p.prompt.ask(v) : "Run this playbook." } },
137
+ ],
138
+ };
139
+ },
140
+ );
141
+ }
142
+
94
143
  for (const tool of TOOLS) {
95
144
  server.registerTool(
96
145
  tool.name,
package/src/tools.js CHANGED
@@ -62,7 +62,7 @@ export const TOOLS = [
62
62
  company: z.string().min(1).max(100).optional().describe("Company name contains this text (case-insensitive)."),
63
63
  domain: z.string().min(3).max(253).optional().describe("Exact company domain, for example `example.com`."),
64
64
  investor: z.string().min(1).max(100).optional().describe("Exact investor name, case-insensitive."),
65
- country: z.string().min(1).max(60).optional().describe("Headquarters country, case-insensitive."),
65
+ country: z.string().min(1).max(60).optional().describe("Headquarters country as a name or ISO code, such as US, United States or DE. Other text, such as a city, matches the stated location."),
66
66
  limit: z.number().int().min(1).max(100).optional().describe("Records per page, 1 to 100 (default 25). Each record returned is billed."),
67
67
  cursor: z.string().max(200).optional().describe("Opaque cursor from the previous page's `next_cursor`."),
68
68
  },