@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 CHANGED
@@ -1,3 +1,33 @@
1
- # Temporary Holding Version
1
+ # DOAJ MCP
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
@@ -0,0 +1,3 @@
1
+ export declare class DoajError extends Error {
2
+ }
3
+ export declare function search(query: string, limit?: number): Promise<string>;
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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createServer(): McpServer;
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": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }