@wentorai/research-plugins 1.4.0 → 1.4.2

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.
Files changed (53) hide show
  1. package/curated/literature/README.md +2 -2
  2. package/curated/writing/README.md +1 -1
  3. package/package.json +1 -1
  4. package/skills/literature/discovery/SKILL.md +1 -1
  5. package/skills/literature/discovery/citation-alert-guide/SKILL.md +2 -2
  6. package/skills/literature/discovery/conference-proceedings-guide/SKILL.md +2 -2
  7. package/skills/literature/discovery/literature-mapping-guide/SKILL.md +1 -1
  8. package/skills/literature/discovery/paper-recommendation-guide/SKILL.md +8 -14
  9. package/skills/literature/discovery/rss-paper-feeds/SKILL.md +20 -14
  10. package/skills/literature/discovery/semantic-paper-radar/SKILL.md +8 -8
  11. package/skills/literature/discovery/semantic-scholar-recs-guide/SKILL.md +103 -86
  12. package/skills/literature/fulltext/open-access-guide/SKILL.md +1 -1
  13. package/skills/literature/fulltext/open-access-mining-guide/SKILL.md +5 -5
  14. package/skills/literature/metadata/citation-network-guide/SKILL.md +3 -3
  15. package/skills/literature/metadata/h-index-guide/SKILL.md +0 -27
  16. package/skills/literature/search/SKILL.md +1 -1
  17. package/skills/literature/search/citation-chaining-guide/SKILL.md +42 -32
  18. package/skills/literature/search/database-comparison-guide/SKILL.md +1 -1
  19. package/skills/literature/search/semantic-scholar-api/SKILL.md +56 -53
  20. package/skills/research/automation/paper-to-agent-guide/SKILL.md +1 -1
  21. package/skills/research/deep-research/in-depth-research-guide/SKILL.md +1 -1
  22. package/skills/research/deep-research/kosmos-scientist-guide/SKILL.md +3 -3
  23. package/skills/research/deep-research/llm-scientific-discovery-guide/SKILL.md +1 -1
  24. package/skills/research/deep-research/local-deep-research-guide/SKILL.md +6 -6
  25. package/skills/research/deep-research/open-researcher-guide/SKILL.md +3 -3
  26. package/skills/research/deep-research/tongyi-deep-research-guide/SKILL.md +4 -4
  27. package/skills/research/methodology/grad-school-guide/SKILL.md +1 -1
  28. package/skills/research/paper-review/automated-review-guide/SKILL.md +1 -1
  29. package/skills/tools/diagram/excalidraw-diagram-guide/SKILL.md +1 -1
  30. package/skills/tools/diagram/mermaid-architect-guide/SKILL.md +1 -1
  31. package/skills/tools/diagram/plantuml-guide/SKILL.md +1 -1
  32. package/skills/tools/document/grobid-pdf-parsing/SKILL.md +1 -1
  33. package/skills/tools/document/paper-parse-guide/SKILL.md +2 -2
  34. package/skills/tools/knowledge-graph/citation-network-builder/SKILL.md +5 -5
  35. package/skills/tools/knowledge-graph/knowledge-graph-construction/SKILL.md +1 -1
  36. package/skills/tools/scraping/academic-web-scraping/SKILL.md +1 -2
  37. package/skills/tools/scraping/google-scholar-scraper/SKILL.md +7 -7
  38. package/skills/writing/citation/SKILL.md +1 -1
  39. package/skills/writing/citation/academic-citation-manager/SKILL.md +20 -17
  40. package/skills/writing/citation/citation-assistant-skill/SKILL.md +72 -58
  41. package/skills/writing/citation/onecite-reference-guide/SKILL.md +1 -1
  42. package/skills/writing/citation/zotero-reference-guide/SKILL.md +1 -1
  43. package/skills/writing/citation/zotero-scholar-guide/SKILL.md +1 -1
  44. package/src/tools/arxiv.ts +3 -0
  45. package/src/tools/biorxiv.ts +19 -3
  46. package/src/tools/crossref.ts +3 -0
  47. package/src/tools/datacite.ts +3 -0
  48. package/src/tools/openalex.ts +6 -0
  49. package/src/tools/opencitations.ts +9 -0
  50. package/src/tools/orcid.ts +3 -0
  51. package/src/tools/pubmed.ts +3 -0
  52. package/src/tools/unpaywall.ts +3 -0
  53. package/src/tools/zenodo.ts +3 -0
@@ -41,7 +41,7 @@ Ethical guidelines:
41
41
  OpenAlex could answer it instead
42
42
 
43
43
  Official and semi-official alternatives:
44
- - Semantic Scholar API: free, 100 requests/sec, excellent coverage
44
+ - OpenAlex API: free, no key required, excellent coverage
45
45
  - OpenAlex API: free, comprehensive, well-documented
46
46
  - Crossref API: free, DOI-based metadata and citation counts
47
47
  - CORE API: free, full-text open access content
@@ -232,12 +232,12 @@ OpenAlex (openalex.org):
232
232
  - Data: titles, abstracts, citations, authors, institutions
233
233
  - Best for: large-scale bibliometric analysis
234
234
 
235
- Semantic Scholar (semanticscholar.org):
236
- - Coverage: 200M+ papers
237
- - API: REST, free key available
238
- - Rate limit: 100 requests/sec with API key
239
- - Data: titles, abstracts, citations, citation contexts, TLDR
240
- - Best for: citation analysis, NLP on papers
235
+ OpenAlex (openalex.org):
236
+ - Coverage: 250M+ works, all disciplines
237
+ - API: REST, no key required
238
+ - Rate limit: ~10 requests/sec polite
239
+ - Data: titles, abstracts, citations, concepts, author profiles
240
+ - Best for: cross-disciplinary analysis, open data research
241
241
 
242
242
  Crossref (crossref.org):
243
243
  - Coverage: 130M+ DOIs
@@ -11,7 +11,7 @@ Select the skill matching the user's need, then `read` its SKILL.md.
11
11
  |-------|-------------|
12
12
  | [academic-citation-manager](./academic-citation-manager/SKILL.md) | Manage academic citations across BibTeX, APA, MLA, and Chicago formats |
13
13
  | [bibtex-management-guide](./bibtex-management-guide/SKILL.md) | Clean, format, deduplicate, and manage BibTeX bibliography files for LaTeX |
14
- | [citation-assistant-skill](./citation-assistant-skill/SKILL.md) | Claude Code skill for citation workflow via Semantic Scholar |
14
+ | [citation-assistant-skill](./citation-assistant-skill/SKILL.md) | Claude Code skill for citation workflow via OpenAlex and CrossRef |
15
15
  | [citation-style-guide](./citation-style-guide/SKILL.md) | APA, MLA, Chicago citation format guide with CSL configuration |
16
16
  | [jabref-reference-guide](./jabref-reference-guide/SKILL.md) | Guide to JabRef open-source BibTeX and BibLaTeX reference manager |
17
17
  | [jasminum-zotero-guide](./jasminum-zotero-guide/SKILL.md) | Guide to Jasminum for retrieving CNKI Chinese academic metadata in Zotero |
@@ -18,7 +18,7 @@ Manage academic citations across multiple formats (BibTeX, APA 7th, MLA 9th, Chi
18
18
 
19
19
  Citation management is a persistent friction point in academic writing. Researchers collect references from multiple sources (databases, PDFs, colleagues, web pages), store them in different formats, and must output them in the specific style required by each target journal. Errors in citations -- misspelled author names, incorrect years, broken DOIs, inconsistent formatting -- are among the most common reasons for desk rejection and reviewer criticism.
20
20
 
21
- This skill provides a comprehensive citation management workflow that goes beyond what GUI reference managers offer. It can retrieve complete metadata from a DOI in seconds, convert between any citation format, detect and merge duplicate entries, validate entries against CrossRef and Semantic Scholar databases, and generate properly formatted bibliographies for any major citation style.
21
+ This skill provides a comprehensive citation management workflow that goes beyond what GUI reference managers offer. It can retrieve complete metadata from a DOI in seconds, convert between any citation format, detect and merge duplicate entries, validate entries against CrossRef and OpenAlex databases, and generate properly formatted bibliographies for any major citation style.
22
22
 
23
23
  The approach is text-based and scriptable, making it ideal for integration with LaTeX workflows, Markdown writing pipelines, and automated document generation. All citation data is stored in standard BibTeX format as the canonical source, with on-demand conversion to other formats for specific manuscript requirements.
24
24
 
@@ -52,33 +52,36 @@ print(bibtex)
52
52
  # }
53
53
  ```
54
54
 
55
- ### From Semantic Scholar
55
+ ### From OpenAlex
56
56
 
57
57
  ```python
58
- def get_citation_from_s2(paper_id):
59
- """Retrieve citation data from Semantic Scholar API."""
60
- url = f"https://api.semanticscholar.org/graph/v1/paper/{paper_id}"
61
- params = {"fields": "title,authors,year,venue,doi,citationCount,externalIds"}
62
- response = requests.get(url, params=params)
58
+ def get_citation_from_openalex(work_id):
59
+ """Retrieve citation data from OpenAlex API."""
60
+ url = f"https://api.openalex.org/works/{work_id}"
61
+ headers = {"User-Agent": "ResearchPlugins/1.0 (https://wentor.ai)"}
62
+ response = requests.get(url, headers=headers)
63
63
  if response.status_code == 200:
64
64
  data = response.json()
65
65
  return format_as_bibtex(data)
66
66
  return None
67
67
 
68
- def format_as_bibtex(s2_data):
69
- """Convert Semantic Scholar data to BibTeX."""
70
- authors = s2_data.get("authors", [])
71
- author_str = " and ".join(a["name"] for a in authors)
72
- first_author = authors[0]["name"].split()[-1] if authors else "Unknown"
73
- year = s2_data.get("year", "")
68
+ def format_as_bibtex(oa_data):
69
+ """Convert OpenAlex data to BibTeX."""
70
+ authorships = oa_data.get("authorships", [])
71
+ author_str = " and ".join(a["author"]["display_name"] for a in authorships)
72
+ first_author = authorships[0]["author"]["display_name"].split()[-1] if authorships else "Unknown"
73
+ year = str(oa_data.get("publication_year", ""))
74
74
  key = f"{first_author}_{year}"
75
75
 
76
+ venue = oa_data.get("primary_location", {}) or {}
77
+ journal = (venue.get("source") or {}).get("display_name", "")
78
+
76
79
  return f"""@article{{{key},
77
- title={{{s2_data.get('title', '')}}},
80
+ title={{{oa_data.get('title', '')}}},
78
81
  author={{{author_str}}},
79
82
  year={{{year}}},
80
- journal={{{s2_data.get('venue', '')}}},
81
- doi={{{s2_data.get('doi', '')}}}
83
+ journal={{{journal}}},
84
+ doi={{{oa_data.get('doi', '')}}}
82
85
  }}"""
83
86
  ```
84
87
 
@@ -308,7 +311,7 @@ pandoc paper.md --citeproc --bibliography=references.bib \
308
311
  ## References
309
312
 
310
313
  - CrossRef API: https://api.crossref.org
311
- - Semantic Scholar API: https://api.semanticscholar.org
314
+ - OpenAlex API: https://api.openalex.org
312
315
  - APA 7th Edition Manual: https://apastyle.apa.org/products/publication-manual-7th-edition
313
316
  - BibTeX documentation: http://www.bibtex.org
314
317
  - CSL styles repository: https://github.com/citation-style-language/styles
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: citation-assistant-skill
3
- description: "Claude Code skill for citation workflow via Semantic Scholar"
3
+ description: "Claude Code skill for citation workflow via OpenAlex and CrossRef"
4
4
  metadata:
5
5
  openclaw:
6
6
  emoji: "📎"
7
7
  category: "writing"
8
8
  subcategory: "citation"
9
- keywords: ["citation assistant", "Semantic Scholar", "Claude Code skill", "reference lookup", "academic citation"]
9
+ keywords: ["citation assistant", "OpenAlex", "Claude Code skill", "reference lookup", "academic citation"]
10
10
  source: "https://github.com/ZhangNy301/citation-assistant"
11
11
  ---
12
12
 
@@ -14,7 +14,7 @@ metadata:
14
14
 
15
15
  ## Overview
16
16
 
17
- Citation Assistant is a Claude Code skill that integrates Semantic Scholar API into the coding workflow for instant paper lookup, citation formatting, and reference management. Search for papers by title or keyword, get formatted BibTeX entries, find related works, and insert citations — all without leaving the terminal. Designed for researchers writing papers in LaTeX or Markdown.
17
+ Citation Assistant is a Claude Code skill that integrates OpenAlex and CrossRef APIs into the coding workflow for instant paper lookup, citation formatting, and reference management. Search for papers by title or keyword, get formatted BibTeX entries, find related works, and insert citations — all without leaving the terminal. Designed for researchers writing papers in LaTeX or Markdown.
18
18
 
19
19
  ## Installation
20
20
 
@@ -32,106 +32,118 @@ openclaw skills install citation-assistant
32
32
  ```python
33
33
  import requests
34
34
 
35
- S2_API = "https://api.semanticscholar.org/graph/v1"
35
+ OA_API = "https://api.openalex.org"
36
36
 
37
37
  def search_papers(query, limit=5):
38
- """Search Semantic Scholar for papers."""
38
+ """Search OpenAlex for papers."""
39
39
  resp = requests.get(
40
- f"{S2_API}/paper/search",
40
+ f"{OA_API}/works",
41
41
  params={
42
- "query": query,
43
- "limit": limit,
44
- "fields": "title,authors,year,citationCount,"
45
- "externalIds,abstract,venue",
42
+ "search": query,
43
+ "per_page": limit,
46
44
  },
45
+ headers={"User-Agent": "ResearchPlugins/1.0 (https://wentor.ai)"},
47
46
  )
48
- return resp.json().get("data", [])
47
+ return resp.json().get("results", [])
49
48
 
50
49
  papers = search_papers("attention mechanism transformer")
51
50
  for p in papers:
52
- authors = ", ".join(a["name"] for a in p["authors"][:3])
53
- print(f"[{p['year']}] {p['title']}")
54
- print(f" {authors} — Citations: {p['citationCount']}")
55
- print(f" DOI: {p.get('externalIds', {}).get('DOI', 'N/A')}")
51
+ authors = [a["author"]["display_name"] for a in p.get("authorships", [])[:3]]
52
+ print(f"[{p.get('publication_year')}] {p.get('title')}")
53
+ print(f" {', '.join(authors)} — Citations: {p.get('cited_by_count')}")
54
+ print(f" DOI: {p.get('doi', 'N/A')}")
56
55
  ```
57
56
 
58
57
  ### BibTeX Generation
59
58
 
60
59
  ```python
61
- def get_bibtex(paper_id):
62
- """Get BibTeX for a Semantic Scholar paper."""
60
+ def get_bibtex(doi):
61
+ """Get BibTeX for a paper via CrossRef DOI resolution."""
63
62
  resp = requests.get(
64
- f"{S2_API}/paper/{paper_id}",
65
- params={
66
- "fields": "title,authors,year,venue,externalIds,"
67
- "journal,publicationTypes",
68
- },
63
+ f"https://api.crossref.org/works/{doi}",
64
+ headers={"User-Agent": "ResearchPlugins/1.0 (https://wentor.ai; mailto:dev@wentor.ai)"},
69
65
  )
70
- paper = resp.json()
66
+ msg = resp.json().get("message", {})
71
67
 
72
68
  # Generate citation key
73
- first_author = paper["authors"][0]["name"].split()[-1].lower()
74
- key = f"{first_author}{paper['year']}"
69
+ authors = msg.get("author", [])
70
+ first_author = authors[0].get("family", "unknown").lower() if authors else "unknown"
71
+ year = str(msg.get("published", {}).get("date-parts", [[""]])[0][0])
72
+ key = f"{first_author}{year}"
75
73
 
76
74
  # Build BibTeX
77
- authors_str = " and ".join(a["name"] for a in paper["authors"])
78
- doi = paper.get("externalIds", {}).get("DOI", "")
75
+ authors_str = " and ".join(f"{a.get('given', '')} {a.get('family', '')}".strip() for a in authors)
76
+ doi_str = msg.get("DOI", "")
77
+
78
+ title = msg.get("title", [""])[0] if isinstance(msg.get("title"), list) else msg.get("title", "")
79
+ journal = msg.get("container-title", [""])[0] if msg.get("container-title") else ""
79
80
 
80
81
  bibtex = f"""@article{{{key},
81
- title = {{{paper['title']}}},
82
+ title = {{{title}}},
82
83
  author = {{{authors_str}}},
83
- year = {{{paper['year']}}},
84
- journal = {{{paper.get('venue', '')}}},
85
- doi = {{{doi}}},
84
+ year = {{{year}}},
85
+ journal = {{{journal}}},
86
+ doi = {{{doi_str}}},
86
87
  }}"""
87
88
  return bibtex
88
89
 
89
90
  # Example
90
- bibtex = get_bibtex("204e3073870fae3d05bcbc2f6a8e263d9b72e776")
91
+ bibtex = get_bibtex("10.18653/v1/N19-1423")
91
92
  print(bibtex)
92
93
  ```
93
94
 
94
95
  ### Citation Context
95
96
 
96
97
  ```python
97
- def get_citation_context(paper_id, limit=10):
98
- """Get papers that cite this work with context."""
98
+ def get_citing_works(openalex_id, limit=10):
99
+ """Get papers that cite this work via OpenAlex."""
99
100
  resp = requests.get(
100
- f"{S2_API}/paper/{paper_id}/citations",
101
+ f"{OA_API}/works",
101
102
  params={
102
- "fields": "title,year,contexts,intents",
103
- "limit": limit,
103
+ "filter": f"cites:{openalex_id}",
104
+ "per_page": limit,
105
+ "sort": "cited_by_count:desc",
104
106
  },
107
+ headers={"User-Agent": "ResearchPlugins/1.0 (https://wentor.ai)"},
105
108
  )
106
- citations = resp.json().get("data", [])
109
+ results = resp.json().get("results", [])
107
110
 
108
- for cit in citations:
109
- paper = cit["citingPaper"]
110
- print(f"\n{paper['title']} ({paper.get('year', '?')})")
111
- for ctx in cit.get("contexts", [])[:2]:
112
- print(f" Context: ...{ctx[:100]}...")
113
- print(f" Intent: {cit.get('intents', ['unknown'])}")
111
+ for paper in results:
112
+ authors = [a["author"]["display_name"] for a in paper.get("authorships", [])[:3]]
113
+ print(f"\n{paper.get('title')} ({paper.get('publication_year', '?')})")
114
+ print(f" Authors: {', '.join(authors)}")
115
+ print(f" Citations: {paper.get('cited_by_count', 0)}")
114
116
 
115
- get_citation_context("204e3073870fae3d05bcbc2f6a8e263d9b72e776")
117
+ get_citing_works("W2741809807")
116
118
  ```
117
119
 
118
120
  ### Related Paper Discovery
119
121
 
120
122
  ```python
121
- def find_related(paper_id, limit=10):
122
- """Find papers related to a given paper."""
123
+ def find_related(openalex_id, limit=10):
124
+ """Find papers related to a given paper via OpenAlex."""
125
+ # Get the paper's concepts, then search for similar works
123
126
  resp = requests.get(
124
- f"{S2_API}/paper/{paper_id}/recommendations",
127
+ f"{OA_API}/works/{openalex_id}",
128
+ headers={"User-Agent": "ResearchPlugins/1.0 (https://wentor.ai)"},
129
+ )
130
+ paper = resp.json()
131
+ concepts = [c["display_name"] for c in paper.get("concepts", [])[:3]]
132
+
133
+ related_resp = requests.get(
134
+ f"{OA_API}/works",
125
135
  params={
126
- "fields": "title,authors,year,citationCount",
127
- "limit": limit,
136
+ "search": " ".join(concepts),
137
+ "per_page": limit,
138
+ "sort": "cited_by_count:desc",
128
139
  },
140
+ headers={"User-Agent": "ResearchPlugins/1.0 (https://wentor.ai)"},
129
141
  )
130
- return resp.json().get("recommendedPapers", [])
142
+ return related_resp.json().get("results", [])
131
143
 
132
- related = find_related("204e3073870fae3d05bcbc2f6a8e263d9b72e776")
144
+ related = find_related("W2741809807")
133
145
  for p in related:
134
- print(f"[{p['year']}] {p['title']} ({p['citationCount']} cites)")
146
+ print(f"[{p.get('publication_year')}] {p.get('title')} ({p.get('cited_by_count')} cites)")
135
147
  ```
136
148
 
137
149
  ## Workflow Integration
@@ -161,10 +173,11 @@ def build_bibliography(queries, output_file="refs.bib"):
161
173
  for query in queries:
162
174
  papers = search_papers(query, limit=3)
163
175
  for paper in papers:
164
- pid = paper.get("paperId")
165
- if pid and pid not in seen_ids:
166
- seen_ids.add(pid)
167
- bibtex = get_bibtex(pid)
176
+ doi = paper.get("doi")
177
+ if doi and doi not in seen_ids:
178
+ seen_ids.add(doi)
179
+ bibtex = get_bibtex(doi.replace("https://doi.org/", ""))
180
+
168
181
  all_bibtex.append(bibtex)
169
182
 
170
183
  with open(output_file, "w") as f:
@@ -189,4 +202,5 @@ build_bibliography([
189
202
  ## References
190
203
 
191
204
  - [Citation Assistant GitHub](https://github.com/ZhangNy301/citation-assistant)
192
- - [Semantic Scholar API](https://api.semanticscholar.org/)
205
+ - [OpenAlex API](https://api.openalex.org/)
206
+ - [CrossRef API](https://api.crossref.org/)
@@ -14,7 +14,7 @@ metadata:
14
14
 
15
15
  ## Overview
16
16
 
17
- OneCite is an AI-powered toolkit for parsing, completing, and formatting academic references. Given incomplete or messy citation strings, it extracts structured metadata, fills in missing fields via API lookups (CrossRef, Semantic Scholar), and outputs clean formatted references in any style (APA, MLA, BibTeX, Chicago). Available as a Python library and MCP server for agent integration.
17
+ OneCite is an AI-powered toolkit for parsing, completing, and formatting academic references. Given incomplete or messy citation strings, it extracts structured metadata, fills in missing fields via API lookups (CrossRef, OpenAlex), and outputs clean formatted references in any style (APA, MLA, BibTeX, Chicago). Available as a Python library and MCP server for agent integration.
18
18
 
19
19
  ## Installation
20
20
 
@@ -38,7 +38,7 @@ The plugin is particularly powerful for literature reviews, where you need to sy
38
38
  - Identifies DOIs, arXiv IDs, and other persistent identifiers within references
39
39
 
40
40
  **Database Matching**
41
- - Matches parsed references against Crossref, Semantic Scholar, and other academic databases
41
+ - Matches parsed references against Crossref, OpenAlex, and other academic databases
42
42
  - Retrieves complete metadata for matched references (abstract, keywords, citation count)
43
43
  - Displays match confidence to help users verify correct identification
44
44
  - Supports manual correction when automatic matching fails
@@ -16,7 +16,7 @@ metadata:
16
16
 
17
17
  Zotero is the most widely used open-source reference manager in academia, offering a powerful combination of local storage, cloud sync, browser integration, and a comprehensive API. While most researchers use Zotero through its desktop application and browser connector, the Zotero API enables programmatic access that is essential for automated research workflows—saving papers from scripts, batch-importing search results, organizing libraries algorithmically, and integrating reference management into custom research pipelines.
18
18
 
19
- This skill covers using the Zotero Web API to create, read, update, and organize library items programmatically. It is designed for researchers who want to automate parts of their reference management workflow: importing papers from arXiv or Semantic Scholar searches directly into Zotero, auto-tagging papers based on content analysis, organizing collections programmatically, and exporting citations in various formats.
19
+ This skill covers using the Zotero Web API to create, read, update, and organize library items programmatically. It is designed for researchers who want to automate parts of their reference management workflow: importing papers from arXiv or OpenAlex searches directly into Zotero, auto-tagging papers based on content analysis, organizing collections programmatically, and exporting citations in various formats.
20
20
 
21
21
  The skill complements the existing `zotero-api` skill by focusing specifically on practical scholar workflows—the common patterns a researcher uses when integrating Zotero into their daily literature management.
22
22
 
@@ -151,6 +151,9 @@ export function createArxivTools(
151
151
  }),
152
152
  }),
153
153
  execute: async (input: { arxiv_id: string }) => {
154
+ if (!input?.arxiv_id) {
155
+ return toolResult({ error: 'arxiv_id parameter is required (e.g., "2301.00001" or "2301.00001v2")' });
156
+ }
154
157
  const id = input.arxiv_id.replace("arXiv:", "").replace(/https?:\/\/arxiv\.org\/abs\//, "");
155
158
  const params = new URLSearchParams({ id_list: id });
156
159
 
@@ -13,17 +13,24 @@ export function createBiorxivTools(
13
13
  name: "search_biorxiv",
14
14
  label: "Search Preprints (bioRxiv)",
15
15
  description:
16
- "Get recent bioRxiv preprints by date range. Covers biology preprints (300K+). Use date range (e.g. '2026-03-01/2026-03-18'), recent count (e.g. '50'), or recent days (e.g. '7d').",
16
+ "Get recent bioRxiv preprints by date range. Covers biology preprints (300K+). Use a date range like '2026-03-01/2026-03-18'. Returns up to 100 preprints per page.",
17
17
  parameters: Type.Object({
18
18
  interval: Type.String({
19
19
  description:
20
- "Date range 'YYYY-MM-DD/YYYY-MM-DD', recent count '50', or recent days '7d'",
20
+ "Date range in YYYY-MM-DD/YYYY-MM-DD format (e.g. '2026-03-01/2026-03-18'). Use today's date for most recent.",
21
21
  }),
22
22
  cursor: Type.Optional(
23
23
  Type.Number({ description: "Pagination offset (default 0, each page returns up to 100)" }),
24
24
  ),
25
25
  }),
26
26
  execute: async (input: { interval: string; cursor?: number }) => {
27
+ if (!input?.interval) {
28
+ return toolResult({ error: 'interval parameter is required (date range in YYYY-MM-DD/YYYY-MM-DD format, e.g. "2026-03-01/2026-03-18")' });
29
+ }
30
+ // Validate date range format — bioRxiv API only accepts YYYY-MM-DD/YYYY-MM-DD
31
+ if (!/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(input.interval)) {
32
+ return toolResult({ error: `Invalid interval format "${input.interval}". Must be YYYY-MM-DD/YYYY-MM-DD (e.g. "2026-03-01/2026-03-18")` });
33
+ }
27
34
  const cursor = input.cursor ?? 0;
28
35
  const tracked = await trackedFetch("biorxiv", `${BASE}/details/biorxiv/${input.interval}/${cursor}/json`, undefined, 30_000);
29
36
  if (isTrackedError(tracked)) return tracked;
@@ -63,13 +70,19 @@ export function createBiorxivTools(
63
70
  parameters: Type.Object({
64
71
  interval: Type.String({
65
72
  description:
66
- "Date range 'YYYY-MM-DD/YYYY-MM-DD', recent count '50', or recent days '7d'",
73
+ "Date range in YYYY-MM-DD/YYYY-MM-DD format (e.g. '2026-03-01/2026-03-18'). Use today's date for most recent.",
67
74
  }),
68
75
  cursor: Type.Optional(
69
76
  Type.Number({ description: "Pagination offset (default 0)" }),
70
77
  ),
71
78
  }),
72
79
  execute: async (input: { interval: string; cursor?: number }) => {
80
+ if (!input?.interval) {
81
+ return toolResult({ error: 'interval parameter is required (date range in YYYY-MM-DD/YYYY-MM-DD format, e.g. "2026-03-01/2026-03-18")' });
82
+ }
83
+ if (!/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(input.interval)) {
84
+ return toolResult({ error: `Invalid interval format "${input.interval}". Must be YYYY-MM-DD/YYYY-MM-DD (e.g. "2026-03-01/2026-03-18")` });
85
+ }
73
86
  const cursor = input.cursor ?? 0;
74
87
  const tracked = await trackedFetch("medrxiv", `${BASE}/details/medrxiv/${input.interval}/${cursor}/json`, undefined, 30_000);
75
88
  if (isTrackedError(tracked)) return tracked;
@@ -112,6 +125,9 @@ export function createBiorxivTools(
112
125
  ),
113
126
  }),
114
127
  execute: async (input: { doi: string; server?: string }) => {
128
+ if (!input?.doi) {
129
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1101/2024.01.15.575123")' });
130
+ }
115
131
  const server = input.server ?? "biorxiv";
116
132
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
117
133
  const tracked = await trackedFetch(server, `${BASE}/details/${server}/${doi}/na/json`, undefined, 15_000);
@@ -24,6 +24,9 @@ export function createCrossRefTools(
24
24
  }),
25
25
  }),
26
26
  execute: async (input: { doi: string }) => {
27
+ if (!input?.doi) {
28
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
29
+ }
27
30
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
28
31
  const tracked = await trackedFetch("crossref", `${BASE}/works/${encodeURIComponent(doi)}`, { headers });
29
32
  if (isTrackedError(tracked)) return tracked;
@@ -118,6 +118,9 @@ export function createDataCiteTools(
118
118
  }),
119
119
  }),
120
120
  execute: async (input: { doi: string }) => {
121
+ if (!input?.doi) {
122
+ return toolResult({ error: 'doi parameter is required (e.g., "10.5281/zenodo.1234567")' });
123
+ }
121
124
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
122
125
 
123
126
  const tracked = await trackedFetch(
@@ -94,6 +94,9 @@ export function createOpenAlexTools(
94
94
  }),
95
95
  }),
96
96
  execute: async (input: { work_id: string }) => {
97
+ if (!input?.work_id) {
98
+ return toolResult({ error: 'work_id parameter is required (e.g., "W2741809807" or a DOI like "10.1234/example")' });
99
+ }
97
100
  const id = input.work_id.startsWith("10.")
98
101
  ? `https://doi.org/${input.work_id}`
99
102
  : input.work_id;
@@ -135,6 +138,9 @@ export function createOpenAlexTools(
135
138
  }),
136
139
  }),
137
140
  execute: async (input: { author_id: string }) => {
141
+ if (!input?.author_id) {
142
+ return toolResult({ error: 'author_id parameter is required (OpenAlex ID e.g. "A5023888391", ORCID, or author name)' });
143
+ }
138
144
  let url: string;
139
145
  if (
140
146
  input.author_id.startsWith("A") ||
@@ -29,6 +29,9 @@ export function createOpenCitationsTools(
29
29
  }),
30
30
  }),
31
31
  execute: async (input: { doi: string }) => {
32
+ if (!input?.doi) {
33
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
34
+ }
32
35
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
33
36
  const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/citations/doi:${encodeURIComponent(doi)}`);
34
37
  if (isTrackedError(tracked)) return tracked;
@@ -58,6 +61,9 @@ export function createOpenCitationsTools(
58
61
  }),
59
62
  }),
60
63
  execute: async (input: { doi: string }) => {
64
+ if (!input?.doi) {
65
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
66
+ }
61
67
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
62
68
  const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/references/doi:${encodeURIComponent(doi)}`);
63
69
  if (isTrackedError(tracked)) return tracked;
@@ -87,6 +93,9 @@ export function createOpenCitationsTools(
87
93
  }),
88
94
  }),
89
95
  execute: async (input: { doi: string }) => {
96
+ if (!input?.doi) {
97
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
98
+ }
90
99
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
91
100
  const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/citation-count/doi:${encodeURIComponent(doi)}`);
92
101
  if (isTrackedError(tracked)) return tracked;
@@ -72,6 +72,9 @@ export function createOrcidTools(
72
72
  }),
73
73
  }),
74
74
  execute: async (input: { orcid: string }) => {
75
+ if (!input?.orcid) {
76
+ return toolResult({ error: 'orcid parameter is required (e.g., "0000-0002-1825-0097")' });
77
+ }
75
78
  const orcid = input.orcid.replace(/^https?:\/\/orcid\.org\//, "");
76
79
 
77
80
  const result = await trackedFetch(
@@ -109,6 +109,9 @@ export function createPubMedTools(
109
109
  pmid: Type.String({ description: "PubMed ID (numeric string)" }),
110
110
  }),
111
111
  execute: async (input: { pmid: string }) => {
112
+ if (!input?.pmid) {
113
+ return toolResult({ error: 'pmid parameter is required (numeric PubMed ID, e.g., "33116299")' });
114
+ }
112
115
  const params = new URLSearchParams({
113
116
  db: "pubmed",
114
117
  id: input.pmid,
@@ -22,6 +22,9 @@ export function createUnpaywallTools(
22
22
  }),
23
23
  }),
24
24
  execute: async (input: { doi: string }) => {
25
+ if (!input?.doi) {
26
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
27
+ }
25
28
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
26
29
  const tracked = await trackedFetch(
27
30
  "unpaywall",
@@ -103,6 +103,9 @@ export function createZenodoTools(
103
103
  }),
104
104
  }),
105
105
  execute: async (input: { record_id: string }) => {
106
+ if (!input?.record_id) {
107
+ return toolResult({ error: 'record_id parameter is required (e.g., "1234567")' });
108
+ }
106
109
  const id = input.record_id.replace(/\D/g, "");
107
110
  const result = await trackedFetch("zenodo", `${BASE}/records/${id}`, undefined, 10_000);
108
111
  if (isTrackedError(result)) return result;