webdata-tools-mcp 0.4.0 → 0.10.1

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/README.md CHANGED
@@ -1,14 +1,19 @@
1
1
  # webdata-tools-mcp
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/webdata-tools-mcp)](https://www.npmjs.com/package/webdata-tools-mcp)
4
+ [![GitHub release](https://img.shields.io/github/v/release/tacps126/webdata-tools-mcp)](https://github.com/tacps126/webdata-tools-mcp/releases/latest)
4
5
  [![CI](https://github.com/tacps126/webdata-tools-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/tacps126/webdata-tools-mcp/actions/workflows/ci.yml)
5
6
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
- [Smithery](https://smithery.ai/servers/tacps126/webdata-tools-mcp)
7
+ [![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.tacps126%2Fwebdata--tools--mcp-2b6cb0)](https://github.com/modelcontextprotocol/registry)
8
+ [![Smithery](https://img.shields.io/badge/Smithery-tacps126%2Fwebdata--tools--mcp-ff5601)](https://smithery.ai/servers/tacps126/webdata-tools-mcp)
7
9
  [![webdata-tools-mcp MCP server](https://glama.ai/mcp/servers/tacps126/webdata-tools-mcp/badges/score.svg)](https://glama.ai/mcp/servers/tacps126/webdata-tools-mcp)
8
10
 
9
- An [MCP](https://modelcontextprotocol.io) server that gives AI agents eleven web-data tools: job
10
- postings, hiring signals, Google News, Trustpilot and app-store reviews, Reddit mentions, website
11
- contacts, German and Austrian property listings, Shopify products and trade-show exhibitor lists.
11
+ An [MCP](https://modelcontextprotocol.io) server that gives AI agents twenty-one web-data tools: job
12
+ postings (company career sites, remote jobs, startups on Wellfound, US tech jobs on Dice, Indeed,
13
+ Naukri), hiring signals, Google News, research papers and patents, field trends and idea validation, Trustpilot and app-store reviews, Reddit mentions and
14
+ buying-intent leads, website contacts, German and Austrian property listings, Shopify products and
15
+ store analysis, trade-show exhibitor lists and upcoming events. Two ready-made prompts turn the research
16
+ tools into an innovation analyst that works the same with any model.
12
17
 
13
18
  Each tool runs a ready-made [Apify](https://apify.com/tacps126) Actor and returns clean JSON. You
14
19
  pay Apify per result (about $1–3 per 1,000 results); there is no subscription and nothing is sent
@@ -20,15 +25,25 @@ anywhere except Apify.
20
25
  |------|-----------------|----------------|-------|
21
26
  | `search_google_news` | News articles with the publisher's real URL, date, related coverage | $2 / 1,000 articles | [google-news](https://apify.com/tacps126/google-news) |
22
27
  | `find_hiring_companies` | Which companies are hiring for given roles (buying-intent signal) | 1¢ per company with a signal | [hiring-signals](https://apify.com/tacps126/hiring-signals) |
23
- | `get_company_jobs` | All open jobs from career pages (Greenhouse, Lever, Ashby, Workday, …) | $1 / 1,000 jobs | [jobs-harvester](https://apify.com/tacps126/jobs-harvester) |
28
+ | `get_company_jobs` | All open jobs from career pages or company names (13 job-board systems: Greenhouse, Lever, Ashby, Workday, Rippling, …) | $1 / 1,000 jobs | [jobs-harvester](https://apify.com/tacps126/jobs-harvester) |
29
+ | `find_remote_jobs` | Remote jobs from company job boards plus the Remote OK, Jobicy, Working Nomads and Himalayas feeds | $2 / 1,000 jobs | [remote-jobs-scraper](https://apify.com/tacps126/remote-jobs-scraper) |
30
+ | `search_startup_jobs` | Startup jobs on Wellfound by role and city: salary and equity, startup size and pitch | $2 / 1,000 jobs | [wellfound-jobs-scraper](https://apify.com/tacps126/wellfound-jobs-scraper) |
31
+ | `search_tech_jobs` | US tech jobs on Dice: salary, employer type, Easy Apply, remote flag | $0.55 / 1,000 jobs | [dice-jobs-scraper](https://apify.com/tacps126/dice-jobs-scraper) |
24
32
  | `search_indeed_jobs` | Indeed listings in 9 countries | $3 / 1,000 jobs | [indeed-jobs](https://apify.com/tacps126/indeed-jobs) |
33
+ | `search_naukri_jobs` | Naukri.com jobs (India): full description, salary in rupees, experience, rating | $1.50 / 1,000 jobs | [naukri-jobs-scraper](https://apify.com/tacps126/naukri-jobs-scraper) |
25
34
  | `get_trustpilot_reviews` | Reviews, TrustScore and company profile | $0.50 / 1,000 reviews | [trustpilot-reviews](https://apify.com/tacps126/trustpilot-reviews) |
26
35
  | `get_app_reviews` | App Store and Google Play reviews | $0.20 / 1,000 reviews | [app-reviews](https://apify.com/tacps126/app-reviews) |
27
- | `search_reddit` | Reddit posts and comments by keyword or subreddit | $1 / 1,000 items | [reddit-monitor](https://apify.com/tacps126/reddit-monitor) |
36
+ | `search_reddit` | Reddit posts and comments by keyword or subreddit, tagged with buying intent | $1 / 1,000 items | [reddit-monitor](https://apify.com/tacps126/reddit-monitor) |
37
+ | `find_reddit_leads` | Reddit posts asking for a tool like yours, seeking a competitor alternative, comparing or complaining | $2 / 1,000 leads + $0.10 / 1,000 posts read | [reddit-lead-finder](https://apify.com/tacps126/reddit-lead-finder) |
38
+ | `search_research` | Papers, preprints and patents from OpenAlex, arXiv, PubMed and Europe PMC, merged; newest or most influential first, with field-normalized impact | $3 / 1,000 results | [research-radar](https://apify.com/tacps126/research-radar) |
39
+ | `analyze_field` | A technology field's opportunity score with reasons, growth and momentum, industry participation and top companies, leading countries, institutions and funders, rising topics; several fields ranked | $0.03 / field | [innovation-radar](https://apify.com/tacps126/innovation-radar) |
40
+ | `validate_idea` | How new or crowded an idea is, who is working on it, and the closest prior work | $0.03 / idea | [innovation-radar](https://apify.com/tacps126/innovation-radar) |
28
41
  | `find_website_contacts` | Emails, phones, socials from websites | $2 / 1,000 sites with contacts | [contact-details](https://apify.com/tacps126/contact-details) |
29
42
  | `search_german_property_listings` | Rent/buy listings in Germany and Austria (ImmoScout24) | $1 / 1,000 listings | [immobilienscout24-scraper](https://apify.com/tacps126/immobilienscout24-scraper) |
30
43
  | `get_shopify_products` | Prices, discounts, stock, variants from Shopify stores | $1 / 1,000 products | [shopify-products](https://apify.com/tacps126/shopify-products) |
31
- | `get_trade_show_exhibitors` | Trade-show exhibitors: booth, email, phone, website, address (76 shows or any supported exhibitor-list link) | $2 / 1,000 exhibitors | [exhibition-scraper](https://apify.com/tacps126/exhibition-scraper) |
44
+ | `analyze_shopify_stores` | Store location, currency, catalog size and snapshot, e-mails, phones, socials | $4 / 1,000 stores | [shopify-store-analyzer](https://apify.com/tacps126/shopify-store-analyzer) |
45
+ | `get_trade_show_exhibitors` | Trade-show exhibitors: booth, email, phone, website, address (91 shows or any supported exhibitor-list link; changes-only monitoring) | $2 / 1,000 exhibitors | [exhibition-scraper](https://apify.com/tacps126/exhibition-scraper) |
46
+ | `find_events` | Upcoming events from Luma, Eventbrite, Meetup: venue, organiser, hosts, tickets, attendees | $1 / 1,000 events | [events-scraper](https://apify.com/tacps126/events-scraper) |
32
47
 
33
48
  Every tool also takes an optional `extra_input` object for any other field the Actor documents.
34
49
  Every result comes back as text and as structured content (`tool`, `count`, `shown`, `truncated`,
@@ -36,12 +51,27 @@ Every result comes back as text and as structured content (`tool`, `count`, `sho
36
51
 
37
52
  More about the tools: <https://tacps126.github.io/mcp.html>
38
53
 
54
+ ## Prompts and agent skill
55
+
56
+ Two ready-made prompts turn the research tools into an analyst, whichever model you use (Claude, GPT, Gemini, a local model): the evidence, the reading guide and the answer shape are fixed, so results stay comparable.
57
+
58
+ | Prompt | What it does |
59
+ |--------|--------------|
60
+ | `field-opportunity-analysis` | Runs `analyze_field` on your fields, then writes ranked opportunities: what to build, for whom, why now, capital needed, who to talk to |
61
+ | `idea-validation` | Runs `validate_idea`, then assesses novelty, closest prior work, timing, who is already there, capital, risks and next steps |
62
+
63
+ For agents that load skills (Claude Code, Codex and others), copy [`skills/innovation-analyst`](skills/innovation-analyst/SKILL.md) into your skills folder.
64
+
39
65
  ## Setup
40
66
 
41
67
  1. Create a free [Apify account](https://apify.com) and copy your API token from
42
68
  **Console → Settings → API & Integrations**.
43
69
  2. Add the server to your client. The token goes in `APIFY_TOKEN`.
44
70
 
71
+ **Claude Desktop, one click:** download `webdata-tools-mcp-<version>.mcpb` from the
72
+ [latest release](https://github.com/tacps126/webdata-tools-mcp/releases/latest) and open it. Claude
73
+ Desktop installs it and asks for your Apify token.
74
+
45
75
  **Claude Desktop / Cursor / Windsurf** (`mcpServers` config):
46
76
 
47
77
  ```json
@@ -64,11 +94,18 @@ claude mcp add webdata -e APIFY_TOKEN=<your Apify token> -- npx -y webdata-tools
64
94
 
65
95
  Requires Node.js 18 or newer.
66
96
 
97
+ Releases are tagged and published by CI from `main` (`.github/workflows/release.yml`).
98
+
67
99
  ## Try it
68
100
 
69
101
  - "Which of these companies are hiring data engineers right now? https://boards.greenhouse.io/stripe, https://jobs.lever.co/spotify"
102
+ - "Find remote senior engineer jobs open to people in Canada posted this week."
103
+ - "Who on Reddit is looking for an alternative to HubSpot this month?"
104
+ - "Analyze these Shopify stores and give me their country, product count and contact e-mail: allbirds.com, gymshark.com"
70
105
  - "Show the 1 and 2 star Trustpilot reviews of example.com from the last 30 days and group the complaints."
71
106
  - "Find news from the last 7 days about open-source LLM licensing, with the real publisher links."
107
+ - "What should a seed-stage founder build in sodium-ion batteries? Compare it with solid-state." (field-opportunity-analysis prompt)
108
+ - "Find the most influential papers of the last three years on retrieval augmented generation."
72
109
  - "Get contact emails for these 20 agency websites." (use only for lawful business outreach)
73
110
 
74
111
  ## Settings
@@ -119,6 +156,13 @@ a tool sends a field the Actor does not accept.
119
156
  The Actors read publicly available pages. You are responsible for following each site's terms and
120
157
  the law that applies to you (GDPR, CAN-SPAM) when you use the data, especially contact details.
121
158
 
159
+ ## Listed on
160
+
161
+ <a href="https://glama.ai/mcp/servers/tacps126/webdata-tools-mcp"><img width="380" height="200" src="https://glama.ai/mcp/servers/tacps126/webdata-tools-mcp/badges/card.svg" alt="webdata-tools-mcp on Glama" /></a>
162
+ <a href="https://mcprush.com/to/webdata-tools-mcp?ref=badge"><img width="380" height="200" src="https://mcprush.com/badge/webdata-tools-mcp/card.svg" alt="Webdata Tools MCP server on MCPRush" /></a>
163
+
164
+ Also on [npm](https://www.npmjs.com/package/webdata-tools-mcp), the official [MCP Registry](https://github.com/modelcontextprotocol/registry) (`io.github.tacps126/webdata-tools-mcp`), and [Smithery](https://smithery.ai/servers/tacps126/webdata-tools-mcp).
165
+
122
166
  ## License
123
167
 
124
168
  MIT
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "webdata-tools-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.10.1",
4
4
  "mcpName": "io.github.tacps126/webdata-tools-mcp",
5
- "description": "MCP server for AI agents: job postings, hiring signals, Google News, Trustpilot and app reviews, Reddit mentions, website contacts, German real estate, Shopify products and trade-show exhibitor lists. Runs ready-made Apify Actors with your own Apify token.",
5
+ "description": "MCP server for AI agents: job postings (career sites, remote, startups, US tech, Indeed, Naukri), hiring signals, Google News, research papers and patents, technology-field trends and idea validation (with analyst prompts), Trustpilot and app reviews, Reddit mentions and buying-intent leads, website contacts, German real estate, Shopify products and store analysis, trade-show exhibitors and upcoming events. Runs ready-made Apify Actors with your own Apify token.",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "webdata-tools-mcp": "src/index.js"
@@ -10,13 +10,16 @@
10
10
  "main": "src/index.js",
11
11
  "files": [
12
12
  "src",
13
+ "skills",
13
14
  "icon.png",
14
15
  "README.md",
15
16
  "LICENSE"
16
17
  ],
17
18
  "scripts": {
18
19
  "start": "node src/index.js",
19
- "test": "node --test test/*.test.js"
20
+ "test": "node --test test/*.test.js",
21
+ "build:mcpb": "node scripts/build-mcpb.mjs",
22
+ "sync:inputs": "node scripts/sync-actor-inputs.mjs"
20
23
  },
21
24
  "keywords": [
22
25
  "mcp",
@@ -30,7 +33,13 @@
30
33
  "reviews",
31
34
  "lead-generation",
32
35
  "trade-shows",
33
- "exhibitors"
36
+ "exhibitors",
37
+ "events",
38
+ "luma",
39
+ "eventbrite",
40
+ "research",
41
+ "patents",
42
+ "innovation"
34
43
  ],
35
44
  "license": "MIT",
36
45
  "engines": {
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: innovation-analyst
3
+ description: Analyse technology fields and validate product or research ideas from open research data (OpenAlex, Europe PMC patents) with the webdata-tools MCP server, then write evidence-backed opportunities — what to build, for whom, why now, capital needed, who to talk to. Use when someone asks what to build in a field, whether an idea is new or crowded, how fast a technology is growing, who leads it (companies, labs, countries), or to compare several technologies.
4
+ ---
5
+
6
+ # Innovation analyst
7
+
8
+ Turns the structured evidence from the `analyze_field`, `validate_idea` and `search_research` tools (webdata-tools MCP server, backed by the Innovation Radar and Research Radar Apify Actors) into an analysis. Works with any model: the evidence, the reading guide and the answer shape are fixed, so results stay comparable.
9
+
10
+ ## Pick the tool
11
+
12
+ | Question | Call |
13
+ |---|---|
14
+ | "What should I build in X?", "Is X growing?", "Who leads X?", "Compare X, Y, Z" | `analyze_field` with all fields in one call (several come back ranked) |
15
+ | "Is my idea new?", "Who is already doing this?" | `validate_idea`; add `analyze_field` on the core technology for growth and industry signals |
16
+ | "What's the key recent work?", "Find papers on X" | `search_research` with `sort_by: "impact"` for influential work, `"newest"` for alerts |
17
+
18
+ Each field or idea costs about $0.03 on the user's Apify account. Don't call the same field twice in one conversation; reuse the record.
19
+
20
+ ## How to read the evidence
21
+
22
+ - `scores.opportunity` (0–100) and `scores.why`: the overall ranking and the signals behind it.
23
+ - `scores.momentum` (0–100): output in the last two full years against the two before; 50 ≈ +40%.
24
+ - `scores.labToMarketGap` (0–100): high momentum with few company authors — research is ahead of products.
25
+ - `industry.recentShare`: share of recent works with a company author (under 5% academic, 5–15% engaging, over 15% active); `industry.trend`: companies moving in or out.
26
+ - `scores.geographicConcentration`: share of the top country among the leaders; high means supply-chain, policy and partner risk.
27
+ - `risingTopics`: sub-topics whose share grew most. `standoutRecentWorks`: the most cited recent papers.
28
+ - `topCompanies`, `keyResearchers`, `topInstitutions`, `topFunders`: partners, hires, licensors, competitors.
29
+ - `reviewShare`: many review papers means the field is consolidating.
30
+ - `relatedPatents[].likelyExpiredByTerm`: filed over 20 years ago — possibly free to use (not legal advice).
31
+ - `growth.stage` says when counts are small: percentages are noisy then; say so.
32
+ - Validation: `crowdedness` (open, early, active, crowded), `matchingWorks`, `worksLastTwoYears`, `closestWorks`, `whoIsWorkingOnIt`.
33
+ - `broaderSpace` / `position`: the biggest neighbouring space with one term left out. "Open niche in a crowded field" means the application is new but the base technology is busy: proof it works, and competition for talent and suppliers.
34
+
35
+ ## Write the analysis
36
+
37
+ Capital buckets: **under $50k** (software or a service), **$50k–$1M** (hardware or regulated software), **over $1M** (deep tech, manufacturing, clinical).
38
+
39
+ **Field landscape** — sections:
40
+ 1. Summary: two or three sentences; with several fields, the ranking and why.
41
+ 2. Opportunities, three to five, best first: the idea, for whom, why now (name the signal), capital bucket with the reason, who to talk to (from the evidence).
42
+ 3. Risks.
43
+ 4. Evidence quality: strong, moderate or thin, and why.
44
+
45
+ **Idea validation** — sections: Novelty, Closest prior work, Timing, Who is already there, Capital (bucket and why), Risks, Next steps (three), Evidence quality.
46
+
47
+ Rules: cite the numbers you rely on; name real organisations only when they appear in the evidence; no generic advice ("do market research"); say when the evidence is thin. Research leads products by years — treat trends as early signals, not market size.
48
+
49
+ ## Without the MCP server
50
+
51
+ The Innovation Radar Actor writes an `AI_BRIEF` record to its run's key-value store: this same rubric plus the run's evidence. Paste it into any assistant.
package/src/prompts.js ADDED
@@ -0,0 +1,87 @@
1
+ // Ready-made analyst prompts: any model connected to this server gets the same
2
+ // rubric, so the analysis reads the same whichever AI writes it.
3
+ // Keep the text in step with apify_actors/actors/innovation-radar/src/rubric.rs
4
+ // (the Actor's own AI step and its AI_BRIEF record use the same rubric).
5
+
6
+ import { z } from 'zod';
7
+
8
+ export const SIGNALS = `How to read the evidence:
9
+ - scores.opportunity (0-100) and scores.why: the overall ranking and the signals behind it.
10
+ - scores.momentum (0-100): research output in the last two full years against the two before; 50 = about +40%.
11
+ - scores.labToMarketGap (0-100): high momentum with few company authors — research is ahead of products.
12
+ - industry.recentShare: share of recent works with a company author (under 5% academic, 5-15% engaging, over 15% active); industry.trend says whether companies are moving in or out.
13
+ - scores.geographicConcentration: share of the top country among the leading countries; high means one country dominates (supply-chain, policy and partner risk).
14
+ - risingTopics: sub-topics whose share grew most; standoutRecentWorks: the most cited recent papers.
15
+ - topCompanies / keyResearchers / topInstitutions / topFunders: who to partner with, hire from, license from or compete against.
16
+ - reviewShare: a high share of review papers means the field is consolidating.
17
+ - broaderSpace / position (validation): the biggest neighbouring space with one term left out; "open niche in a crowded field" means the application is new but the technology around it is busy (competition for talent, suppliers and attention; also proof the base technology works).
18
+ - relatedPatents.likelyExpiredByTerm: filed more than 20 years ago — possibly free to use (not legal advice).
19
+ Small counts make percentages noisy — say so when growth.stage reports it.`;
20
+
21
+ export const LANDSCAPE = `You are a pragmatic technology and innovation analyst advising founders, R&D leaders and investors.
22
+ From the field evidence, find concrete product or business opportunities.
23
+ For each opportunity say what to build, for whom, why now (which signal shows the timing), what it would take
24
+ (capital bucket: under $50k software/service, $50k-$1M hardware or regulated software, over $1M deep tech, manufacturing or clinical),
25
+ and who in the evidence could be a partner, customer or competitor.
26
+ Be specific, cite the signals you rely on, and say when the evidence is thin. No generic advice.`;
27
+
28
+ export const VALIDATION = `You are a pragmatic technology and innovation analyst. Assess the idea against the evidence:
29
+ how new it is (crowdedness, closest prior work), whether the timing is right (growth), who is already working on it
30
+ (researchers, institutions, companies), what it would take to build (capital bucket: under $50k, $50k-$1M, over $1M),
31
+ the main risks, and the three most useful next steps. Be specific, cite the evidence, and say when it is thin.`;
32
+
33
+ const LANDSCAPE_SHAPE = `Answer in these sections:
34
+ 1. Summary — two or three sentences; when several fields were analysed, the ranking and why.
35
+ 2. Opportunities — three to five, best first. For each: the idea, for whom, why now, capital bucket with the reason, who to talk to.
36
+ 3. Risks.
37
+ 4. Evidence quality — strong, moderate or thin, and why.`;
38
+
39
+ const VALIDATION_SHAPE = `Answer in these sections: Novelty, Closest prior work, Timing, Who is already there,
40
+ Capital (bucket and why), Risks, Next steps (three), Evidence quality (strong, moderate or thin).`;
41
+
42
+ const list = (s) =>
43
+ String(s)
44
+ .split(/\n|;/)
45
+ .map((x) => x.trim())
46
+ .filter(Boolean);
47
+
48
+ export const PROMPTS = [
49
+ {
50
+ name: 'field-opportunity-analysis',
51
+ title: 'Find opportunities in a technology field',
52
+ description:
53
+ 'Analyse one or more technology or research fields with analyze_field, then write ranked, evidence-backed product and business opportunities (why now, capital needed, who to talk to).',
54
+ args: {
55
+ fields: z.string().describe('Fields or technologies, one per line or separated by semicolons, e.g. "sodium-ion battery; solid state battery".'),
56
+ audience: z.string().optional().describe('Who the analysis is for, e.g. "a seed-stage founder", "a corporate R&D team". Default: founders, R&D leaders and investors.'),
57
+ },
58
+ text: ({ fields, audience }) => {
59
+ const names = list(fields);
60
+ return `Call the analyze_field tool with fields ${JSON.stringify(names)} (one call for all of them; ${names.length > 1 ? 'the records come back ranked by opportunity score' : 'one record'}).
61
+ Then use only its records as the evidence.${audience ? `\nThe analysis is for ${audience}.` : ''}
62
+
63
+ ${LANDSCAPE}
64
+
65
+ ${SIGNALS}
66
+
67
+ ${LANDSCAPE_SHAPE}`;
68
+ },
69
+ },
70
+ {
71
+ name: 'idea-validation',
72
+ title: 'Validate a product or research idea',
73
+ description:
74
+ 'Check an idea with validate_idea (how much research already covers it, who is working on it, closest prior work, related patents), then assess novelty, timing, capital, risks and next steps.',
75
+ args: {
76
+ idea: z.string().describe('The idea in a sentence or two, e.g. "a window that generates solar power with perovskite tandem cells".'),
77
+ },
78
+ text: ({ idea }) => `Call the validate_idea tool with ideas ${JSON.stringify([String(idea).trim()])}.
79
+ Then use only its record as the evidence. If the idea is broad, you may also call analyze_field on its core technology for growth and industry signals.
80
+
81
+ ${VALIDATION}
82
+
83
+ ${SIGNALS}
84
+
85
+ ${VALIDATION_SHAPE}`,
86
+ },
87
+ ];
package/src/server.js CHANGED
@@ -4,6 +4,7 @@ import { readFileSync } from 'node:fs';
4
4
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
5
  import { z } from 'zod';
6
6
  import { ApifyError, runActorSync } from './apify.js';
7
+ import { PROMPTS } from './prompts.js';
7
8
  import { extraInput, TOOLS } from './tools.js';
8
9
 
9
10
  const { version: VERSION } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
@@ -102,5 +103,15 @@ export function buildServer({ env = process.env, fetchImpl = fetch } = {}) {
102
103
  },
103
104
  );
104
105
  }
106
+ for (const prompt of PROMPTS) {
107
+ server.registerPrompt(
108
+ prompt.name,
109
+ { title: prompt.title, description: prompt.description, argsSchema: prompt.args },
110
+ (args) => ({
111
+ description: prompt.description,
112
+ messages: [{ role: 'user', content: { type: 'text', text: prompt.text(args) } }],
113
+ }),
114
+ );
115
+ }
105
116
  return server;
106
117
  }
package/src/tools.js CHANGED
@@ -99,39 +99,116 @@ export const TOOLS = [
99
99
  title: 'Get all open jobs from company career pages',
100
100
  price: 'about $0.001 per job',
101
101
  description:
102
- 'All open jobs from company career pages in one normalized format (title, company, location, remote/hybrid, seniority, salary, apply URL, posted date). Paste career-page URLs from Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Workday, Personio or Teamtailor; the board type is detected automatically.',
102
+ 'All open jobs from companies in one normalized format (title, company, location with country code, remote/hybrid, seniority, salary, skills, apply URL, posted date). Give career-page URLs (the company\'s own careers page or its job board) or just company names; 13 job-board systems are supported (Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Rippling, BambooHR, Pinpoint, Breezy, Recruitee, Personio, Teamtailor) and found automatically.',
103
103
  shape: {
104
- career_page_urls: strings('Career page URLs, e.g. "https://jobs.lever.co/spotify".'),
104
+ career_page_urls: z.array(z.string()).optional().describe('Career page URLs, e.g. "https://www.figma.com/careers" or "https://jobs.lever.co/spotify".'),
105
+ company_names: z.array(z.string()).optional().describe('Company names, e.g. ["Datadog", "Ramp"]; each is looked up on every supported job-board system.'),
105
106
  title_includes: z.array(z.string()).optional().describe('Keep jobs whose title contains any of these.'),
106
107
  location_includes: z.array(z.string()).optional().describe('Keep jobs in these locations.'),
108
+ countries: z.array(z.string()).optional().describe('Keep jobs in these countries: ISO codes ("US", "DE") or names.'),
107
109
  remote_only: z.boolean().optional().describe('Only remote jobs.'),
108
110
  posted_within_days: z.number().int().min(1).max(365).optional().describe('Only jobs posted in the last N days.'),
109
111
  max_items: maxItems(50),
110
112
  },
113
+ check: (a) =>
114
+ (a.career_page_urls?.length || a.company_names?.length)
115
+ ? null
116
+ : 'Provide at least one of: career_page_urls or company_names.',
111
117
  map: (a) =>
112
118
  clean({
113
119
  careerPageUrls: a.career_page_urls,
120
+ companyNames: a.company_names,
114
121
  titleIncludes: a.title_includes,
115
122
  locationIncludes: a.location_includes,
123
+ countries: a.countries,
116
124
  remoteOnly: a.remote_only,
117
125
  postedWithinDays: a.posted_within_days,
118
126
  maxItems: a.max_items,
119
127
  }),
120
128
  limit: (a) => a.max_items,
121
129
  },
130
+ {
131
+ name: 'find_remote_jobs',
132
+ actor: 'tacps126/remote-jobs-scraper',
133
+ title: 'Find remote jobs from company career sites and remote-job feeds',
134
+ price: 'about $0.002 per job',
135
+ description:
136
+ 'Remote jobs straight from employers: hundreds of company job boards plus the Remote OK, Jobicy, Working Nomads and Himalayas feeds, keeping only roles the posting marks as remote. Normalized title, company, country, seniority, salary and skills; each job links to its source.',
137
+ shape: {
138
+ title_includes: z.array(z.string()).optional().describe('Keep jobs whose title contains any of these, e.g. ["engineer"].'),
139
+ countries: z.array(z.string()).optional().describe('Keep jobs open to these countries: ISO codes ("US", "CA") or names.'),
140
+ posted_within_days: z.number().int().min(1).max(365).optional().describe('Only jobs posted in the last N days.'),
141
+ min_annual_salary: z.number().int().min(0).optional().describe('Minimum yearly pay in the posting\'s currency (jobs without pay are kept).'),
142
+ max_items: maxItems(50),
143
+ },
144
+ map: (a) =>
145
+ clean({
146
+ titleIncludes: a.title_includes,
147
+ countries: a.countries,
148
+ postedWithinDays: a.posted_within_days,
149
+ minAnnualSalary: a.min_annual_salary,
150
+ maxItems: a.max_items,
151
+ }),
152
+ limit: (a) => a.max_items,
153
+ },
154
+ {
155
+ name: 'search_startup_jobs',
156
+ actor: 'tacps126/wellfound-jobs-scraper',
157
+ title: 'Search startup jobs on Wellfound (AngelList Talent)',
158
+ price: 'about $0.002 per job',
159
+ description:
160
+ 'Startup jobs from Wellfound by role and city, or remote: title, startup name, size and pitch, salary and equity range, years of experience, remote or on-site, apply link.',
161
+ shape: {
162
+ role: z.string().min(1).describe('The role, e.g. "software engineer", "product manager", "designer".'),
163
+ city: z.string().optional().describe('Optional city, e.g. "San Francisco", "New York", "London".'),
164
+ remote: z.boolean().default(false).describe('Only remote jobs (ignores city).'),
165
+ max_items: maxItems(50),
166
+ },
167
+ map: (a) =>
168
+ clean({
169
+ query: a.role,
170
+ location: a.remote ? undefined : a.city,
171
+ remote: a.remote || undefined,
172
+ maxItems: a.max_items,
173
+ }),
174
+ limit: (a) => a.max_items,
175
+ },
176
+ {
177
+ name: 'search_tech_jobs',
178
+ actor: 'tacps126/dice-jobs-scraper',
179
+ title: 'Search US tech jobs on Dice',
180
+ price: 'about $0.00055 per job',
181
+ description:
182
+ 'US tech jobs from Dice by keyword and city: title, company, location, salary text, employer type (direct hire or recruiter), Easy Apply, remote / on-site / hybrid, posted and updated dates.',
183
+ shape: {
184
+ query: z.string().min(1).describe('What to search for, e.g. "data engineer", "java developer".'),
185
+ location: z.string().optional().describe('Optional US city or state, e.g. "Austin, TX".'),
186
+ remote: z.boolean().default(false).describe('Only remote jobs.'),
187
+ max_items: maxItems(50, 300),
188
+ },
189
+ map: (a) =>
190
+ clean({
191
+ query: a.query,
192
+ location: a.location,
193
+ remote: a.remote || undefined,
194
+ maxItems: a.max_items,
195
+ }),
196
+ limit: (a) => a.max_items,
197
+ },
122
198
  {
123
199
  name: 'search_indeed_jobs',
124
200
  actor: 'tacps126/indeed-jobs',
125
201
  title: 'Search Indeed jobs',
126
202
  price: 'about $0.003 per job',
127
203
  description:
128
- 'Search Indeed job listings by keyword and location in 9 countries (us, uk, ca, au, ie, de, fr, nl, sg): title, company, location, salary, job type, benefits, skills and apply link.',
204
+ 'Search Indeed job listings by keyword and location in 9 countries (us, uk, ca, au, ie, de, fr, nl, sg): title, company, location, salary, job type, benefits, skills, apply link and (with details) the full job description.',
129
205
  shape: {
130
206
  query: z.string().min(1).describe('Job title, skill or company, e.g. "react developer".'),
131
207
  location: z.string().optional().describe('City, state, postcode or "Remote". Empty = whole country.'),
132
208
  country: z.enum(['us', 'uk', 'ca', 'au', 'ie', 'de', 'fr', 'nl', 'sg']).default('us').describe('Indeed country site.'),
133
209
  date_posted_days: z.enum(['1', '3', '7', '14']).optional().describe('Only jobs posted in the last N days.'),
134
210
  job_type: z.enum(['fulltime', 'parttime', 'contract', 'temporary', 'internship']).optional().describe('Only this employment type.'),
211
+ include_details: z.boolean().default(true).describe("Add each job's full description. Off = search-card summary only."),
135
212
  max_items: maxItems(30),
136
213
  },
137
214
  map: (a) =>
@@ -141,6 +218,31 @@ export const TOOLS = [
141
218
  country: a.country,
142
219
  datePosted: a.date_posted_days,
143
220
  jobType: a.job_type,
221
+ includeDetails: a.include_details,
222
+ maxItems: a.max_items,
223
+ }),
224
+ limit: (a) => a.max_items,
225
+ },
226
+ {
227
+ name: 'search_naukri_jobs',
228
+ actor: 'tacps126/naukri-jobs-scraper',
229
+ title: 'Search Naukri.com jobs (India)',
230
+ price: 'about $0.0015 per job',
231
+ description:
232
+ "Jobs from Naukri.com, India's largest job board, by keyword and city: title, company, AmbitionBox rating, experience range in years, skills, and (with details) the full description, salary in rupees, industry, department and qualifications.",
233
+ shape: {
234
+ query: z.string().min(1).describe('Job title, skill or company, e.g. "python developer".'),
235
+ location: z.string().optional().describe('City in India, e.g. "Bangalore", "Pune", "Delhi NCR". Empty = all of India.'),
236
+ date_posted_days: z.enum(['1', '3', '7', '15', '30']).optional().describe('Only jobs posted in the last N days.'),
237
+ include_details: z.boolean().default(true).describe('Open each job for the full description, salary, industry and qualifications. Off = faster, search-card fields only.'),
238
+ max_items: maxItems(30, 500),
239
+ },
240
+ map: (a) =>
241
+ clean({
242
+ queries: [a.query],
243
+ locations: a.location ? [a.location] : undefined,
244
+ datePosted: a.date_posted_days,
245
+ includeDetails: a.include_details,
144
246
  maxItems: a.max_items,
145
247
  }),
146
248
  limit: (a) => a.max_items,
@@ -224,6 +326,89 @@ export const TOOLS = [
224
326
  }),
225
327
  limit: (a) => a.max_items,
226
328
  },
329
+ {
330
+ name: 'find_reddit_leads',
331
+ actor: 'tacps126/reddit-lead-finder',
332
+ title: 'Find buying-intent posts on Reddit',
333
+ price: 'about $0.002 per lead plus $0.0001 per post read',
334
+ description:
335
+ 'Reddit posts and comments where people ask for a tool like yours, look for an alternative to a competitor, compare options or complain. Each lead is tagged with its intent (recommendation_request, alternative_seeking, comparison, pain_point) and the phrase that showed it.',
336
+ shape: {
337
+ categories: z.array(z.string()).optional().describe('What you sell, as buyers say it, e.g. ["CRM", "invoice software"].'),
338
+ competitors: z.array(z.string()).optional().describe('Products whose users you want, e.g. ["HubSpot"].'),
339
+ subreddits: z.array(z.string()).optional().describe('Only search these subreddits, e.g. ["smallbusiness", "SaaS"].'),
340
+ time: z.enum(['day', 'week', 'month', 'year']).default('month').describe('How far back to look.'),
341
+ max_items: maxItems(30, 300),
342
+ },
343
+ check: (a) =>
344
+ (a.categories?.length || a.competitors?.length)
345
+ ? null
346
+ : 'Provide at least one of: categories or competitors.',
347
+ map: (a) =>
348
+ clean({
349
+ keywords: a.categories,
350
+ competitors: a.competitors,
351
+ subreddits: a.subreddits,
352
+ time: a.time,
353
+ maxItems: a.max_items,
354
+ maxPostsPerSource: 50,
355
+ }),
356
+ limit: (a) => a.max_items,
357
+ },
358
+ {
359
+ name: 'search_research',
360
+ actor: 'tacps126/research-radar',
361
+ title: 'Search papers, preprints and patents',
362
+ price: 'about $0.003 per result',
363
+ description:
364
+ 'Papers, preprints and patents from OpenAlex, arXiv, PubMed and Europe PMC in one deduplicated list, newest first or most influential first: title, abstract, authors and affiliations, venue, DOI, PDF link, citations, citations per year, field-normalized impact (top 1% / 10%), open-access status, and for patents the number, applicants and IPC/CPC classes. Retracted papers are left out.',
365
+ shape: {
366
+ queries: strings('Topics, keywords or phrases, e.g. "solid state battery".'),
367
+ sources: z.array(z.enum(['openalex', 'arxiv', 'pubmed', 'europepmc', 'patents', 'crossref'])).optional().describe('Which sources to search. Default: openalex, arxiv, pubmed, europepmc, patents.'),
368
+ posted_within_days: z.number().int().min(1).max(3650).optional().describe('Only results published in the last N days.'),
369
+ open_access_only: z.boolean().default(false).describe('Only papers that are free to read.'),
370
+ sort_by: z.enum(['newest', 'citations', 'impact']).default('newest').describe('newest first, most cited, or highest field-normalized impact (influential work).'),
371
+ max_items: maxItems(30, 500),
372
+ },
373
+ map: (a) =>
374
+ clean({
375
+ queries: a.queries,
376
+ sources: a.sources,
377
+ postedWithinDays: a.posted_within_days,
378
+ openAccessOnly: a.open_access_only,
379
+ sortBy: a.sort_by,
380
+ maxItemsPerSource: Math.min(Math.max(a.max_items, 10), 100),
381
+ maxItems: a.max_items,
382
+ }),
383
+ limit: (a) => a.max_items,
384
+ },
385
+ {
386
+ name: 'analyze_field',
387
+ actor: 'tacps126/innovation-radar',
388
+ title: 'Analyse a technology or research field',
389
+ price: 'about $0.03 per field',
390
+ description:
391
+ 'A landscape of a technology or research field from open research data: an opportunity score (0-100) with its reasons, momentum, lab-to-market gap and geographic concentration; works per year and growth stage; industry participation and the top companies; key researchers, institutions, countries and funders; rising sub-topics, standout recent work and related patents. Several fields come back ranked. For written opportunities, use the field-opportunity-analysis prompt.',
392
+ shape: {
393
+ fields: strings('Fields or technologies, e.g. "solid state battery", "CRISPR base editing".'),
394
+ years: z.number().int().min(2).max(40).default(10).describe('Years of history for the trend.'),
395
+ },
396
+ map: (a) => clean({ mode: 'landscape', fields: a.fields, years: a.years, topWorks: 10 }),
397
+ limit: (a) => a.fields.length,
398
+ },
399
+ {
400
+ name: 'validate_idea',
401
+ actor: 'tacps126/innovation-radar',
402
+ title: 'Check how new or crowded an idea is',
403
+ price: 'about $0.03 per idea',
404
+ description:
405
+ 'How much research already covers a product or research idea: matching works, works in the last two years, crowdedness (open, early, active, crowded), growth, who is working on it (researchers, institutions, companies), the closest prior work and related patents. For a written assessment, use the idea-validation prompt.',
406
+ shape: {
407
+ ideas: strings('Ideas in plain words, e.g. "a window that generates solar power with perovskite tandem cells".'),
408
+ },
409
+ map: (a) => clean({ mode: 'validate', ideas: a.ideas, topWorks: 10 }),
410
+ limit: (a) => a.ideas.length,
411
+ },
227
412
  {
228
413
  name: 'find_website_contacts',
229
414
  actor: 'tacps126/contact-details',
@@ -262,6 +447,10 @@ export const TOOLS = [
262
447
  max_price: z.number().optional().describe('Maximum price in EUR (monthly rent or purchase price).'),
263
448
  min_area: z.number().optional().describe('Minimum area in m2.'),
264
449
  min_rooms: z.number().optional().describe('Minimum number of rooms.'),
450
+ changes_only: z
451
+ .boolean()
452
+ .default(false)
453
+ .describe('Only listings that are new, changed (old and new price, rent, size, rooms) or removed since the last call with the same search. For price tracking on a schedule.'),
265
454
  max_items: maxItems(50),
266
455
  },
267
456
  map: (a) =>
@@ -274,6 +463,7 @@ export const TOOLS = [
274
463
  maxPrice: a.max_price,
275
464
  minArea: a.min_area,
276
465
  minRooms: a.min_rooms,
466
+ changesOnly: a.changes_only || undefined,
277
467
  maxItems: a.max_items,
278
468
  }),
279
469
  limit: (a) => a.max_items,
@@ -302,17 +492,43 @@ export const TOOLS = [
302
492
  }),
303
493
  limit: (a) => a.max_items,
304
494
  },
495
+ {
496
+ name: 'analyze_shopify_stores',
497
+ actor: 'tacps126/shopify-store-analyzer',
498
+ title: 'Analyze Shopify stores (location, catalog, contacts)',
499
+ price: 'about $0.004 per store',
500
+ description:
501
+ 'For each Shopify store: name, city and country, currency, product and collection counts, a catalog snapshot (price range and median, share on sale and in stock, top brands and product types, newest product) and its e-mails, phones and social profiles. Non-Shopify sites are skipped for free.',
502
+ shape: {
503
+ stores: strings('Store websites or domains, e.g. ["allbirds.com", "gymshark.com"].'),
504
+ include_contacts: z.boolean().default(true).describe('Also read the store\'s contact pages for e-mails, phones and socials.'),
505
+ countries: z.array(z.string()).optional().describe('Keep only stores based in these countries (ISO codes or names).'),
506
+ max_items: maxItems(20, 1000),
507
+ },
508
+ map: (a) =>
509
+ clean({
510
+ startUrls: a.stores,
511
+ includeContacts: a.include_contacts,
512
+ countries: a.countries,
513
+ maxItems: a.max_items,
514
+ }),
515
+ limit: (a) => a.max_items,
516
+ },
305
517
  {
306
518
  name: 'get_trade_show_exhibitors',
307
519
  actor: 'tacps126/exhibition-scraper',
308
520
  title: 'Get trade show exhibitor lists (booths and contacts)',
309
521
  price: 'about $0.002 per exhibitor',
310
522
  description:
311
- "Exhibitor lists of trade shows as B2B leads: company, booth and hall, email, phone, website, address, socials and categories. 76 built-in shows (e.g. medica, sema-show, nab-show, fabtech, ish, automechanika, wtm-london, world-health-expo) or any exhibitor-list link on Messe Frankfurt, Messe Düsseldorf, RX, Map Your Show, a2z / A2Z Connect, Swapcard or Corussoft; the link's own show and year are used. Contact coverage depends on what the organiser publishes.",
523
+ "Exhibitor lists of trade shows as B2B leads: company, booth and hall, email, phone, website, address, socials and categories. 91 built-in shows (e.g. medica, sema-show, nab-show, fabtech, ish, automechanika, wtm-london, orgatec, eurotier, wine-paris, biofach) or any exhibitor-list link or show homepage on Messe Frankfurt, Messe Düsseldorf, Koelnmesse, NürnbergMesse, Newfront/ExpoPlatform, RX, Map Your Show, a2z / A2Z Connect, Swapcard or Corussoft; the link's own show and year are used. Contact coverage depends on what the organiser publishes.",
312
524
  shape: {
313
525
  shows: z.array(z.string().min(1)).optional().describe('Built-in show ids, e.g. ["medica","sema-show"]. The Actor README lists all ids.'),
314
526
  urls: z.array(z.string().url()).optional().describe("Links to any show's exhibitor list or exhibitor search page (or its homepage)."),
315
527
  include_contacts: z.boolean().default(true).describe('Open each exhibitor profile for email, phone, website, address and socials. Off = names and booths only (faster).'),
528
+ changes_only: z
529
+ .boolean()
530
+ .default(false)
531
+ .describe('Only exhibitors that are new, changed (old and new booth, email, phone, website, address) or removed since the last call with the same shows. Removed ones need the whole list, so it ignores max_items for that.'),
316
532
  max_items: maxItems(50, 2000),
317
533
  },
318
534
  map: (a) =>
@@ -320,11 +536,41 @@ export const TOOLS = [
320
536
  exhibitionList: a.shows,
321
537
  startUrls: (a.urls ?? []).map((url) => ({ url })),
322
538
  includeDetails: a.include_contacts,
539
+ changesOnly: a.changes_only || undefined,
323
540
  maxResults: a.max_items,
324
541
  }),
325
542
  // max_items applies per show.
326
543
  limit: (a) => a.max_items * Math.max(1, (a.shows?.length ?? 0) + (a.urls?.length ?? 0)),
327
544
  },
545
+ {
546
+ name: 'find_events',
547
+ actor: 'tacps126/events-scraper',
548
+ title: 'Find upcoming events (Luma, Eventbrite, Meetup)',
549
+ price: 'about $0.001 per event',
550
+ description:
551
+ 'Upcoming events by topic and city, or from Luma / Eventbrite / Meetup links (or any page with schema.org events): name, dates, venue with address and coordinates, organiser, hosts, ticket prices, free/sold-out and attendee count, in one format. Use for event prospecting, sponsorships and community research.',
552
+ shape: {
553
+ searches: z.array(z.string().min(1)).optional().describe('Topics or keywords, e.g. ["AI","startup"].'),
554
+ location: z.string().optional().describe('City in plain words (e.g. "London", "Bengaluru") or "online". Luma searches within a city.'),
555
+ urls: z.array(z.string().url()).optional().describe('Luma city/category/calendar/event links, Eventbrite searches or events, Meetup search or group links, or any events page.'),
556
+ sources: z.array(z.enum(['luma', 'eventbrite', 'meetup'])).optional().describe('Sites searched for `searches` (default all three). Meetup knows about 70 big cities by name; others as "City, CC" (e.g. "Lyon, FR").'),
557
+ include_details: z.boolean().default(true).describe('Open each event for organiser, tickets and full description. Off = faster.'),
558
+ max_items: maxItems(30, 500),
559
+ },
560
+ map: (a) =>
561
+ clean({
562
+ searches: a.searches,
563
+ location: a.location,
564
+ startUrls: (a.urls ?? []).map((url) => ({ url })),
565
+ sources: a.sources,
566
+ includeDetails: a.include_details,
567
+ maxItems: a.max_items,
568
+ }),
569
+ // max_items applies per link and per search on each site.
570
+ limit: (a) =>
571
+ a.max_items *
572
+ Math.max(1, (a.searches?.length ?? 0) * (a.sources?.length ?? 3) + (a.urls?.length ?? 0)),
573
+ },
328
574
  ];
329
575
 
330
576
  export const ACTORS = [...new Set(TOOLS.map((t) => t.actor))];