@wentorai/research-plugins 1.2.0 → 1.2.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "research-plugins",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "name": "Research Plugins",
5
5
  "description": "487 academic research skills, 13 agent tools, 150 MCP configs & 6 curated lists for Research-Claw",
6
6
  "skills": ["./skills"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wentorai/research-plugins",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "description": "487 academic research skills, 150 MCP configs, 13 agent tools, and 6 curated lists for Research-Claw and AI agents",
6
6
  "keywords": [
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
3
3
 
4
4
  const BASE = "https://export.arxiv.org/api/query";
5
5
 
6
+ function toolResult(data: unknown) {
7
+ const text = JSON.stringify(data, null, 2);
8
+ return { content: [{ type: "text" as const, text }] };
9
+ }
10
+
6
11
  function parseArxivXml(xml: string) {
7
12
  const entries: Record<string, unknown>[] = [];
8
13
  const entryBlocks = xml.split("<entry>").slice(1);
@@ -59,9 +64,10 @@ export function createArxivTools(
59
64
  return [
60
65
  {
61
66
  name: "search_arxiv",
67
+ label: "Search Papers (arXiv)",
62
68
  description:
63
69
  "Search arXiv preprint repository. Covers physics, math, CS, biology, quantitative finance, statistics, and more.",
64
- inputSchema: Type.Object({
70
+ parameters: Type.Object({
65
71
  query: Type.String({
66
72
  description:
67
73
  "Search query. Supports field prefixes: ti: (title), au: (author), abs: (abstract), cat: (category). E.g. 'ti:transformer AND cat:cs.CL'",
@@ -79,7 +85,7 @@ export function createArxivTools(
79
85
  Type.String({ description: "Sort order: 'ascending' or 'descending'" }),
80
86
  ),
81
87
  }),
82
- handler: async (input: {
88
+ execute: async (input: {
83
89
  query: string;
84
90
  max_results?: number;
85
91
  sort_by?: string;
@@ -93,7 +99,7 @@ export function createArxivTools(
93
99
  });
94
100
 
95
101
  const res = await fetch(`${BASE}?${params}`);
96
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
102
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
97
103
  const xml = await res.text();
98
104
 
99
105
  const totalMatch = xml.match(
@@ -101,30 +107,31 @@ export function createArxivTools(
101
107
  );
102
108
  const total = totalMatch ? parseInt(totalMatch[1], 10) : 0;
103
109
 
104
- return {
110
+ return toolResult({
105
111
  total_results: total,
106
112
  papers: parseArxivXml(xml),
107
- };
113
+ });
108
114
  },
109
115
  },
110
116
  {
111
117
  name: "get_arxiv_paper",
118
+ label: "Get Paper Details (arXiv)",
112
119
  description:
113
120
  "Get detailed information about a specific arXiv paper by its ID.",
114
- inputSchema: Type.Object({
121
+ parameters: Type.Object({
115
122
  arxiv_id: Type.String({
116
123
  description: "arXiv paper ID, e.g. '2301.00001' or '2301.00001v2'",
117
124
  }),
118
125
  }),
119
- handler: async (input: { arxiv_id: string }) => {
126
+ execute: async (input: { arxiv_id: string }) => {
120
127
  const id = input.arxiv_id.replace("arXiv:", "");
121
128
  const params = new URLSearchParams({ id_list: id });
122
129
  const res = await fetch(`${BASE}?${params}`);
123
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
130
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
124
131
  const xml = await res.text();
125
132
  const papers = parseArxivXml(xml);
126
- if (papers.length === 0) return { error: "Paper not found" };
127
- return papers[0];
133
+ if (papers.length === 0) return toolResult({ error: "Paper not found" });
134
+ return toolResult(papers[0]);
128
135
  },
129
136
  },
130
137
  ];
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
3
3
 
4
4
  const BASE = "https://api.crossref.org";
5
5
 
6
+ function toolResult(data: unknown) {
7
+ const text = JSON.stringify(data, null, 2);
8
+ return { content: [{ type: "text" as const, text }] };
9
+ }
10
+
6
11
  export function createCrossRefTools(
7
12
  _ctx: OpenClawPluginToolContext,
8
13
  _api: OpenClawPluginApi,
@@ -14,22 +19,23 @@ export function createCrossRefTools(
14
19
  return [
15
20
  {
16
21
  name: "resolve_doi",
22
+ label: "Resolve DOI (CrossRef)",
17
23
  description:
18
24
  "Resolve a DOI to get full bibliographic metadata from CrossRef (title, authors, journal, dates, references).",
19
- inputSchema: Type.Object({
25
+ parameters: Type.Object({
20
26
  doi: Type.String({
21
27
  description: "DOI to resolve, e.g. '10.1038/nature12373'",
22
28
  }),
23
29
  }),
24
- handler: async (input: { doi: string }) => {
30
+ execute: async (input: { doi: string }) => {
25
31
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
26
32
  const res = await fetch(`${BASE}/works/${encodeURIComponent(doi)}`, {
27
33
  headers,
28
34
  });
29
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
35
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
30
36
  const data = await res.json();
31
37
  const w = data.message;
32
- return {
38
+ return toolResult({
33
39
  doi: w.DOI,
34
40
  title: w.title?.[0],
35
41
  authors: w.author?.map(
@@ -44,14 +50,15 @@ export function createCrossRefTools(
44
50
  url: w.URL,
45
51
  abstract: w.abstract,
46
52
  license: w.license?.[0]?.URL,
47
- };
53
+ });
48
54
  },
49
55
  },
50
56
  {
51
57
  name: "search_crossref",
58
+ label: "Search Works (CrossRef)",
52
59
  description:
53
60
  "Search CrossRef for scholarly works by query. Covers 150M+ DOIs across all publishers.",
54
- inputSchema: Type.Object({
61
+ parameters: Type.Object({
55
62
  query: Type.String({ description: "Search query" }),
56
63
  limit: Type.Optional(
57
64
  Type.Number({ description: "Max results (default 10, max 100)" }),
@@ -71,7 +78,7 @@ export function createCrossRefTools(
71
78
  }),
72
79
  ),
73
80
  }),
74
- handler: async (input: {
81
+ execute: async (input: {
75
82
  query: string;
76
83
  limit?: number;
77
84
  from_year?: number;
@@ -89,9 +96,9 @@ export function createCrossRefTools(
89
96
  if (input.sort) params.set("sort", input.sort);
90
97
 
91
98
  const res = await fetch(`${BASE}/works?${params}`, { headers });
92
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
99
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
93
100
  const data = await res.json();
94
- return {
101
+ return toolResult({
95
102
  total_results: data.message?.["total-results"],
96
103
  items: data.message?.items?.map((w: Record<string, unknown>) => ({
97
104
  doi: w.DOI,
@@ -105,7 +112,7 @@ export function createCrossRefTools(
105
112
  type: w.type,
106
113
  cited_by: w["is-referenced-by-count"],
107
114
  })),
108
- };
115
+ });
109
116
  },
110
117
  },
111
118
  ];
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
3
3
 
4
4
  const BASE = "https://api.openalex.org";
5
5
 
6
+ function toolResult(data: unknown) {
7
+ const text = JSON.stringify(data, null, 2);
8
+ return { content: [{ type: "text" as const, text }] };
9
+ }
10
+
6
11
  export function createOpenAlexTools(
7
12
  _ctx: OpenClawPluginToolContext,
8
13
  _api: OpenClawPluginApi,
@@ -14,9 +19,10 @@ export function createOpenAlexTools(
14
19
  return [
15
20
  {
16
21
  name: "search_openalex",
22
+ label: "Search Works (OpenAlex)",
17
23
  description:
18
24
  "Search academic works via OpenAlex (free, no key required). Covers 250M+ works across all disciplines.",
19
- inputSchema: Type.Object({
25
+ parameters: Type.Object({
20
26
  query: Type.String({ description: "Search query for works" }),
21
27
  limit: Type.Optional(
22
28
  Type.Number({ description: "Max results (default 10, max 200)" }),
@@ -36,7 +42,7 @@ export function createOpenAlexTools(
36
42
  }),
37
43
  ),
38
44
  }),
39
- handler: async (input: {
45
+ execute: async (input: {
40
46
  query: string;
41
47
  limit?: number;
42
48
  from_year?: number;
@@ -57,9 +63,9 @@ export function createOpenAlexTools(
57
63
  if (input.sort_by) params.set("sort", input.sort_by);
58
64
 
59
65
  const res = await fetch(`${BASE}/works?${params}`, { headers });
60
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
66
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
61
67
  const data = await res.json();
62
- return {
68
+ return toolResult({
63
69
  total_count: data.meta?.count,
64
70
  results: data.results?.map((w: Record<string, unknown>) => ({
65
71
  id: w.id,
@@ -76,29 +82,30 @@ export function createOpenAlexTools(
76
82
  )
77
83
  : [],
78
84
  })),
79
- };
85
+ });
80
86
  },
81
87
  },
82
88
  {
83
89
  name: "get_work",
90
+ label: "Get Work Details (OpenAlex)",
84
91
  description:
85
92
  "Get full details of a work by its OpenAlex ID, DOI, or other identifier.",
86
- inputSchema: Type.Object({
93
+ parameters: Type.Object({
87
94
  work_id: Type.String({
88
95
  description:
89
96
  "Work identifier: OpenAlex ID (e.g. 'W2741809807'), DOI URL, or PMID",
90
97
  }),
91
98
  }),
92
- handler: async (input: { work_id: string }) => {
99
+ execute: async (input: { work_id: string }) => {
93
100
  const id = input.work_id.startsWith("10.")
94
101
  ? `https://doi.org/${input.work_id}`
95
102
  : input.work_id;
96
103
  const res = await fetch(`${BASE}/works/${encodeURIComponent(id)}`, {
97
104
  headers,
98
105
  });
99
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
106
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
100
107
  const w = await res.json();
101
- return {
108
+ return toolResult({
102
109
  id: w.id,
103
110
  doi: w.doi,
104
111
  title: w.title,
@@ -117,20 +124,21 @@ export function createOpenAlexTools(
117
124
  ?.slice(0, 10)
118
125
  .map((c: Record<string, unknown>) => c.display_name),
119
126
  referenced_works_count: w.referenced_works?.length,
120
- };
127
+ });
121
128
  },
122
129
  },
123
130
  {
124
131
  name: "get_author_openalex",
132
+ label: "Get Author (OpenAlex)",
125
133
  description:
126
134
  "Get author information from OpenAlex including publications, h-index, and affiliations.",
127
- inputSchema: Type.Object({
135
+ parameters: Type.Object({
128
136
  author_id: Type.String({
129
137
  description:
130
138
  "Author identifier: OpenAlex ID (e.g. 'A5023888391'), ORCID, or name search",
131
139
  }),
132
140
  }),
133
- handler: async (input: { author_id: string }) => {
141
+ execute: async (input: { author_id: string }) => {
134
142
  let url: string;
135
143
  if (
136
144
  input.author_id.startsWith("A") ||
@@ -145,14 +153,14 @@ export function createOpenAlexTools(
145
153
  per_page: "5",
146
154
  });
147
155
  const res = await fetch(`${BASE}/authors?${params}`, { headers });
148
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
149
- return res.json();
156
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
157
+ return toolResult(await res.json());
150
158
  }
151
159
 
152
160
  const res = await fetch(url, { headers });
153
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
161
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
154
162
  const a = await res.json();
155
- return {
163
+ return toolResult({
156
164
  id: a.id,
157
165
  display_name: a.display_name,
158
166
  orcid: a.orcid,
@@ -167,7 +175,7 @@ export function createOpenAlexTools(
167
175
  top_concepts: a.x_concepts
168
176
  ?.slice(0, 5)
169
177
  .map((c: Record<string, unknown>) => c.display_name),
170
- };
178
+ });
171
179
  },
172
180
  },
173
181
  ];
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
3
3
 
4
4
  const EUTILS = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils";
5
5
 
6
+ function toolResult(data: unknown) {
7
+ const text = JSON.stringify(data, null, 2);
8
+ return { content: [{ type: "text" as const, text }] };
9
+ }
10
+
6
11
  export function createPubMedTools(
7
12
  _ctx: OpenClawPluginToolContext,
8
13
  _api: OpenClawPluginApi,
@@ -10,9 +15,10 @@ export function createPubMedTools(
10
15
  return [
11
16
  {
12
17
  name: "search_pubmed",
18
+ label: "Search Articles (PubMed)",
13
19
  description:
14
20
  "Search PubMed biomedical literature database. Covers 36M+ citations from MEDLINE, life science journals, and online books.",
15
- inputSchema: Type.Object({
21
+ parameters: Type.Object({
16
22
  query: Type.String({
17
23
  description:
18
24
  "PubMed search query. Supports MeSH terms and field tags, e.g. 'CRISPR[Title] AND 2024[PDAT]'",
@@ -32,7 +38,7 @@ export function createPubMedTools(
32
38
  Type.String({ description: "Max publication date (YYYY/MM/DD)" }),
33
39
  ),
34
40
  }),
35
- handler: async (input: {
41
+ execute: async (input: {
36
42
  query: string;
37
43
  max_results?: number;
38
44
  sort?: string;
@@ -53,11 +59,11 @@ export function createPubMedTools(
53
59
 
54
60
  const searchRes = await fetch(`${EUTILS}/esearch.fcgi?${searchParams}`);
55
61
  if (!searchRes.ok)
56
- return { error: `Search error: ${searchRes.status} ${searchRes.statusText}` };
62
+ return toolResult({ error: `Search error: ${searchRes.status} ${searchRes.statusText}` });
57
63
  const searchData = await searchRes.json();
58
64
  const ids: string[] = searchData.esearchresult?.idlist ?? [];
59
65
 
60
- if (ids.length === 0) return { total_count: searchData.esearchresult?.count ?? 0, articles: [] };
66
+ if (ids.length === 0) return toolResult({ total_count: searchData.esearchresult?.count ?? 0, articles: [] });
61
67
 
62
68
  const summaryParams = new URLSearchParams({
63
69
  db: "pubmed",
@@ -66,7 +72,7 @@ export function createPubMedTools(
66
72
  });
67
73
  const summaryRes = await fetch(`${EUTILS}/esummary.fcgi?${summaryParams}`);
68
74
  if (!summaryRes.ok)
69
- return { error: `Summary error: ${summaryRes.status} ${summaryRes.statusText}` };
75
+ return toolResult({ error: `Summary error: ${summaryRes.status} ${summaryRes.statusText}` });
70
76
  const summaryData = await summaryRes.json();
71
77
 
72
78
  const articles = ids.map((id) => {
@@ -87,27 +93,28 @@ export function createPubMedTools(
87
93
  };
88
94
  });
89
95
 
90
- return {
96
+ return toolResult({
91
97
  total_count: parseInt(searchData.esearchresult?.count ?? "0", 10),
92
98
  articles,
93
- };
99
+ });
94
100
  },
95
101
  },
96
102
  {
97
103
  name: "get_article",
104
+ label: "Get Article Details (PubMed)",
98
105
  description:
99
106
  "Get detailed article metadata from PubMed by PMID, including abstract.",
100
- inputSchema: Type.Object({
107
+ parameters: Type.Object({
101
108
  pmid: Type.String({ description: "PubMed ID (numeric string)" }),
102
109
  }),
103
- handler: async (input: { pmid: string }) => {
110
+ execute: async (input: { pmid: string }) => {
104
111
  const params = new URLSearchParams({
105
112
  db: "pubmed",
106
113
  id: input.pmid,
107
114
  retmode: "xml",
108
115
  });
109
116
  const res = await fetch(`${EUTILS}/efetch.fcgi?${params}`);
110
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
117
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
111
118
  const xml = await res.text();
112
119
 
113
120
  const getText = (tag: string) => {
@@ -148,7 +155,7 @@ export function createPubMedTools(
148
155
  xml.match(/<ArticleId IdType="pmc">([\s\S]*?)<\/ArticleId>/)?.[1]?.trim() ??
149
156
  "";
150
157
 
151
- return {
158
+ return toolResult({
152
159
  pmid: input.pmid,
153
160
  title: getText("ArticleTitle"),
154
161
  abstract: getAbstract(),
@@ -159,7 +166,7 @@ export function createPubMedTools(
159
166
  pmc,
160
167
  mesh_terms: getMesh(),
161
168
  url: `https://pubmed.ncbi.nlm.nih.gov/${input.pmid}/`,
162
- };
169
+ });
163
170
  },
164
171
  },
165
172
  ];
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
3
3
 
4
4
  const BASE = "https://api.semanticscholar.org/graph/v1";
5
5
 
6
+ function toolResult(data: unknown) {
7
+ const text = JSON.stringify(data, null, 2);
8
+ return { content: [{ type: "text" as const, text }] };
9
+ }
10
+
6
11
  export function createSemanticScholarTools(
7
12
  ctx: OpenClawPluginToolContext,
8
13
  api: OpenClawPluginApi,
@@ -15,9 +20,10 @@ export function createSemanticScholarTools(
15
20
  return [
16
21
  {
17
22
  name: "search_papers",
23
+ label: "Search Papers (Semantic Scholar)",
18
24
  description:
19
25
  "Search academic papers on Semantic Scholar. Returns titles, abstracts, authors, citation counts.",
20
- inputSchema: Type.Object({
26
+ parameters: Type.Object({
21
27
  query: Type.String({ description: "Search query keywords" }),
22
28
  limit: Type.Optional(
23
29
  Type.Number({ description: "Max results to return (default 10, max 100)" }),
@@ -35,7 +41,7 @@ export function createSemanticScholarTools(
35
41
  Type.Boolean({ description: "Only return open access papers" }),
36
42
  ),
37
43
  }),
38
- handler: async (input: {
44
+ execute: async (input: {
39
45
  query: string;
40
46
  limit?: number;
41
47
  year?: string;
@@ -54,36 +60,38 @@ export function createSemanticScholarTools(
54
60
  if (input.open_access_only) params.set("openAccessPdf", "");
55
61
 
56
62
  const res = await fetch(`${BASE}/paper/search?${params}`, { headers });
57
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
58
- return res.json();
63
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
64
+ return toolResult(await res.json());
59
65
  },
60
66
  },
61
67
  {
62
68
  name: "get_paper",
69
+ label: "Get Paper Details (Semantic Scholar)",
63
70
  description:
64
71
  "Get detailed information about a specific paper by its Semantic Scholar ID, DOI, or ArXiv ID.",
65
- inputSchema: Type.Object({
72
+ parameters: Type.Object({
66
73
  paper_id: Type.String({
67
74
  description:
68
75
  "Paper identifier: Semantic Scholar ID, DOI (e.g. '10.1234/...'), or ArXiv ID (e.g. 'arXiv:2301.00001')",
69
76
  }),
70
77
  }),
71
- handler: async (input: { paper_id: string }) => {
78
+ execute: async (input: { paper_id: string }) => {
72
79
  const fields =
73
80
  "title,abstract,authors,year,citationCount,referenceCount,tldr,url,venue,isOpenAccess,openAccessPdf,fieldsOfStudy,publicationDate";
74
81
  const res = await fetch(
75
82
  `${BASE}/paper/${encodeURIComponent(input.paper_id)}?fields=${fields}`,
76
83
  { headers },
77
84
  );
78
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
79
- return res.json();
85
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
86
+ return toolResult(await res.json());
80
87
  },
81
88
  },
82
89
  {
83
90
  name: "get_citations",
91
+ label: "Get Citations (Semantic Scholar)",
84
92
  description:
85
93
  "Get papers that cite a given paper. Useful for forward citation tracking.",
86
- inputSchema: Type.Object({
94
+ parameters: Type.Object({
87
95
  paper_id: Type.String({ description: "Paper identifier (S2 ID, DOI, or ArXiv ID)" }),
88
96
  limit: Type.Optional(
89
97
  Type.Number({ description: "Max citations to return (default 20, max 100)" }),
@@ -92,7 +100,7 @@ export function createSemanticScholarTools(
92
100
  Type.Number({ description: "Pagination offset" }),
93
101
  ),
94
102
  }),
95
- handler: async (input: { paper_id: string; limit?: number; offset?: number }) => {
103
+ execute: async (input: { paper_id: string; limit?: number; offset?: number }) => {
96
104
  const fields = "title,authors,year,citationCount,url,abstract";
97
105
  const limit = Math.min(input.limit ?? 20, 100);
98
106
  const offset = input.offset ?? 0;
@@ -100,8 +108,8 @@ export function createSemanticScholarTools(
100
108
  `${BASE}/paper/${encodeURIComponent(input.paper_id)}/citations?fields=${fields}&limit=${limit}&offset=${offset}`,
101
109
  { headers },
102
110
  );
103
- if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
104
- return res.json();
111
+ if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
112
+ return toolResult(await res.json());
105
113
  },
106
114
  },
107
115
  ];
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
3
3
 
4
4
  const BASE = "https://api.unpaywall.org/v2";
5
5
 
6
+ function toolResult(data: unknown) {
7
+ const text = JSON.stringify(data, null, 2);
8
+ return { content: [{ type: "text" as const, text }] };
9
+ }
10
+
6
11
  export function createUnpaywallTools(
7
12
  _ctx: OpenClawPluginToolContext,
8
13
  _api: OpenClawPluginApi,
@@ -12,21 +17,22 @@ export function createUnpaywallTools(
12
17
  return [
13
18
  {
14
19
  name: "find_oa_version",
20
+ label: "Find Open Access Version (Unpaywall)",
15
21
  description:
16
22
  "Find open access versions of a paper by DOI using Unpaywall. Returns free PDF links from repositories, preprint servers, and publisher OA pages.",
17
- inputSchema: Type.Object({
23
+ parameters: Type.Object({
18
24
  doi: Type.String({
19
25
  description: "DOI of the paper, e.g. '10.1038/nature12373'",
20
26
  }),
21
27
  }),
22
- handler: async (input: { doi: string }) => {
28
+ execute: async (input: { doi: string }) => {
23
29
  const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
24
30
  const res = await fetch(
25
31
  `${BASE}/${encodeURIComponent(doi)}?email=${email}`,
26
32
  );
27
33
  if (!res.ok) {
28
- if (res.status === 404) return { error: "DOI not found in Unpaywall" };
29
- return { error: `API error: ${res.status} ${res.statusText}` };
34
+ if (res.status === 404) return toolResult({ error: "DOI not found in Unpaywall" });
35
+ return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
30
36
  }
31
37
  const data = await res.json();
32
38
 
@@ -40,7 +46,7 @@ export function createUnpaywallTools(
40
46
  version: loc.version,
41
47
  }));
42
48
 
43
- return {
49
+ return toolResult({
44
50
  doi: data.doi,
45
51
  title: data.title,
46
52
  is_oa: data.is_oa,
@@ -51,7 +57,7 @@ export function createUnpaywallTools(
51
57
  publisher: data.publisher,
52
58
  published_date: data.published_date,
53
59
  oa_locations: oaLocations,
54
- };
60
+ });
55
61
  },
56
62
  },
57
63
  ];