@wentorai/research-plugins 1.3.2 → 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 (266) hide show
  1. package/README.md +32 -56
  2. package/curated/analysis/README.md +1 -13
  3. package/curated/domains/README.md +1 -5
  4. package/curated/literature/README.md +3 -12
  5. package/curated/research/README.md +1 -18
  6. package/curated/tools/README.md +1 -12
  7. package/curated/writing/README.md +2 -6
  8. package/index.ts +88 -5
  9. package/openclaw.plugin.json +3 -12
  10. package/package.json +3 -5
  11. package/skills/analysis/statistics/SKILL.md +1 -1
  12. package/skills/analysis/statistics/meta-analysis-guide/SKILL.md +1 -1
  13. package/skills/domains/ai-ml/SKILL.md +3 -2
  14. package/skills/domains/ai-ml/generative-ai-guide/SKILL.md +1 -0
  15. package/skills/domains/ai-ml/huggingface-api/SKILL.md +251 -0
  16. package/skills/domains/biomedical/SKILL.md +9 -2
  17. package/skills/domains/biomedical/alphafold-api/SKILL.md +227 -0
  18. package/skills/domains/biomedical/biothings-api/SKILL.md +296 -0
  19. package/skills/domains/biomedical/clinicaltrials-api-v2/SKILL.md +216 -0
  20. package/skills/domains/biomedical/enrichr-api/SKILL.md +264 -0
  21. package/skills/domains/biomedical/ensembl-rest-api/SKILL.md +204 -0
  22. package/skills/domains/biomedical/medical-data-api/SKILL.md +197 -0
  23. package/skills/domains/biomedical/pdb-structure-api/SKILL.md +219 -0
  24. package/skills/domains/business/SKILL.md +2 -3
  25. package/skills/domains/chemistry/SKILL.md +3 -2
  26. package/skills/domains/chemistry/catalysis-hub-api/SKILL.md +171 -0
  27. package/skills/domains/education/SKILL.md +2 -3
  28. package/skills/domains/law/SKILL.md +3 -2
  29. package/skills/domains/law/uk-legislation-api/SKILL.md +179 -0
  30. package/skills/literature/discovery/SKILL.md +1 -1
  31. package/skills/literature/discovery/citation-alert-guide/SKILL.md +2 -2
  32. package/skills/literature/discovery/conference-proceedings-guide/SKILL.md +2 -2
  33. package/skills/literature/discovery/literature-mapping-guide/SKILL.md +1 -1
  34. package/skills/literature/discovery/paper-recommendation-guide/SKILL.md +8 -14
  35. package/skills/literature/discovery/rss-paper-feeds/SKILL.md +20 -14
  36. package/skills/literature/discovery/semantic-paper-radar/SKILL.md +8 -8
  37. package/skills/literature/discovery/semantic-scholar-recs-guide/SKILL.md +103 -86
  38. package/skills/literature/fulltext/SKILL.md +3 -2
  39. package/skills/literature/fulltext/arxiv-latex-source/SKILL.md +195 -0
  40. package/skills/literature/fulltext/open-access-guide/SKILL.md +1 -1
  41. package/skills/literature/fulltext/open-access-mining-guide/SKILL.md +5 -5
  42. package/skills/literature/metadata/citation-network-guide/SKILL.md +3 -3
  43. package/skills/literature/metadata/h-index-guide/SKILL.md +0 -27
  44. package/skills/literature/search/SKILL.md +3 -4
  45. package/skills/literature/search/citation-chaining-guide/SKILL.md +42 -32
  46. package/skills/literature/search/database-comparison-guide/SKILL.md +1 -1
  47. package/skills/literature/search/semantic-scholar-api/SKILL.md +56 -53
  48. package/skills/research/automation/SKILL.md +2 -3
  49. package/skills/research/automation/datagen-research-guide/SKILL.md +1 -0
  50. package/skills/research/automation/mle-agent-guide/SKILL.md +1 -0
  51. package/skills/research/automation/paper-to-agent-guide/SKILL.md +2 -1
  52. package/skills/research/deep-research/auto-deep-research-guide/SKILL.md +1 -0
  53. package/skills/research/deep-research/in-depth-research-guide/SKILL.md +1 -1
  54. package/skills/research/deep-research/kosmos-scientist-guide/SKILL.md +3 -3
  55. package/skills/research/deep-research/llm-scientific-discovery-guide/SKILL.md +1 -1
  56. package/skills/research/deep-research/local-deep-research-guide/SKILL.md +6 -6
  57. package/skills/research/deep-research/open-researcher-guide/SKILL.md +3 -3
  58. package/skills/research/deep-research/tongyi-deep-research-guide/SKILL.md +4 -4
  59. package/skills/research/methodology/SKILL.md +1 -1
  60. package/skills/research/methodology/claude-scientific-guide/SKILL.md +1 -0
  61. package/skills/research/methodology/grad-school-guide/SKILL.md +1 -1
  62. package/skills/research/methodology/qualitative-research-guide/SKILL.md +1 -1
  63. package/skills/research/paper-review/SKILL.md +1 -1
  64. package/skills/research/paper-review/automated-review-guide/SKILL.md +1 -1
  65. package/skills/research/paper-review/peer-review-guide/SKILL.md +1 -1
  66. package/skills/tools/diagram/excalidraw-diagram-guide/SKILL.md +1 -1
  67. package/skills/tools/diagram/mermaid-architect-guide/SKILL.md +1 -1
  68. package/skills/tools/diagram/plantuml-guide/SKILL.md +1 -1
  69. package/skills/tools/document/grobid-pdf-parsing/SKILL.md +1 -1
  70. package/skills/tools/document/paper-parse-guide/SKILL.md +2 -2
  71. package/skills/tools/knowledge-graph/SKILL.md +2 -3
  72. package/skills/tools/knowledge-graph/citation-network-builder/SKILL.md +5 -5
  73. package/skills/tools/knowledge-graph/knowledge-graph-construction/SKILL.md +1 -1
  74. package/skills/tools/ocr-translate/zotero-pdf2zh-guide/SKILL.md +1 -0
  75. package/skills/tools/scraping/academic-web-scraping/SKILL.md +1 -2
  76. package/skills/tools/scraping/google-scholar-scraper/SKILL.md +7 -7
  77. package/skills/writing/citation/SKILL.md +1 -1
  78. package/skills/writing/citation/academic-citation-manager/SKILL.md +20 -17
  79. package/skills/writing/citation/citation-assistant-skill/SKILL.md +72 -58
  80. package/skills/writing/citation/obsidian-citation-guide/SKILL.md +1 -0
  81. package/skills/writing/citation/obsidian-zotero-guide/SKILL.md +1 -0
  82. package/skills/writing/citation/onecite-reference-guide/SKILL.md +1 -1
  83. package/skills/writing/citation/papersgpt-zotero-guide/SKILL.md +1 -0
  84. package/skills/writing/citation/zotero-mdnotes-guide/SKILL.md +1 -0
  85. package/skills/writing/citation/zotero-reference-guide/SKILL.md +2 -1
  86. package/skills/writing/citation/zotero-scholar-guide/SKILL.md +1 -1
  87. package/skills/writing/composition/scientific-writing-resources/SKILL.md +1 -0
  88. package/skills/writing/latex/latex-drawing-collection/SKILL.md +1 -0
  89. package/skills/writing/latex/latex-templates-collection/SKILL.md +1 -0
  90. package/skills/writing/templates/novathesis-guide/SKILL.md +1 -0
  91. package/src/tools/arxiv.ts +81 -30
  92. package/src/tools/biorxiv.ts +158 -0
  93. package/src/tools/crossref.ts +63 -22
  94. package/src/tools/datacite.ts +191 -0
  95. package/src/tools/dblp.ts +125 -0
  96. package/src/tools/doaj.ts +82 -0
  97. package/src/tools/europe-pmc.ts +159 -0
  98. package/src/tools/hal.ts +118 -0
  99. package/src/tools/inspire-hep.ts +165 -0
  100. package/src/tools/openaire.ts +158 -0
  101. package/src/tools/openalex.ts +26 -15
  102. package/src/tools/opencitations.ts +112 -0
  103. package/src/tools/orcid.ts +139 -0
  104. package/src/tools/osf-preprints.ts +104 -0
  105. package/src/tools/pubmed.ts +22 -13
  106. package/src/tools/ror.ts +118 -0
  107. package/src/tools/unpaywall.ts +15 -6
  108. package/src/tools/util.ts +141 -0
  109. package/src/tools/zenodo.ts +157 -0
  110. package/mcp-configs/academic-db/ChatSpatial.json +0 -17
  111. package/mcp-configs/academic-db/academia-mcp.json +0 -17
  112. package/mcp-configs/academic-db/academic-paper-explorer.json +0 -17
  113. package/mcp-configs/academic-db/academic-search-mcp-server.json +0 -17
  114. package/mcp-configs/academic-db/agentinterviews-mcp.json +0 -17
  115. package/mcp-configs/academic-db/all-in-mcp.json +0 -17
  116. package/mcp-configs/academic-db/alphafold-mcp.json +0 -20
  117. package/mcp-configs/academic-db/apple-health-mcp.json +0 -17
  118. package/mcp-configs/academic-db/arxiv-latex-mcp.json +0 -17
  119. package/mcp-configs/academic-db/arxiv-mcp-server.json +0 -17
  120. package/mcp-configs/academic-db/bgpt-mcp.json +0 -17
  121. package/mcp-configs/academic-db/biomcp.json +0 -17
  122. package/mcp-configs/academic-db/biothings-mcp.json +0 -17
  123. package/mcp-configs/academic-db/brightspace-mcp.json +0 -21
  124. package/mcp-configs/academic-db/catalysishub-mcp-server.json +0 -17
  125. package/mcp-configs/academic-db/climatiq-mcp.json +0 -20
  126. package/mcp-configs/academic-db/clinicaltrialsgov-mcp-server.json +0 -17
  127. package/mcp-configs/academic-db/deep-research-mcp.json +0 -17
  128. package/mcp-configs/academic-db/dicom-mcp.json +0 -17
  129. package/mcp-configs/academic-db/enrichr-mcp-server.json +0 -17
  130. package/mcp-configs/academic-db/fec-mcp-server.json +0 -17
  131. package/mcp-configs/academic-db/fhir-mcp-server-themomentum.json +0 -17
  132. package/mcp-configs/academic-db/fhir-mcp.json +0 -19
  133. package/mcp-configs/academic-db/gget-mcp.json +0 -17
  134. package/mcp-configs/academic-db/gibs-mcp.json +0 -20
  135. package/mcp-configs/academic-db/gis-mcp-server.json +0 -22
  136. package/mcp-configs/academic-db/google-earth-engine-mcp.json +0 -21
  137. package/mcp-configs/academic-db/google-researcher-mcp.json +0 -17
  138. package/mcp-configs/academic-db/idea-reality-mcp.json +0 -17
  139. package/mcp-configs/academic-db/legiscan-mcp.json +0 -19
  140. package/mcp-configs/academic-db/lex.json +0 -17
  141. package/mcp-configs/academic-db/m4-clinical-mcp.json +0 -21
  142. package/mcp-configs/academic-db/medical-mcp.json +0 -21
  143. package/mcp-configs/academic-db/nexonco-mcp.json +0 -20
  144. package/mcp-configs/academic-db/omop-mcp.json +0 -20
  145. package/mcp-configs/academic-db/onekgpd-mcp.json +0 -20
  146. package/mcp-configs/academic-db/openedu-mcp.json +0 -20
  147. package/mcp-configs/academic-db/opengenes-mcp.json +0 -20
  148. package/mcp-configs/academic-db/openstax-mcp.json +0 -21
  149. package/mcp-configs/academic-db/openstreetmap-mcp.json +0 -21
  150. package/mcp-configs/academic-db/opentargets-mcp.json +0 -21
  151. package/mcp-configs/academic-db/pdb-mcp.json +0 -21
  152. package/mcp-configs/academic-db/smithsonian-mcp.json +0 -20
  153. package/mcp-configs/ai-platform/Adaptive-Graph-of-Thoughts-MCP-server.json +0 -17
  154. package/mcp-configs/ai-platform/ai-counsel.json +0 -17
  155. package/mcp-configs/ai-platform/atlas-mcp-server.json +0 -17
  156. package/mcp-configs/ai-platform/counsel-mcp.json +0 -17
  157. package/mcp-configs/ai-platform/cross-llm-mcp.json +0 -17
  158. package/mcp-configs/ai-platform/gptr-mcp.json +0 -17
  159. package/mcp-configs/ai-platform/magi-researchers.json +0 -21
  160. package/mcp-configs/ai-platform/mcp-academic-researcher.json +0 -22
  161. package/mcp-configs/ai-platform/open-paper-machine.json +0 -21
  162. package/mcp-configs/ai-platform/paper-intelligence.json +0 -21
  163. package/mcp-configs/ai-platform/paper-reader.json +0 -21
  164. package/mcp-configs/ai-platform/paperdebugger.json +0 -21
  165. package/mcp-configs/browser/decipher-research-agent.json +0 -17
  166. package/mcp-configs/browser/deep-research.json +0 -17
  167. package/mcp-configs/browser/everything-claude-code.json +0 -17
  168. package/mcp-configs/browser/exa-mcp.json +0 -20
  169. package/mcp-configs/browser/gpt-researcher.json +0 -17
  170. package/mcp-configs/browser/heurist-agent-framework.json +0 -17
  171. package/mcp-configs/browser/mcp-searxng.json +0 -21
  172. package/mcp-configs/browser/mcp-webresearch.json +0 -20
  173. package/mcp-configs/cloud-docs/confluence-mcp.json +0 -37
  174. package/mcp-configs/cloud-docs/google-drive-mcp.json +0 -35
  175. package/mcp-configs/cloud-docs/notion-mcp.json +0 -29
  176. package/mcp-configs/communication/discord-mcp.json +0 -29
  177. package/mcp-configs/communication/discourse-mcp.json +0 -21
  178. package/mcp-configs/communication/slack-mcp.json +0 -29
  179. package/mcp-configs/communication/telegram-mcp.json +0 -28
  180. package/mcp-configs/data-platform/4everland-hosting-mcp.json +0 -17
  181. package/mcp-configs/data-platform/automl-stat-mcp.json +0 -21
  182. package/mcp-configs/data-platform/context-keeper.json +0 -17
  183. package/mcp-configs/data-platform/context7.json +0 -19
  184. package/mcp-configs/data-platform/contextstream-mcp.json +0 -17
  185. package/mcp-configs/data-platform/email-mcp.json +0 -17
  186. package/mcp-configs/data-platform/jefferson-stats-mcp.json +0 -22
  187. package/mcp-configs/data-platform/mcp-excel-server.json +0 -21
  188. package/mcp-configs/data-platform/mcp-stata.json +0 -21
  189. package/mcp-configs/data-platform/mcpstack-jupyter.json +0 -21
  190. package/mcp-configs/data-platform/ml-mcp.json +0 -21
  191. package/mcp-configs/data-platform/nasdaq-data-link-mcp.json +0 -20
  192. package/mcp-configs/data-platform/numpy-mcp.json +0 -21
  193. package/mcp-configs/database/neo4j-mcp.json +0 -37
  194. package/mcp-configs/database/postgres-mcp.json +0 -28
  195. package/mcp-configs/database/sqlite-mcp.json +0 -29
  196. package/mcp-configs/dev-platform/geogebra-mcp.json +0 -21
  197. package/mcp-configs/dev-platform/github-mcp.json +0 -31
  198. package/mcp-configs/dev-platform/gitlab-mcp.json +0 -34
  199. package/mcp-configs/dev-platform/latex-mcp-server.json +0 -21
  200. package/mcp-configs/dev-platform/manim-mcp.json +0 -20
  201. package/mcp-configs/dev-platform/mcp-echarts.json +0 -20
  202. package/mcp-configs/dev-platform/panel-viz-mcp.json +0 -20
  203. package/mcp-configs/dev-platform/paperbanana.json +0 -20
  204. package/mcp-configs/dev-platform/texflow-mcp.json +0 -20
  205. package/mcp-configs/dev-platform/texmcp.json +0 -20
  206. package/mcp-configs/dev-platform/typst-mcp.json +0 -21
  207. package/mcp-configs/dev-platform/vizro-mcp.json +0 -20
  208. package/mcp-configs/email/email-mcp.json +0 -40
  209. package/mcp-configs/email/gmail-mcp.json +0 -37
  210. package/mcp-configs/note-knowledge/ApeRAG.json +0 -17
  211. package/mcp-configs/note-knowledge/In-Memoria.json +0 -17
  212. package/mcp-configs/note-knowledge/agent-memory.json +0 -17
  213. package/mcp-configs/note-knowledge/aimemo.json +0 -17
  214. package/mcp-configs/note-knowledge/biel-mcp.json +0 -19
  215. package/mcp-configs/note-knowledge/cognee.json +0 -17
  216. package/mcp-configs/note-knowledge/context-awesome.json +0 -17
  217. package/mcp-configs/note-knowledge/context-mcp.json +0 -17
  218. package/mcp-configs/note-knowledge/conversation-handoff-mcp.json +0 -17
  219. package/mcp-configs/note-knowledge/cortex.json +0 -17
  220. package/mcp-configs/note-knowledge/devrag.json +0 -17
  221. package/mcp-configs/note-knowledge/easy-obsidian-mcp.json +0 -17
  222. package/mcp-configs/note-knowledge/engram.json +0 -17
  223. package/mcp-configs/note-knowledge/gnosis-mcp.json +0 -17
  224. package/mcp-configs/note-knowledge/graphlit-mcp-server.json +0 -19
  225. package/mcp-configs/note-knowledge/local-faiss-mcp.json +0 -21
  226. package/mcp-configs/note-knowledge/mcp-memory-service.json +0 -21
  227. package/mcp-configs/note-knowledge/mcp-obsidian.json +0 -23
  228. package/mcp-configs/note-knowledge/mcp-ragdocs.json +0 -20
  229. package/mcp-configs/note-knowledge/mcp-summarizer.json +0 -21
  230. package/mcp-configs/note-knowledge/mediawiki-mcp.json +0 -21
  231. package/mcp-configs/note-knowledge/openzim-mcp.json +0 -20
  232. package/mcp-configs/note-knowledge/zettelkasten-mcp.json +0 -21
  233. package/mcp-configs/reference-mgr/academic-paper-mcp-http.json +0 -20
  234. package/mcp-configs/reference-mgr/academix.json +0 -20
  235. package/mcp-configs/reference-mgr/arxiv-cli.json +0 -17
  236. package/mcp-configs/reference-mgr/arxiv-research-mcp.json +0 -21
  237. package/mcp-configs/reference-mgr/arxiv-search-mcp.json +0 -17
  238. package/mcp-configs/reference-mgr/chiken.json +0 -17
  239. package/mcp-configs/reference-mgr/claude-scholar.json +0 -17
  240. package/mcp-configs/reference-mgr/devonthink-mcp.json +0 -17
  241. package/mcp-configs/reference-mgr/google-scholar-abstract-mcp.json +0 -19
  242. package/mcp-configs/reference-mgr/google-scholar-mcp.json +0 -20
  243. package/mcp-configs/reference-mgr/mcp-paperswithcode.json +0 -21
  244. package/mcp-configs/reference-mgr/mcp-scholarly.json +0 -20
  245. package/mcp-configs/reference-mgr/mcp-simple-arxiv.json +0 -20
  246. package/mcp-configs/reference-mgr/mcp-simple-pubmed.json +0 -20
  247. package/mcp-configs/reference-mgr/mcp-zotero.json +0 -21
  248. package/mcp-configs/reference-mgr/mendeley-mcp.json +0 -20
  249. package/mcp-configs/reference-mgr/ncbi-mcp-server.json +0 -22
  250. package/mcp-configs/reference-mgr/onecite.json +0 -21
  251. package/mcp-configs/reference-mgr/paper-search-mcp.json +0 -21
  252. package/mcp-configs/reference-mgr/pubmed-search-mcp.json +0 -21
  253. package/mcp-configs/reference-mgr/scholar-mcp.json +0 -21
  254. package/mcp-configs/reference-mgr/scholar-multi-mcp.json +0 -21
  255. package/mcp-configs/reference-mgr/seerai.json +0 -21
  256. package/mcp-configs/reference-mgr/semantic-scholar-fastmcp.json +0 -21
  257. package/mcp-configs/reference-mgr/sourcelibrary.json +0 -20
  258. package/mcp-configs/registry.json +0 -476
  259. package/mcp-configs/repository/dataverse-mcp.json +0 -33
  260. package/mcp-configs/repository/huggingface-mcp.json +0 -29
  261. package/skills/domains/business/xpert-bi-guide/SKILL.md +0 -84
  262. package/skills/domains/education/edumcp-guide/SKILL.md +0 -74
  263. package/skills/literature/search/paper-search-mcp-guide/SKILL.md +0 -107
  264. package/skills/research/automation/mcp-server-guide/SKILL.md +0 -211
  265. package/skills/tools/knowledge-graph/paperpile-notion-guide/SKILL.md +0 -84
  266. package/src/tools/semantic-scholar.ts +0 -66
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
4
 
5
5
  const EUTILS = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils";
6
6
 
@@ -53,23 +53,27 @@ export function createPubMedTools(
53
53
  if (input.max_date) searchParams.set("maxdate", input.max_date);
54
54
  if (input.min_date || input.max_date) searchParams.set("datetype", "pdat");
55
55
 
56
- const searchRes = await fetch(`${EUTILS}/esearch.fcgi?${searchParams}`);
57
- if (!searchRes.ok)
58
- return toolResult({ error: `Search error: ${searchRes.status} ${searchRes.statusText}` });
59
- const searchData = await searchRes.json();
56
+ const searchTracked = await trackedFetch("pubmed", `${EUTILS}/esearch.fcgi?${searchParams}`);
57
+ if (isTrackedError(searchTracked)) return searchTracked;
58
+ const searchData = await searchTracked.res.json();
60
59
  const ids: string[] = searchData.esearchresult?.idlist ?? [];
61
60
 
62
- if (ids.length === 0) return toolResult({ total_count: searchData.esearchresult?.count ?? 0, articles: [] });
61
+ if (ids.length === 0) {
62
+ return toolResult({
63
+ total_count: searchData.esearchresult?.count ?? 0,
64
+ articles: [],
65
+ _source_health: { source: "pubmed", latency_ms: searchTracked.latency_ms },
66
+ });
67
+ }
63
68
 
64
69
  const summaryParams = new URLSearchParams({
65
70
  db: "pubmed",
66
71
  id: ids.join(","),
67
72
  retmode: "json",
68
73
  });
69
- const summaryRes = await fetch(`${EUTILS}/esummary.fcgi?${summaryParams}`);
70
- if (!summaryRes.ok)
71
- return toolResult({ error: `Summary error: ${summaryRes.status} ${summaryRes.statusText}` });
72
- const summaryData = await summaryRes.json();
74
+ const summaryTracked = await trackedFetch("pubmed", `${EUTILS}/esummary.fcgi?${summaryParams}`);
75
+ if (isTrackedError(summaryTracked)) return summaryTracked;
76
+ const summaryData = await summaryTracked.res.json();
73
77
 
74
78
  const articles = ids.map((id) => {
75
79
  const doc = summaryData.result?.[id];
@@ -92,6 +96,7 @@ export function createPubMedTools(
92
96
  return toolResult({
93
97
  total_count: parseInt(searchData.esearchresult?.count ?? "0", 10),
94
98
  articles,
99
+ _source_health: { source: "pubmed", latency_ms: summaryTracked.latency_ms },
95
100
  });
96
101
  },
97
102
  },
@@ -104,14 +109,17 @@ export function createPubMedTools(
104
109
  pmid: Type.String({ description: "PubMed ID (numeric string)" }),
105
110
  }),
106
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
+ }
107
115
  const params = new URLSearchParams({
108
116
  db: "pubmed",
109
117
  id: input.pmid,
110
118
  retmode: "xml",
111
119
  });
112
- const res = await fetch(`${EUTILS}/efetch.fcgi?${params}`);
113
- if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
114
- const xml = await res.text();
120
+ const tracked = await trackedFetch("pubmed", `${EUTILS}/efetch.fcgi?${params}`);
121
+ if (isTrackedError(tracked)) return tracked;
122
+ const xml = await tracked.res.text();
115
123
 
116
124
  const getText = (tag: string) => {
117
125
  const m = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
@@ -162,6 +170,7 @@ export function createPubMedTools(
162
170
  pmc,
163
171
  mesh_terms: getMesh(),
164
172
  url: `https://pubmed.ncbi.nlm.nih.gov/${input.pmid}/`,
173
+ _source_health: { source: "pubmed", latency_ms: tracked.latency_ms },
165
174
  });
166
175
  },
167
176
  },
@@ -0,0 +1,118 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
+
5
+ const BASE = "https://api.ror.org/v2";
6
+
7
+ export function createRorTools(
8
+ _ctx: OpenClawPluginToolContext,
9
+ _api: OpenClawPluginApi,
10
+ ) {
11
+ return [
12
+ {
13
+ name: "search_ror",
14
+ label: "Search Research Organizations (ROR)",
15
+ description:
16
+ "Search the Research Organization Registry (ROR) for institutions, universities, labs, and funders. Returns canonical identifiers, locations, types, and links. Useful for affiliation disambiguation and finding institution metadata.",
17
+ parameters: Type.Object({
18
+ query: Type.String({
19
+ description: "Organization name or acronym to search, e.g. 'MIT', 'Max Planck', 'CNRS'",
20
+ }),
21
+ max_results: Type.Optional(
22
+ Type.Number({ description: "Max results (default 10, max 50)" }),
23
+ ),
24
+ }),
25
+ execute: async (input: { query: string; max_results?: number }) => {
26
+ const params = new URLSearchParams({
27
+ query: input.query,
28
+ });
29
+
30
+ const tracked = await trackedFetch("ror", `${BASE}/organizations?${params}`, undefined, 10_000);
31
+ if (isTrackedError(tracked)) return tracked;
32
+ const data = await tracked.res.json();
33
+
34
+ const maxResults = Math.min(input.max_results ?? 10, 50);
35
+ const items = (data.items ?? []).slice(0, maxResults).map(
36
+ (org: Record<string, unknown>) => {
37
+ const names = org.names as Array<{
38
+ value: string;
39
+ types: string[];
40
+ lang: string | null;
41
+ }> | undefined;
42
+ const locations = org.locations as Array<{
43
+ geonames_details: {
44
+ name: string;
45
+ country_name: string;
46
+ country_code: string;
47
+ country_subdivision_name?: string;
48
+ lat: number;
49
+ lng: number;
50
+ };
51
+ }> | undefined;
52
+ const links = org.links as Array<{
53
+ type: string;
54
+ value: string;
55
+ }> | undefined;
56
+ const externalIds = org.external_ids as Array<{
57
+ type: string;
58
+ all: string[];
59
+ preferred: string | null;
60
+ }> | undefined;
61
+
62
+ // Extract the display name (ror_display type preferred, then first label)
63
+ const displayName = names?.find(
64
+ (n) => n.types.includes("ror_display"),
65
+ )?.value;
66
+ const acronym = names?.find(
67
+ (n) => n.types.includes("acronym"),
68
+ )?.value;
69
+ const aliases = names
70
+ ?.filter((n) => n.types.includes("alias"))
71
+ .map((n) => n.value);
72
+ const labels = names
73
+ ?.filter((n) => n.types.includes("label") && !n.types.includes("ror_display"))
74
+ .map((n) => ({ name: n.value, lang: n.lang }));
75
+
76
+ const location = locations?.[0]?.geonames_details;
77
+ const website = links?.find((l) => l.type === "website")?.value;
78
+ const wikipedia = links?.find((l) => l.type === "wikipedia")?.value;
79
+
80
+ // Extract GRID and other IDs
81
+ const gridId = externalIds?.find((e) => e.type === "grid")?.preferred;
82
+ const wikidataId = externalIds?.find((e) => e.type === "wikidata")?.preferred;
83
+
84
+ return {
85
+ ror_id: org.id,
86
+ name: displayName ?? names?.[0]?.value,
87
+ acronym,
88
+ aliases: aliases?.length ? aliases : undefined,
89
+ labels: labels?.length ? labels : undefined,
90
+ types: org.types,
91
+ status: org.status,
92
+ established: org.established,
93
+ location: location
94
+ ? {
95
+ city: location.name,
96
+ country: location.country_name,
97
+ country_code: location.country_code,
98
+ region: location.country_subdivision_name,
99
+ }
100
+ : undefined,
101
+ website,
102
+ wikipedia,
103
+ domains: org.domains,
104
+ grid_id: gridId,
105
+ wikidata_id: wikidataId,
106
+ };
107
+ },
108
+ );
109
+
110
+ return toolResult({
111
+ total_results: data.number_of_results,
112
+ organizations: items,
113
+ _source_health: { source: "ror", latency_ms: tracked.latency_ms },
114
+ });
115
+ },
116
+ },
117
+ ];
118
+ }
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.unpaywall.org/v2";
6
6
 
@@ -22,15 +22,23 @@ 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
- const res = await fetch(
29
+ const tracked = await trackedFetch(
30
+ "unpaywall",
27
31
  `${BASE}/${encodeURIComponent(doi)}?email=${email}`,
28
32
  );
29
- if (!res.ok) {
30
- if (res.status === 404) return toolResult({ error: "DOI not found in Unpaywall" });
31
- return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
33
+ if (isTrackedError(tracked)) {
34
+ // Special handling for 404 (DOI not found)
35
+ if (tracked.details && typeof tracked.details === "object" && "error" in (tracked.details as Record<string, unknown>)) {
36
+ const errMsg = String((tracked.details as Record<string, string>).error);
37
+ if (errMsg.includes("404")) return toolResult({ error: "DOI not found in Unpaywall" });
38
+ }
39
+ return tracked;
32
40
  }
33
- const data = await res.json();
41
+ const data = await tracked.res.json();
34
42
 
35
43
  const oaLocations = (
36
44
  data.oa_locations as Record<string, unknown>[] | undefined
@@ -53,6 +61,7 @@ export function createUnpaywallTools(
53
61
  publisher: data.publisher,
54
62
  published_date: data.published_date,
55
63
  oa_locations: oaLocations,
64
+ _source_health: { source: "unpaywall", latency_ms: tracked.latency_ms },
56
65
  });
57
66
  },
58
67
  },
package/src/tools/util.ts CHANGED
@@ -9,3 +9,144 @@ export function toolResult(data: unknown) {
9
9
  const text = JSON.stringify(data, null, 2);
10
10
  return { content: [{ type: "text" as const, text }], details: data };
11
11
  }
12
+
13
+ // ── Source Health Tracking ───────────────────────────────────────────────
14
+
15
+ interface SourceHealthEntry {
16
+ source: string;
17
+ total_calls: number;
18
+ successes: number;
19
+ failures: number;
20
+ last_status: number | null;
21
+ last_latency_ms: number | null;
22
+ last_error: string | null;
23
+ avg_latency_ms: number;
24
+ last_called: string | null;
25
+ }
26
+
27
+ const healthRegistry = new Map<string, SourceHealthEntry>();
28
+
29
+ function getOrCreateEntry(source: string): SourceHealthEntry {
30
+ let entry = healthRegistry.get(source);
31
+ if (!entry) {
32
+ entry = {
33
+ source,
34
+ total_calls: 0,
35
+ successes: 0,
36
+ failures: 0,
37
+ last_status: null,
38
+ last_latency_ms: null,
39
+ last_error: null,
40
+ avg_latency_ms: 0,
41
+ last_called: null,
42
+ };
43
+ healthRegistry.set(source, entry);
44
+ }
45
+ return entry;
46
+ }
47
+
48
+ function recordSuccess(entry: SourceHealthEntry, status: number, latencyMs: number) {
49
+ entry.total_calls++;
50
+ entry.successes++;
51
+ entry.last_status = status;
52
+ entry.last_latency_ms = latencyMs;
53
+ entry.last_error = null;
54
+ entry.last_called = new Date().toISOString();
55
+ entry.avg_latency_ms = Math.round(
56
+ (entry.avg_latency_ms * (entry.total_calls - 1) + latencyMs) / entry.total_calls,
57
+ );
58
+ }
59
+
60
+ function recordFailure(entry: SourceHealthEntry, error: string, status?: number) {
61
+ entry.total_calls++;
62
+ entry.failures++;
63
+ entry.last_status = status ?? null;
64
+ entry.last_error = error;
65
+ entry.last_called = new Date().toISOString();
66
+ }
67
+
68
+ export interface TrackedResponse {
69
+ res: Response;
70
+ latency_ms: number;
71
+ source: string;
72
+ }
73
+
74
+ /**
75
+ * Fetch with automatic source health tracking, timeout, and error handling.
76
+ *
77
+ * @param source - Source identifier (e.g. "europe_pmc", "dblp")
78
+ * @param url - Request URL
79
+ * @param options - Fetch options
80
+ * @param timeoutMs - Timeout in milliseconds (default: 10000)
81
+ * @returns TrackedResponse on success, or a toolResult error on failure
82
+ */
83
+ export async function trackedFetch(
84
+ source: string,
85
+ url: string,
86
+ options?: RequestInit,
87
+ timeoutMs = 10_000,
88
+ ): Promise<TrackedResponse | ReturnType<typeof toolResult>> {
89
+ const entry = getOrCreateEntry(source);
90
+ const start = Date.now();
91
+
92
+ try {
93
+ const res = await fetch(url, {
94
+ ...options,
95
+ signal: AbortSignal.timeout(timeoutMs),
96
+ });
97
+ const latency = Date.now() - start;
98
+
99
+ if (!res.ok) {
100
+ recordFailure(entry, `HTTP ${res.status} ${res.statusText}`, res.status);
101
+ return toolResult({
102
+ error: `${source} API error: ${res.status} ${res.statusText}`,
103
+ _source_health: {
104
+ source,
105
+ status: res.status,
106
+ latency_ms: latency,
107
+ success_rate: entry.total_calls > 0
108
+ ? `${Math.round((entry.successes / entry.total_calls) * 100)}%`
109
+ : "N/A",
110
+ },
111
+ });
112
+ }
113
+
114
+ recordSuccess(entry, res.status, latency);
115
+ return { res, latency_ms: latency, source };
116
+ } catch (err) {
117
+ const latency = Date.now() - start;
118
+ const message = err instanceof Error ? err.message : String(err);
119
+ const isTimeout = message.includes("abort") || message.includes("timeout");
120
+ recordFailure(entry, isTimeout ? `Timeout after ${timeoutMs}ms` : message);
121
+
122
+ return toolResult({
123
+ error: `${source} ${isTimeout ? "timeout" : "network error"}: ${message}`,
124
+ _source_health: {
125
+ source,
126
+ latency_ms: latency,
127
+ success_rate: entry.total_calls > 0
128
+ ? `${Math.round((entry.successes / entry.total_calls) * 100)}%`
129
+ : "N/A",
130
+ suggestion: isTimeout
131
+ ? "This source is slow. Try a different source or reduce result count."
132
+ : "This source may be temporarily unavailable. Try an alternative.",
133
+ },
134
+ });
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Get health status for all tracked sources. Useful for agent decision-making.
140
+ */
141
+ export function getSourceHealth(): SourceHealthEntry[] {
142
+ return Array.from(healthRegistry.values());
143
+ }
144
+
145
+ /**
146
+ * Check if a tracked fetch result is an error (toolResult) or success (TrackedResponse).
147
+ */
148
+ export function isTrackedError(
149
+ result: TrackedResponse | ReturnType<typeof toolResult>,
150
+ ): result is ReturnType<typeof toolResult> {
151
+ return "content" in result && "details" in result;
152
+ }
@@ -0,0 +1,157 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
+
5
+ const BASE = "https://zenodo.org/api";
6
+
7
+ export function createZenodoTools(
8
+ _ctx: OpenClawPluginToolContext,
9
+ _api: OpenClawPluginApi,
10
+ ) {
11
+ return [
12
+ {
13
+ name: "search_zenodo",
14
+ label: "Search Records (Zenodo)",
15
+ description:
16
+ "Search Zenodo for open research data, software, publications, and other research outputs. Covers 3M+ records with DOIs. Uniquely includes datasets, software, presentations, and other non-publication outputs.",
17
+ parameters: Type.Object({
18
+ query: Type.String({ description: "Search query" }),
19
+ type: Type.Optional(
20
+ Type.String({
21
+ description:
22
+ "Resource type filter: 'publication', 'dataset', 'software', 'image', 'video', 'lesson', 'poster', 'presentation'",
23
+ }),
24
+ ),
25
+ size: Type.Optional(
26
+ Type.Number({ description: "Max results (default 10, max 100)" }),
27
+ ),
28
+ sort: Type.Optional(
29
+ Type.String({
30
+ description:
31
+ "Sort by: 'bestmatch' (relevance), 'mostrecent', '-mostrecent' (oldest first)",
32
+ }),
33
+ ),
34
+ access_right: Type.Optional(
35
+ Type.String({
36
+ description: "Access filter: 'open', 'embargoed', 'restricted', 'closed'",
37
+ }),
38
+ ),
39
+ }),
40
+ execute: async (input: {
41
+ query: string;
42
+ type?: string;
43
+ size?: number;
44
+ sort?: string;
45
+ access_right?: string;
46
+ }) => {
47
+ const params = new URLSearchParams({
48
+ q: input.query,
49
+ size: String(Math.min(input.size ?? 10, 100)),
50
+ });
51
+ if (input.type) params.set("type", input.type);
52
+ if (input.sort) params.set("sort", input.sort);
53
+ if (input.access_right) params.set("access_right", input.access_right);
54
+
55
+ const result = await trackedFetch("zenodo", `${BASE}/records?${params}`, undefined, 10_000);
56
+ if (isTrackedError(result)) return result;
57
+ const data = await result.res.json();
58
+
59
+ return toolResult({
60
+ total_results: data.hits?.total,
61
+ source: "zenodo",
62
+ records: data.hits?.hits?.map(
63
+ (h: Record<string, unknown>) => {
64
+ const meta = h.metadata as Record<string, unknown> | undefined;
65
+ const creators = meta?.creators as Array<{ name: string; affiliation?: string }> | undefined;
66
+ const resourceType = meta?.resource_type as Record<string, string> | undefined;
67
+ const license = meta?.license as Record<string, string> | undefined;
68
+
69
+ return {
70
+ id: h.id,
71
+ doi: meta?.doi ?? h.doi,
72
+ title: meta?.title ?? h.title,
73
+ authors: creators?.map((c) => c.name),
74
+ year: meta?.publication_date
75
+ ? String(meta.publication_date).slice(0, 4)
76
+ : undefined,
77
+ publication_date: meta?.publication_date,
78
+ resource_type: resourceType?.type,
79
+ access_right: meta?.access_right,
80
+ keywords: meta?.keywords,
81
+ license: license?.id,
82
+ description: meta?.description
83
+ ? String(meta.description).replace(/<[^>]*>/g, "").slice(0, 300)
84
+ : undefined,
85
+ url: (h.links as Record<string, string>)?.self_html
86
+ ?? `https://zenodo.org/records/${h.id}`,
87
+ source: "zenodo",
88
+ };
89
+ },
90
+ ),
91
+ _latency_ms: result.latency_ms,
92
+ });
93
+ },
94
+ },
95
+ {
96
+ name: "get_zenodo_record",
97
+ label: "Get Record Details (Zenodo)",
98
+ description:
99
+ "Get full details of a specific Zenodo record by its numeric ID. Returns metadata, files, and download links.",
100
+ parameters: Type.Object({
101
+ record_id: Type.String({
102
+ description: "Zenodo record ID (numeric), e.g. '7042164'",
103
+ }),
104
+ }),
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
+ }
109
+ const id = input.record_id.replace(/\D/g, "");
110
+ const result = await trackedFetch("zenodo", `${BASE}/records/${id}`, undefined, 10_000);
111
+ if (isTrackedError(result)) return result;
112
+ const h = await result.res.json();
113
+
114
+ const meta = h.metadata as Record<string, unknown> | undefined;
115
+ const creators = meta?.creators as Array<{ name: string; affiliation?: string }> | undefined;
116
+ const resourceType = meta?.resource_type as Record<string, string> | undefined;
117
+ const license = meta?.license as Record<string, string> | undefined;
118
+ const files = h.files as Array<{ key: string; size: number; links?: Record<string, string> }> | undefined;
119
+ const stats = h.stats as Record<string, number> | undefined;
120
+
121
+ return toolResult({
122
+ id: h.id,
123
+ doi: meta?.doi ?? h.doi,
124
+ title: meta?.title ?? h.title,
125
+ authors: creators?.map((c) => c.name),
126
+ year: meta?.publication_date
127
+ ? String(meta.publication_date).slice(0, 4)
128
+ : undefined,
129
+ publication_date: meta?.publication_date,
130
+ description: meta?.description
131
+ ? String(meta.description).replace(/<[^>]*>/g, "")
132
+ : undefined,
133
+ resource_type: resourceType?.type,
134
+ access_right: meta?.access_right,
135
+ keywords: meta?.keywords,
136
+ license: license?.id,
137
+ version: meta?.version,
138
+ files: files?.map((f) => ({
139
+ filename: f.key,
140
+ size_bytes: f.size,
141
+ download_url: f.links?.self,
142
+ })),
143
+ stats: stats
144
+ ? {
145
+ downloads: stats.downloads,
146
+ views: stats.views,
147
+ }
148
+ : undefined,
149
+ url: (h.links as Record<string, string>)?.self_html
150
+ ?? `https://zenodo.org/records/${h.id}`,
151
+ source: "zenodo",
152
+ _latency_ms: result.latency_ms,
153
+ });
154
+ },
155
+ },
156
+ ];
157
+ }
@@ -1,17 +0,0 @@
1
- {
2
- "id": "ChatSpatial",
3
- "name": "ChatSpatial",
4
- "description": "Spatial transcriptomics analysis via natural language",
5
- "category": "academic-db",
6
- "install": {
7
- "runtime": "python",
8
- "package": "ChatSpatial",
9
- "command": "pip install ChatSpatial"
10
- },
11
- "config": {
12
- "env": {}
13
- },
14
- "tools": ["load_data", "identify_domains", "visualize"],
15
- "verified": false,
16
- "source": "https://github.com/cafferychen777/ChatSpatial"
17
- }
@@ -1,17 +0,0 @@
1
- {
2
- "id": "academia-mcp",
3
- "name": "Academia MCP",
4
- "description": "Tools for automatic scientific research",
5
- "category": "academic-db",
6
- "install": {
7
- "runtime": "python",
8
- "package": "academia-mcp",
9
- "command": "pip install academia-mcp"
10
- },
11
- "config": {
12
- "env": {}
13
- },
14
- "tools": ["search", "analyze"],
15
- "verified": false,
16
- "source": "https://github.com/IlyaGusev/academia_mcp"
17
- }
@@ -1,17 +0,0 @@
1
- {
2
- "id": "academic-paper-explorer",
3
- "name": "Academic Paper Explorer",
4
- "description": "Academic paper discovery via arXiv and Semantic Scholar",
5
- "category": "academic-db",
6
- "install": {
7
- "runtime": "node",
8
- "package": "academic-paper-explorer",
9
- "command": "npm install -g academic-paper-explorer"
10
- },
11
- "config": {
12
- "env": {}
13
- },
14
- "tools": ["search", "read_paper", "find_code"],
15
- "verified": false,
16
- "source": "https://github.com/Leonard013/academic-paper-explorer"
17
- }
@@ -1,17 +0,0 @@
1
- {
2
- "id": "academic-search-mcp-server",
3
- "name": "Academic Paper Search",
4
- "description": "Academic Paper Search MCP for Semantic Scholar and Crossref APIs",
5
- "category": "academic-db",
6
- "install": {
7
- "runtime": "python",
8
- "package": "academic-search-mcp-server",
9
- "command": "pip install academic-search-mcp-server"
10
- },
11
- "config": {
12
- "env": {}
13
- },
14
- "tools": ["search_papers", "fetch_paper_details", "search_by_topic"],
15
- "verified": false,
16
- "source": "https://github.com/afrise/academic-search-mcp-server"
17
- }
@@ -1,17 +0,0 @@
1
- {
2
- "id": "agentinterviews-mcp",
3
- "name": "Agent Interviews",
4
- "description": "AI-powered qualitative research interviews at scale",
5
- "category": "academic-db",
6
- "install": {
7
- "runtime": "python",
8
- "package": "agentinterviews-mcp",
9
- "command": "pip install agentinterviews-mcp"
10
- },
11
- "config": {
12
- "env": {}
13
- },
14
- "tools": ["create_interviewer", "manage_project", "analyze_data"],
15
- "verified": false,
16
- "source": "https://github.com/thinkchainai/agentinterviews_mcp"
17
- }
@@ -1,17 +0,0 @@
1
- {
2
- "id": "all-in-mcp",
3
- "name": "All-in MCP",
4
- "description": "Academic paper search and PDF processing",
5
- "category": "academic-db",
6
- "install": {
7
- "runtime": "python",
8
- "package": "all-in-mcp",
9
- "command": "pip install all-in-mcp"
10
- },
11
- "config": {
12
- "env": {}
13
- },
14
- "tools": ["search", "process_pdf"],
15
- "verified": false,
16
- "source": "https://github.com/isomoes/all-in-mcp"
17
- }
@@ -1,20 +0,0 @@
1
- {
2
- "id": "alphafold-mcp",
3
- "name": "AlphaFold MCP",
4
- "description": "AlphaFold Protein Structure Database for structure prediction",
5
- "category": "academic-db",
6
- "install": {
7
- "runtime": "node",
8
- "package": "alphafold-mcp-server",
9
- "command": "npm install -g alphafold-mcp-server"
10
- },
11
- "config": {
12
- "env": {}
13
- },
14
- "tools": [
15
- "get_structure",
16
- "search_protein"
17
- ],
18
- "verified": false,
19
- "source": "https://github.com/ac3xx/AlphaFold-MCP-Server"
20
- }