@mrfentmen/doaj-mcp 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -2
- package/dist/api.d.ts +3 -0
- package/dist/api.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +18 -0
- package/package.json +42 -4
package/README.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DOAJ MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Search the Directory of Open Access Journals for free full-text articles. Keyless — no signup, no API key, no environment variables.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx -y @mrfentmen/doaj-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add it to your MCP client:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"doaj-mcp": { "command": "npx", "args": ["-y", "@mrfentmen/doaj-mcp"] }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
| Tool | What it does |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `search` | Search DOAJ open access articles |
|
|
26
|
+
|
|
27
|
+
## Notes
|
|
28
|
+
|
|
29
|
+
DOAJ lists vetted open access journals and their articles. Every result can be read for free.
|
|
30
|
+
|
|
31
|
+
## Licence
|
|
32
|
+
|
|
33
|
+
MIT
|
package/dist/api.d.ts
ADDED
package/dist/api.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOAJ — Search the Directory of Open Access Journals for free full-text articles. Keyless: no signup, no API key, no environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Verified live against doaj.org.
|
|
5
|
+
*/
|
|
6
|
+
const API = "https://doaj.org/api";
|
|
7
|
+
export class DoajError extends Error {
|
|
8
|
+
}
|
|
9
|
+
const val = (v) => {
|
|
10
|
+
if (typeof v === "string")
|
|
11
|
+
return v;
|
|
12
|
+
if (typeof v === "number")
|
|
13
|
+
return Number.isFinite(v) ? String(v) : "";
|
|
14
|
+
if (Array.isArray(v))
|
|
15
|
+
return v.length > 0 ? val(v[0]) : "";
|
|
16
|
+
if (v && typeof v === "object") {
|
|
17
|
+
const o = v;
|
|
18
|
+
if ("en" in o)
|
|
19
|
+
return val(o.en);
|
|
20
|
+
const first = Object.values(o)[0];
|
|
21
|
+
return first === undefined ? "" : val(first);
|
|
22
|
+
}
|
|
23
|
+
return "";
|
|
24
|
+
};
|
|
25
|
+
const at = (o, path) => path === "" ? o : path.split(".").reduce((acc, k) => (acc && typeof acc === "object" ? acc[k] : undefined), o);
|
|
26
|
+
const rows = (v) => (Array.isArray(v) ? v : []);
|
|
27
|
+
async function get(url, init, timeoutMs = 25000) {
|
|
28
|
+
const ctrl = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
...init,
|
|
33
|
+
signal: ctrl.signal,
|
|
34
|
+
headers: { "user-agent": "doaj-mcp", accept: "application/json", ...(init?.headers ?? {}) },
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
throw new DoajError(`DOAJ returned HTTP ${res.status}`);
|
|
38
|
+
return (await res.json());
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
if (e instanceof DoajError)
|
|
42
|
+
throw e;
|
|
43
|
+
if (e.name === "AbortError")
|
|
44
|
+
throw new DoajError("DOAJ request timed out");
|
|
45
|
+
throw new DoajError(`DOAJ request failed: ${e.message}`);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function search(query, limit = 10) {
|
|
52
|
+
const q = query.trim();
|
|
53
|
+
if (q === "")
|
|
54
|
+
throw new DoajError("query must not be empty");
|
|
55
|
+
const size = Math.min(100, Math.max(1, limit));
|
|
56
|
+
const body = await get(`${API}/search/articles/${encodeURIComponent(q)}?pageSize=${size}`);
|
|
57
|
+
const items = rows(at(body, "results"));
|
|
58
|
+
if (items.length === 0)
|
|
59
|
+
return `DOAJ found nothing for "${q}".`;
|
|
60
|
+
const total = val(at(body, "total"));
|
|
61
|
+
const lines = items.map((r, i) => {
|
|
62
|
+
const title = val(at(r, "bibjson.title"));
|
|
63
|
+
const year = val(at(r, "bibjson.year"));
|
|
64
|
+
const journal = val(at(r, "bibjson.journal.title"));
|
|
65
|
+
const bits = [year, journal].filter((b) => b !== "").join(" · ");
|
|
66
|
+
return `${i + 1}. ${title}${bits === "" ? "" : ` — ${bits}`}`;
|
|
67
|
+
});
|
|
68
|
+
return [`DOAJ articles for "${q}" — ${total} total, showing ${items.length}`, "", ...lines].join("\n");
|
|
69
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createServer } from "./server.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
const server = createServer();
|
|
6
|
+
const transport = new StdioServerTransport();
|
|
7
|
+
await server.connect(transport);
|
|
8
|
+
console.error("doaj-mcp running on stdio");
|
|
9
|
+
}
|
|
10
|
+
main().catch((err) => {
|
|
11
|
+
console.error("Fatal error:", err);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { search } from "./api.js";
|
|
4
|
+
const text = (t) => ({ content: [{ type: "text", text: t }] });
|
|
5
|
+
const fail = (t) => ({ content: [{ type: "text", text: t }], isError: true });
|
|
6
|
+
const errorMessage = (e) => `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
7
|
+
export function createServer() {
|
|
8
|
+
const server = new McpServer({ name: "doaj-mcp", version: "1.0.0" });
|
|
9
|
+
server.tool("search", "Search DOAJ open access articles", { query: z.string().min(1).describe("Search text"), limit: z.number().int().min(1).max(100).default(10).describe("How many results") }, async ({ query, limit }) => {
|
|
10
|
+
try {
|
|
11
|
+
return text(await search(query, limit));
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return fail(errorMessage(e));
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return server;
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrfentmen/doaj-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "DOAJ: search open access journal articles with titles and years - keyless.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"mcpName": "io.github.mrfentmen/doaj-mcp",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mrfentmen/awesome-servers.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"doaj-mcp": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"test": "node tests/smoke.mjs"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"doaj",
|
|
26
|
+
"research",
|
|
27
|
+
"open-access",
|
|
28
|
+
"journals",
|
|
29
|
+
"papers",
|
|
30
|
+
"keyless"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
35
|
+
"zod": "^3.23.8"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"typescript": "^5.6.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
}
|
|
44
|
+
}
|