science-ai-mcp-server 0.1.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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +43 -0
- package/dist/config.js.map +1 -0
- package/dist/http.d.ts +19 -0
- package/dist/http.js +84 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/article-writer.d.ts +15 -0
- package/dist/tools/article-writer.js +156 -0
- package/dist/tools/article-writer.js.map +1 -0
- package/dist/tools/duplicate-publication.d.ts +9 -0
- package/dist/tools/duplicate-publication.js +62 -0
- package/dist/tools/duplicate-publication.js.map +1 -0
- package/dist/tools/hakem-review.d.ts +9 -0
- package/dist/tools/hakem-review.js +76 -0
- package/dist/tools/hakem-review.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +22 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/journal-recommender.d.ts +9 -0
- package/dist/tools/journal-recommender.js +115 -0
- package/dist/tools/journal-recommender.js.map +1 -0
- package/dist/tools/pre-check.d.ts +13 -0
- package/dist/tools/pre-check.js +84 -0
- package/dist/tools/pre-check.js.map +1 -0
- package/dist/tools/research-gaps.d.ts +9 -0
- package/dist/tools/research-gaps.js +82 -0
- package/dist/tools/research-gaps.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Science AI Journal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# science-ai-mcp-server
|
|
2
|
+
|
|
3
|
+
[Model Context Protocol](https://modelcontextprotocol.io/) server for [Science AI Journal](https://scienceaijournal.com) tools. Use Pre-Check, AI Review, Research Gaps, Journal Recommender, Duplicate Publication Checker, and the Article Writer from inside Claude Desktop, Cursor, Cline, or any MCP-enabled client — without leaving your assistant.
|
|
4
|
+
|
|
5
|
+
Full docs and screenshots: **<https://scienceaijournal.com/developers/mcp>**
|
|
6
|
+
|
|
7
|
+
## Quick start (≈ 2 minutes)
|
|
8
|
+
|
|
9
|
+
1. Generate an API key at <https://scienceaijournal.com/settings?section=api-keys> (free account required). Copy the `saij_…` token — you only see it once.
|
|
10
|
+
2. Add this block to your Claude Desktop config file:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"science-ai-journal": {
|
|
16
|
+
"command": "npx",
|
|
17
|
+
"args": ["-y", "science-ai-mcp-server"],
|
|
18
|
+
"env": {
|
|
19
|
+
"SCIENCE_AI_API_KEY": "saij_…paste your key…"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Config file locations:
|
|
27
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
28
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
29
|
+
- **Linux:** `~/.config/Claude/claude_desktop_config.json`
|
|
30
|
+
|
|
31
|
+
3. Restart Claude Desktop. The `science-ai-journal` server appears in the 🔌 tool list.
|
|
32
|
+
|
|
33
|
+
The same config works in **Cursor** (`~/.cursor/mcp.json`), **Cline** (via the VS Code extension panel), and **Continue** (anywhere that supports an `mcpServers` object).
|
|
34
|
+
|
|
35
|
+
## Tools
|
|
36
|
+
|
|
37
|
+
| Tool | What it does | Cost |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `pre_check_paper` | Tier 1-5 acceptance probability from title + abstract (local FTS5, no LLM) | Free |
|
|
40
|
+
| `check_duplicate_publication` | Cross-reference against CrossRef + arXiv + medRxiv + bioRxiv + Unpaywall + a 900k-paper library | Free |
|
|
41
|
+
| `recommend_journals` | Ranked target journals from a 1,214-venue index, with predatory-journal flags | Free |
|
|
42
|
+
| `find_research_gaps` | Synthesised gaps + most-cited + most-recent papers around your query | Credits |
|
|
43
|
+
| `hakem_review_paper` | Single-agent HAKEM peer-review pass on a prepared prompt | Credits |
|
|
44
|
+
| `start_writer_pipeline` / `get_writer_pipeline_status` | Drive the Article Writer pipeline from outside the wizard | Credits |
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
| Env var | Default | Purpose |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `SCIENCE_AI_API_KEY` | _required_ | Your `saij_…` token |
|
|
51
|
+
| `SCIENCE_AI_BASE_URL` | `https://scienceaijournal.com` | Override for self-hosted or staging environments |
|
|
52
|
+
|
|
53
|
+
## Troubleshooting
|
|
54
|
+
|
|
55
|
+
- **Tools don't appear after editing config:** fully quit the host app (not just close the window) and reopen.
|
|
56
|
+
- **401 unauthorized when calling a tool:** the key was wrong, revoked, or expired. Mint a fresh one at Settings → API Keys.
|
|
57
|
+
- **429 rate limit on `find_research_gaps`:** you hit the 10-per-hour IP quota. Wait an hour.
|
|
58
|
+
- **`npx` can't resolve the package:** ensure Node ≥ 20 and that your npm registry isn't set to a private mirror without this package yet (`npm config set registry https://registry.npmjs.org/`).
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration — read once at process start.
|
|
3
|
+
*
|
|
4
|
+
* stdio MCP servers run as long-lived subprocesses spawned by the host
|
|
5
|
+
* client (Claude Desktop, Cursor, …). Env vars are baked in at spawn
|
|
6
|
+
* time from the client's config file; we don't watch for changes.
|
|
7
|
+
*/
|
|
8
|
+
export interface ServerConfig {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
userAgent: string;
|
|
12
|
+
/** Default request timeout in ms; per-tool overrides allowed. */
|
|
13
|
+
requestTimeoutMs: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function getConfig(): ServerConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration — read once at process start.
|
|
3
|
+
*
|
|
4
|
+
* stdio MCP servers run as long-lived subprocesses spawned by the host
|
|
5
|
+
* client (Claude Desktop, Cursor, …). Env vars are baked in at spawn
|
|
6
|
+
* time from the client's config file; we don't watch for changes.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_BASE_URL = "https://scienceaijournal.com";
|
|
9
|
+
let cached = null;
|
|
10
|
+
export function getConfig() {
|
|
11
|
+
if (cached)
|
|
12
|
+
return cached;
|
|
13
|
+
const apiKey = (process.env.SCIENCE_AI_API_KEY || "").trim();
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
// Fail loudly on stdout so the MCP host surfaces a useful error to the
|
|
16
|
+
// user. Without an API key every tool call would just 401, which the
|
|
17
|
+
// host UI tends to render as a generic "tool failed".
|
|
18
|
+
throw new Error("SCIENCE_AI_API_KEY is required.\n" +
|
|
19
|
+
"Generate a key at https://scienceaijournal.com/settings?section=api-keys\n" +
|
|
20
|
+
"and add it to your MCP client config under the `env` block:\n\n" +
|
|
21
|
+
' "env": { "SCIENCE_AI_API_KEY": "saij_…" }');
|
|
22
|
+
}
|
|
23
|
+
if (!apiKey.startsWith("saij_")) {
|
|
24
|
+
throw new Error(`SCIENCE_AI_API_KEY looks malformed (expected "saij_…" prefix). Got ${apiKey.slice(0, 8)}…`);
|
|
25
|
+
}
|
|
26
|
+
const baseUrl = (process.env.SCIENCE_AI_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "") ||
|
|
27
|
+
DEFAULT_BASE_URL;
|
|
28
|
+
const requestTimeoutMs = (() => {
|
|
29
|
+
const raw = process.env.SCIENCE_AI_REQUEST_TIMEOUT_MS;
|
|
30
|
+
if (!raw)
|
|
31
|
+
return 120_000; // 2 min — covers HAKEM agent calls
|
|
32
|
+
const n = Number(raw);
|
|
33
|
+
return Number.isFinite(n) && n > 0 ? Math.min(n, 600_000) : 120_000;
|
|
34
|
+
})();
|
|
35
|
+
cached = {
|
|
36
|
+
apiKey,
|
|
37
|
+
baseUrl,
|
|
38
|
+
userAgent: `science-ai-mcp-server/0.1.0 (+${DEFAULT_BASE_URL}/developers/mcp)`,
|
|
39
|
+
requestTimeoutMs,
|
|
40
|
+
};
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,gBAAgB,GAAG,8BAA8B,CAAC;AAUxD,IAAI,MAAM,GAAwB,IAAI,CAAC;AAEvC,MAAM,UAAU,SAAS;IACvB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,uEAAuE;QACvE,qEAAqE;QACrE,sDAAsD;QACtD,MAAM,IAAI,KAAK,CACb,mCAAmC;YACjC,4EAA4E;YAC5E,iEAAiE;YACjE,6CAA6C,CAChD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,sEAAsE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAC5F,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GACX,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QACxE,gBAAgB,CAAC;IAEnB,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE;QAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC;QACtD,IAAI,CAAC,GAAG;YAAE,OAAO,OAAO,CAAC,CAAC,mCAAmC;QAC7D,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACtE,CAAC,CAAC,EAAE,CAAC;IAEL,MAAM,GAAG;QACP,MAAM;QACN,OAAO;QACP,SAAS,EAAE,iCAAiC,gBAAgB,kBAAkB;QAC9E,gBAAgB;KACjB,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP client for talking to the Science AI Journal app.
|
|
3
|
+
*
|
|
4
|
+
* Adds the Bearer auth header on every request, applies a generous-but-
|
|
5
|
+
* bounded timeout, normalises non-2xx responses into thrown Errors with
|
|
6
|
+
* useful messages, and parses JSON responses.
|
|
7
|
+
*
|
|
8
|
+
* Kept dependency-free — Node 20+ ships a global fetch + AbortController,
|
|
9
|
+
* which is everything we need.
|
|
10
|
+
*/
|
|
11
|
+
export interface RequestOpts {
|
|
12
|
+
method?: "GET" | "POST" | "DELETE";
|
|
13
|
+
path: string;
|
|
14
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
15
|
+
body?: unknown;
|
|
16
|
+
/** Override the per-process default. */
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function request<T = unknown>(opts: RequestOpts): Promise<T>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP client for talking to the Science AI Journal app.
|
|
3
|
+
*
|
|
4
|
+
* Adds the Bearer auth header on every request, applies a generous-but-
|
|
5
|
+
* bounded timeout, normalises non-2xx responses into thrown Errors with
|
|
6
|
+
* useful messages, and parses JSON responses.
|
|
7
|
+
*
|
|
8
|
+
* Kept dependency-free — Node 20+ ships a global fetch + AbortController,
|
|
9
|
+
* which is everything we need.
|
|
10
|
+
*/
|
|
11
|
+
import { getConfig } from "./config.js";
|
|
12
|
+
export async function request(opts) {
|
|
13
|
+
const cfg = getConfig();
|
|
14
|
+
const url = new URL(opts.path, cfg.baseUrl);
|
|
15
|
+
if (opts.query) {
|
|
16
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
17
|
+
if (v === undefined)
|
|
18
|
+
continue;
|
|
19
|
+
url.searchParams.set(k, String(v));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeoutMs = opts.timeoutMs ?? cfg.requestTimeoutMs;
|
|
24
|
+
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
25
|
+
let res;
|
|
26
|
+
try {
|
|
27
|
+
res = await fetch(url, {
|
|
28
|
+
method: opts.method ?? (opts.body ? "POST" : "GET"),
|
|
29
|
+
headers: {
|
|
30
|
+
"Authorization": `Bearer ${cfg.apiKey}`,
|
|
31
|
+
"Accept": "application/json",
|
|
32
|
+
"User-Agent": cfg.userAgent,
|
|
33
|
+
...(opts.body !== undefined
|
|
34
|
+
? { "Content-Type": "application/json" }
|
|
35
|
+
: {}),
|
|
36
|
+
},
|
|
37
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
38
|
+
signal: controller.signal,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
clearTimeout(t);
|
|
43
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
44
|
+
throw new Error(`Request to ${url.pathname} timed out after ${timeoutMs}ms.`);
|
|
45
|
+
}
|
|
46
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
47
|
+
throw new Error(`Network error calling ${url.pathname}: ${msg}`);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
clearTimeout(t);
|
|
51
|
+
}
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
let detail = "";
|
|
54
|
+
try {
|
|
55
|
+
detail = (await res.text()).slice(0, 500);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* ignore */
|
|
59
|
+
}
|
|
60
|
+
if (res.status === 401) {
|
|
61
|
+
throw new Error(`401 Unauthorized — your SCIENCE_AI_API_KEY is missing, revoked, or expired. ` +
|
|
62
|
+
`Generate a fresh key at ${cfg.baseUrl}/settings?section=api-keys`);
|
|
63
|
+
}
|
|
64
|
+
if (res.status === 429) {
|
|
65
|
+
const retryAfter = res.headers.get("retry-after");
|
|
66
|
+
throw new Error(`429 Rate Limit — too many requests. ${retryAfter ? `Retry after ${retryAfter}s.` : "Wait a minute and try again."}`);
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`HTTP ${res.status} from ${url.pathname}${detail ? `: ${detail}` : ""}`);
|
|
69
|
+
}
|
|
70
|
+
// Some routes return text/event-stream or plain text; we only do JSON here.
|
|
71
|
+
const ct = res.headers.get("content-type") || "";
|
|
72
|
+
if (!ct.includes("application/json")) {
|
|
73
|
+
const text = await res.text();
|
|
74
|
+
// Best-effort JSON parse; if it isn't JSON, surface as text.
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return (await res.json());
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* science-ai-mcp-server — stdio entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* Spawned as a subprocess by Claude Desktop / Cursor / Cline. Communicates
|
|
6
|
+
* over JSON-RPC on stdin/stdout (the stdio transport). One process per
|
|
7
|
+
* MCP host; configuration comes from env vars set in the host's
|
|
8
|
+
* mcp config file (see /developers/mcp on scienceaijournal.com).
|
|
9
|
+
*
|
|
10
|
+
* Failures during config load print to stderr so the host surfaces them
|
|
11
|
+
* to the user; the process exits 1 so the host knows the server is dead.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* science-ai-mcp-server — stdio entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* Spawned as a subprocess by Claude Desktop / Cursor / Cline. Communicates
|
|
6
|
+
* over JSON-RPC on stdin/stdout (the stdio transport). One process per
|
|
7
|
+
* MCP host; configuration comes from env vars set in the host's
|
|
8
|
+
* mcp config file (see /developers/mcp on scienceaijournal.com).
|
|
9
|
+
*
|
|
10
|
+
* Failures during config load print to stderr so the host surfaces them
|
|
11
|
+
* to the user; the process exits 1 so the host knows the server is dead.
|
|
12
|
+
*/
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { getConfig } from "./config.js";
|
|
16
|
+
import { registerAllTools } from "./tools/index.js";
|
|
17
|
+
async function main() {
|
|
18
|
+
// Validate config first. Throws with a helpful message if SCIENCE_AI_API_KEY
|
|
19
|
+
// is missing or malformed — the host surfaces stderr to the user.
|
|
20
|
+
try {
|
|
21
|
+
getConfig();
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
+
process.stderr.write(`[science-ai-mcp-server] startup failed:\n${msg}\n`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const server = new McpServer({
|
|
29
|
+
name: "science-ai-journal",
|
|
30
|
+
version: "0.1.0",
|
|
31
|
+
});
|
|
32
|
+
registerAllTools(server);
|
|
33
|
+
const transport = new StdioServerTransport();
|
|
34
|
+
await server.connect(transport);
|
|
35
|
+
// Stdio servers run until the host disconnects. The SDK handles SIGINT
|
|
36
|
+
// / pipe-close cleanly, so no extra signal wiring needed here.
|
|
37
|
+
}
|
|
38
|
+
main().catch((err) => {
|
|
39
|
+
const msg = err instanceof Error ? err.stack || err.message : String(err);
|
|
40
|
+
process.stderr.write(`[science-ai-mcp-server] crashed:\n${msg}\n`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,KAAK,UAAU,IAAI;IACjB,6EAA6E;IAC7E,kEAAkE;IAClE,IAAI,CAAC;QACH,SAAS,EAAE,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4CAA4C,GAAG,IAAI,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAEzB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,uEAAuE;IACvE,+DAA+D;AACjE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,GAAG,IAAI,CAAC,CAAC;IACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* start_writer_pipeline + get_writer_pipeline_status — /api/writer/pipeline
|
|
3
|
+
*
|
|
4
|
+
* The Article Writer pipeline is long-running (minutes per step), so we
|
|
5
|
+
* expose it as TWO MCP tools — the LLM-host orchestrates polling. Same
|
|
6
|
+
* route, different HTTP methods:
|
|
7
|
+
*
|
|
8
|
+
* POST /api/writer/pipeline → enqueues a job, returns jobId
|
|
9
|
+
* GET /api/writer/pipeline?sessionId=…§ion=… → latest job for the (session, section)
|
|
10
|
+
*
|
|
11
|
+
* The MCP wrapper exposes status under a different name so callers can
|
|
12
|
+
* tell start vs poll apart.
|
|
13
|
+
*/
|
|
14
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
|
+
export declare function registerArticleWriter(server: McpServer): void;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* start_writer_pipeline + get_writer_pipeline_status — /api/writer/pipeline
|
|
3
|
+
*
|
|
4
|
+
* The Article Writer pipeline is long-running (minutes per step), so we
|
|
5
|
+
* expose it as TWO MCP tools — the LLM-host orchestrates polling. Same
|
|
6
|
+
* route, different HTTP methods:
|
|
7
|
+
*
|
|
8
|
+
* POST /api/writer/pipeline → enqueues a job, returns jobId
|
|
9
|
+
* GET /api/writer/pipeline?sessionId=…§ion=… → latest job for the (session, section)
|
|
10
|
+
*
|
|
11
|
+
* The MCP wrapper exposes status under a different name so callers can
|
|
12
|
+
* tell start vs poll apart.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { request } from "../http.js";
|
|
16
|
+
const SECTIONS = [
|
|
17
|
+
"step_7_5",
|
|
18
|
+
"introduction",
|
|
19
|
+
"methodology",
|
|
20
|
+
"results",
|
|
21
|
+
"discussion",
|
|
22
|
+
"references",
|
|
23
|
+
"abstract",
|
|
24
|
+
];
|
|
25
|
+
const startShape = {
|
|
26
|
+
sessionId: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(200)
|
|
30
|
+
.describe("WriterSession id you already own. Create one via the web UI at /writer first; the MCP server does not own the session-creation flow today."),
|
|
31
|
+
section: z
|
|
32
|
+
.enum(SECTIONS)
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Which section to run. Default 'step_7_5' (the code-search → adapt → verify → simulate chain)."),
|
|
35
|
+
language: z
|
|
36
|
+
.string()
|
|
37
|
+
.max(40)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Simulation language override (python | r | matlab | ...)."),
|
|
40
|
+
force: z
|
|
41
|
+
.boolean()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("If true, abort any existing queued/running job for this (sessionId, section) and enqueue fresh."),
|
|
44
|
+
};
|
|
45
|
+
const statusShape = {
|
|
46
|
+
sessionId: z.string().min(1).max(200),
|
|
47
|
+
section: z.enum(SECTIONS).optional(),
|
|
48
|
+
};
|
|
49
|
+
export function registerArticleWriter(server) {
|
|
50
|
+
server.registerTool("start_writer_pipeline", {
|
|
51
|
+
title: "Start Article Writer Pipeline",
|
|
52
|
+
description: "Enqueue a writer-pipeline job for an existing WriterSession. " +
|
|
53
|
+
"Returns immediately with a jobId; the orchestrator-worker claims " +
|
|
54
|
+
"it within ~5 seconds. Default section is step_7_5 — the auto-chain " +
|
|
55
|
+
"that replaces the three buttons in Step 7.5. Use " +
|
|
56
|
+
"`get_writer_pipeline_status` to poll until status is 'done', 'failed', " +
|
|
57
|
+
"or 'aborted'. Uses Science AI Journal credits.",
|
|
58
|
+
inputSchema: startShape,
|
|
59
|
+
}, async (args) => {
|
|
60
|
+
const body = {
|
|
61
|
+
sessionId: args.sessionId,
|
|
62
|
+
section: args.section ?? "step_7_5",
|
|
63
|
+
force: args.force ?? false,
|
|
64
|
+
};
|
|
65
|
+
if (args.language) {
|
|
66
|
+
body.config = { language: args.language };
|
|
67
|
+
}
|
|
68
|
+
const r = await request({
|
|
69
|
+
method: "POST",
|
|
70
|
+
path: "/api/writer/pipeline",
|
|
71
|
+
body,
|
|
72
|
+
});
|
|
73
|
+
if (!r.ok) {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `Failed to enqueue: ${r.error ?? "(no error message)"}`,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const tail = r.alreadyQueued
|
|
85
|
+
? " (already queued; pass force=true to supersede)"
|
|
86
|
+
: "";
|
|
87
|
+
return {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: `Writer job enqueued: jobId=${r.jobId}${tail}\nPoll with get_writer_pipeline_status({ sessionId: "${args.sessionId}", section: "${args.section ?? "step_7_5"}" }).`,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
server.registerTool("get_writer_pipeline_status", {
|
|
97
|
+
title: "Get Article Writer Pipeline Status",
|
|
98
|
+
description: "Return the latest writer-pipeline job for a (sessionId, section). " +
|
|
99
|
+
"Use after `start_writer_pipeline` and poll every 5-10 seconds. " +
|
|
100
|
+
"Terminal statuses: 'done', 'failed', 'aborted'.",
|
|
101
|
+
inputSchema: statusShape,
|
|
102
|
+
}, async (args) => {
|
|
103
|
+
const r = await request({
|
|
104
|
+
method: "GET",
|
|
105
|
+
path: "/api/writer/pipeline",
|
|
106
|
+
query: {
|
|
107
|
+
sessionId: args.sessionId,
|
|
108
|
+
section: args.section ?? "step_7_5",
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
if (!r.ok) {
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: `Status fetch failed: ${r.error ?? "(no error message)"}`,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
isError: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (!r.job) {
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: "No job found for this (sessionId, section). Did you call start_writer_pipeline first?",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const j = r.job;
|
|
133
|
+
const lines = [];
|
|
134
|
+
lines.push(`Writer pipeline job ${j.id}`);
|
|
135
|
+
lines.push(` Section: ${j.section}`);
|
|
136
|
+
lines.push(` Status: ${j.status}${j.stage ? ` (stage: ${j.stage})` : ""}`);
|
|
137
|
+
if (typeof j.progressPct === "number") {
|
|
138
|
+
const stageProgress = j.stageIdx != null && j.totalStages != null
|
|
139
|
+
? ` — stage ${j.stageIdx}/${j.totalStages}`
|
|
140
|
+
: "";
|
|
141
|
+
lines.push(` Progress: ${j.progressPct}%${stageProgress}`);
|
|
142
|
+
}
|
|
143
|
+
if (j.progressNote)
|
|
144
|
+
lines.push(` Note: ${j.progressNote}`);
|
|
145
|
+
if (j.hallucination)
|
|
146
|
+
lines.push(` Hallucination flag: ${j.hallucination}`);
|
|
147
|
+
if (j.error)
|
|
148
|
+
lines.push(` Error: ${j.error}`);
|
|
149
|
+
if (j.queuedAt)
|
|
150
|
+
lines.push(` Queued: ${j.queuedAt}`);
|
|
151
|
+
if (j.finishedAt)
|
|
152
|
+
lines.push(` Finished: ${j.finishedAt}`);
|
|
153
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=article-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article-writer.js","sourceRoot":"","sources":["../../src/tools/article-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check_duplicate_publication — POST /api/prior-publication
|
|
3
|
+
*
|
|
4
|
+
* Cross-references title + abstract (and optionally DOI) against
|
|
5
|
+
* CrossRef + arXiv + medRxiv + bioRxiv + Unpaywall + the institutional
|
|
6
|
+
* library. Returns a status, confidence, and a human-readable message.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
export declare function registerDuplicatePublication(server: McpServer): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check_duplicate_publication — POST /api/prior-publication
|
|
3
|
+
*
|
|
4
|
+
* Cross-references title + abstract (and optionally DOI) against
|
|
5
|
+
* CrossRef + arXiv + medRxiv + bioRxiv + Unpaywall + the institutional
|
|
6
|
+
* library. Returns a status, confidence, and a human-readable message.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { request } from "../http.js";
|
|
10
|
+
const inputShape = {
|
|
11
|
+
title: z
|
|
12
|
+
.string()
|
|
13
|
+
.min(1)
|
|
14
|
+
.max(600)
|
|
15
|
+
.describe("Paper title (required)."),
|
|
16
|
+
abstract: z
|
|
17
|
+
.string()
|
|
18
|
+
.max(20_000)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Paper abstract — improves match accuracy."),
|
|
21
|
+
doi: z
|
|
22
|
+
.string()
|
|
23
|
+
.max(200)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Known DOI for the paper, if any."),
|
|
26
|
+
};
|
|
27
|
+
export function registerDuplicatePublication(server) {
|
|
28
|
+
server.registerTool("check_duplicate_publication", {
|
|
29
|
+
title: "Check Duplicate Publication",
|
|
30
|
+
description: "Pre-submission duplicate-publication and salami-slicing check. " +
|
|
31
|
+
"Cross-references the title + abstract against CrossRef, arXiv, " +
|
|
32
|
+
"medRxiv, bioRxiv, Unpaywall, and a 900k-paper institutional " +
|
|
33
|
+
"library in ~30 seconds. Returns a status (likely_published, " +
|
|
34
|
+
"uncertain, or not_found) with a confidence score and message. " +
|
|
35
|
+
"Free. Use before submission to catch accidental duplicates or " +
|
|
36
|
+
"to confirm a preprint hasn't been formally published yet.",
|
|
37
|
+
inputSchema: inputShape,
|
|
38
|
+
}, async (args) => {
|
|
39
|
+
const result = await request({
|
|
40
|
+
method: "POST",
|
|
41
|
+
path: "/api/prior-publication",
|
|
42
|
+
body: {
|
|
43
|
+
title: args.title,
|
|
44
|
+
abstract: args.abstract,
|
|
45
|
+
doi: args.doi,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const lines = ["Duplicate publication check"];
|
|
49
|
+
lines.push(` Status: ${result.status ?? "(unknown)"}`);
|
|
50
|
+
if (typeof result.confidence === "number") {
|
|
51
|
+
lines.push(` Confidence: ${Math.round(result.confidence * 100)}%`);
|
|
52
|
+
}
|
|
53
|
+
if (result.message) {
|
|
54
|
+
lines.push(` Detail: ${result.message}`);
|
|
55
|
+
}
|
|
56
|
+
if (result.link) {
|
|
57
|
+
lines.push(` Link: ${result.link}`);
|
|
58
|
+
}
|
|
59
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=duplicate-publication.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-publication.js","sourceRoot":"","sources":["../../src/tools/duplicate-publication.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC,MAAM,UAAU,GAAG;IACjB,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,CAAC,yBAAyB,CAAC;IACtC,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,CAAC,MAAM,CAAC;SACX,QAAQ,EAAE;SACV,QAAQ,CAAC,2CAA2C,CAAC;IACxD,GAAG,EAAE,CAAC;SACH,MAAM,EAAE;SACR,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,kCAAkC,CAAC;CAChD,CAAC;AAUF,MAAM,UAAU,4BAA4B,CAAC,MAAiB;IAC5D,MAAM,CAAC,YAAY,CACjB,6BAA6B,EAC7B;QACE,KAAK,EAAE,6BAA6B;QACpC,WAAW,EACT,iEAAiE;YACjE,iEAAiE;YACjE,8DAA8D;YAC9D,8DAA8D;YAC9D,gEAAgE;YAChE,gEAAgE;YAChE,2DAA2D;QAC7D,WAAW,EAAE,UAAU;KACxB,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,MAAM,OAAO,CAAmB;YAC7C,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,wBAAwB;YAC9B,IAAI,EAAE;gBACJ,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,GAAG,EAAE,IAAI,CAAC,GAAG;aACd;SACF,CAAC,CAAC;QAEH,MAAM,KAAK,GAAa,CAAC,6BAA6B,CAAC,CAAC;QACxD,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,MAAM,IAAI,WAAW,EAAE,CAAC,CAAC;QACxD,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC1C,KAAK,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACjE,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hakem_review_paper — POST /api/review/claude
|
|
3
|
+
*
|
|
4
|
+
* Single HAKEM-agent peer-review call. Text-only for MVP (vision /
|
|
5
|
+
* figure-image review deferred). The MCP boundary doesn't stream — we
|
|
6
|
+
* return the final normalised review JSON in one chunk.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
export declare function registerHakemReview(server: McpServer): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hakem_review_paper — POST /api/review/claude
|
|
3
|
+
*
|
|
4
|
+
* Single HAKEM-agent peer-review call. Text-only for MVP (vision /
|
|
5
|
+
* figure-image review deferred). The MCP boundary doesn't stream — we
|
|
6
|
+
* return the final normalised review JSON in one chunk.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { request } from "../http.js";
|
|
10
|
+
const inputShape = {
|
|
11
|
+
prompt: z
|
|
12
|
+
.string()
|
|
13
|
+
.min(100)
|
|
14
|
+
.max(100_000)
|
|
15
|
+
.describe("Prepared agent prompt — usually the manuscript text plus any agent-specific framing. " +
|
|
16
|
+
"The server wraps it in <manuscript> tags to defuse injection, prepends RAG examples " +
|
|
17
|
+
"if agentType is supplied, and runs the chosen Claude model."),
|
|
18
|
+
agentType: z
|
|
19
|
+
.enum(["methodology", "language", "figures", "plagiarism", "literature"])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Which HAKEM specialist agent to run. Drives the calibration RAG corpus " +
|
|
22
|
+
"and the system prompt; defaults to a generic reviewer when omitted."),
|
|
23
|
+
};
|
|
24
|
+
export function registerHakemReview(server) {
|
|
25
|
+
server.registerTool("hakem_review_paper", {
|
|
26
|
+
title: "HAKEM Single-Agent Review",
|
|
27
|
+
description: "Run one of the HAKEM specialist agents (methodology, language, figures, " +
|
|
28
|
+
"plagiarism, or literature) on a prepared agent prompt and return the " +
|
|
29
|
+
"structured editorial decision: score (1-10), verdict (Accept / Minor " +
|
|
30
|
+
"Revision / Major Revision / Reject), summary, strengths, concerns, " +
|
|
31
|
+
"detailed multi-section review, confidence band, and questions for the " +
|
|
32
|
+
"authors. Uses Science AI Journal credits. For the full 5-agent + " +
|
|
33
|
+
"synthesis flow, prefer the web UI at scienceaijournal.com/ai-review.",
|
|
34
|
+
inputSchema: inputShape,
|
|
35
|
+
}, async (args) => {
|
|
36
|
+
const result = await request({
|
|
37
|
+
method: "POST",
|
|
38
|
+
path: "/api/review/claude",
|
|
39
|
+
body: { prompt: args.prompt, agentType: args.agentType },
|
|
40
|
+
// 90s server-side max + 30s margin for slow upstream proxies.
|
|
41
|
+
timeoutMs: 120_000,
|
|
42
|
+
});
|
|
43
|
+
const lines = [];
|
|
44
|
+
lines.push(`HAKEM review (${args.agentType ?? "generic"})`);
|
|
45
|
+
lines.push(` Score: ${result.score ?? "?"}/10 Verdict: ${result.verdict ?? "?"} Confidence: ${result.confidence ?? "?"}`);
|
|
46
|
+
if (result.summary) {
|
|
47
|
+
lines.push("");
|
|
48
|
+
lines.push(`Summary:\n ${result.summary}`);
|
|
49
|
+
}
|
|
50
|
+
if (result.strengths?.length) {
|
|
51
|
+
lines.push("");
|
|
52
|
+
lines.push("Strengths:");
|
|
53
|
+
for (const s of result.strengths)
|
|
54
|
+
lines.push(` + ${s}`);
|
|
55
|
+
}
|
|
56
|
+
if (result.concerns?.length) {
|
|
57
|
+
lines.push("");
|
|
58
|
+
lines.push("Concerns:");
|
|
59
|
+
for (const c of result.concerns)
|
|
60
|
+
lines.push(` - ${c}`);
|
|
61
|
+
}
|
|
62
|
+
if (result.detailed_review) {
|
|
63
|
+
lines.push("");
|
|
64
|
+
lines.push("Detailed review:");
|
|
65
|
+
lines.push(result.detailed_review);
|
|
66
|
+
}
|
|
67
|
+
if (result.questions_for_authors?.length) {
|
|
68
|
+
lines.push("");
|
|
69
|
+
lines.push("Questions for authors:");
|
|
70
|
+
for (const q of result.questions_for_authors)
|
|
71
|
+
lines.push(` ? ${q}`);
|
|
72
|
+
}
|
|
73
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=hakem-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hakem-review.js","sourceRoot":"","sources":["../../src/tools/hakem-review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel: register every tool against an McpServer.
|
|
3
|
+
*
|
|
4
|
+
* Adding a tool? Drop a new file alongside, export a `registerXxx(server)`
|
|
5
|
+
* function, and import + call it here. Keep the call list alphabetised by
|
|
6
|
+
* tool slug so it's easy to scan against the /developers/mcp catalogue.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
export declare function registerAllTools(server: McpServer): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel: register every tool against an McpServer.
|
|
3
|
+
*
|
|
4
|
+
* Adding a tool? Drop a new file alongside, export a `registerXxx(server)`
|
|
5
|
+
* function, and import + call it here. Keep the call list alphabetised by
|
|
6
|
+
* tool slug so it's easy to scan against the /developers/mcp catalogue.
|
|
7
|
+
*/
|
|
8
|
+
import { registerArticleWriter } from "./article-writer.js";
|
|
9
|
+
import { registerDuplicatePublication } from "./duplicate-publication.js";
|
|
10
|
+
import { registerHakemReview } from "./hakem-review.js";
|
|
11
|
+
import { registerJournalRecommender } from "./journal-recommender.js";
|
|
12
|
+
import { registerPreCheck } from "./pre-check.js";
|
|
13
|
+
import { registerResearchGaps } from "./research-gaps.js";
|
|
14
|
+
export function registerAllTools(server) {
|
|
15
|
+
registerArticleWriter(server); // exposes start_ + get_writer_pipeline_status
|
|
16
|
+
registerDuplicatePublication(server);
|
|
17
|
+
registerHakemReview(server);
|
|
18
|
+
registerJournalRecommender(server);
|
|
19
|
+
registerPreCheck(server);
|
|
20
|
+
registerResearchGaps(server);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,8CAA8C;IAC7E,4BAA4B,CAAC,MAAM,CAAC,CAAC;IACrC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,0BAA0B,CAAC,MAAM,CAAC,CAAC;IACnC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recommend_journals — POST /api/journal-recommender
|
|
3
|
+
*
|
|
4
|
+
* Returns ranked target journals from a ~1,200-venue index with tier,
|
|
5
|
+
* publisher, OA status, impact factor proxy, predatory flag, and
|
|
6
|
+
* example similar papers per recommendation.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
export declare function registerJournalRecommender(server: McpServer): void;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recommend_journals — POST /api/journal-recommender
|
|
3
|
+
*
|
|
4
|
+
* Returns ranked target journals from a ~1,200-venue index with tier,
|
|
5
|
+
* publisher, OA status, impact factor proxy, predatory flag, and
|
|
6
|
+
* example similar papers per recommendation.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { request } from "../http.js";
|
|
10
|
+
const filtersShape = z
|
|
11
|
+
.object({
|
|
12
|
+
excludePredatory: z.boolean().optional(),
|
|
13
|
+
openAccessOnly: z.boolean().optional(),
|
|
14
|
+
minTier: z.union([z.literal(1), z.literal(2), z.literal(3)]).optional(),
|
|
15
|
+
excludePreprints: z.boolean().optional(),
|
|
16
|
+
})
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Optional filter object. excludePredatory drops Beall's-archive matches; " +
|
|
19
|
+
"openAccessOnly keeps only OA journals; minTier sets a Tier 1/2/3 floor.");
|
|
20
|
+
const inputShape = {
|
|
21
|
+
title: z
|
|
22
|
+
.string()
|
|
23
|
+
.min(1)
|
|
24
|
+
.max(600)
|
|
25
|
+
.describe("Paper title (required)."),
|
|
26
|
+
abstract: z
|
|
27
|
+
.string()
|
|
28
|
+
.max(20_000)
|
|
29
|
+
.describe("Paper abstract (required, can be empty string)."),
|
|
30
|
+
fullText: z
|
|
31
|
+
.string()
|
|
32
|
+
.max(200_000)
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Optional full manuscript text for higher-signal matching."),
|
|
35
|
+
keywords: z
|
|
36
|
+
.string()
|
|
37
|
+
.max(600)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Optional author keywords; comma-separated. High-signal topic input."),
|
|
40
|
+
field: z
|
|
41
|
+
.string()
|
|
42
|
+
.max(200)
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Detected research field for a topical boost (optional)."),
|
|
45
|
+
filters: filtersShape,
|
|
46
|
+
sort: z
|
|
47
|
+
.enum(["best", "fastest", "highestIf", "mostAccepting"])
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Sort mode. "best" (default) maximises relevance; "fastest" prefers fast-decision venues; ' +
|
|
50
|
+
'"highestIf" sorts by 2-year mean citedness; "mostAccepting" prefers higher published-volume venues.'),
|
|
51
|
+
maxResults: z
|
|
52
|
+
.number()
|
|
53
|
+
.int()
|
|
54
|
+
.min(1)
|
|
55
|
+
.max(25)
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("How many recommendations to return. Default 10, cap 25."),
|
|
58
|
+
};
|
|
59
|
+
export function registerJournalRecommender(server) {
|
|
60
|
+
server.registerTool("recommend_journals", {
|
|
61
|
+
title: "Recommend Target Journals",
|
|
62
|
+
description: "Recommend ranked target journals for a paper from a ~1,200-venue " +
|
|
63
|
+
"index. Each result includes a letter grade (A-F), a match percentage, " +
|
|
64
|
+
"tier (1-3), publisher, open-access status, 2-year mean citedness, " +
|
|
65
|
+
"predatory-journal flag, and 2-3 example similar papers that landed " +
|
|
66
|
+
"at that venue. Free — runs locally via FTS5 + topic-RAG, no LLM call. " +
|
|
67
|
+
"Use when the user asks 'where should I submit this paper' or wants " +
|
|
68
|
+
"to compare target venues before deciding.",
|
|
69
|
+
inputSchema: inputShape,
|
|
70
|
+
}, async (args) => {
|
|
71
|
+
const result = await request({
|
|
72
|
+
method: "POST",
|
|
73
|
+
path: "/api/journal-recommender",
|
|
74
|
+
body: {
|
|
75
|
+
title: args.title,
|
|
76
|
+
abstract: args.abstract,
|
|
77
|
+
fullText: args.fullText,
|
|
78
|
+
keywords: args.keywords,
|
|
79
|
+
field: args.field,
|
|
80
|
+
filters: args.filters,
|
|
81
|
+
sort: args.sort,
|
|
82
|
+
maxResults: args.maxResults,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const lines = [];
|
|
86
|
+
lines.push(`Journal recommendations (${result.recommendations.length} of ${result.indexJournalCount} indexed, ${result.executionMs}ms)`);
|
|
87
|
+
if (result.ftsErrored) {
|
|
88
|
+
lines.push(" [warning] full-text-search index errored — results are degraded.");
|
|
89
|
+
}
|
|
90
|
+
lines.push("");
|
|
91
|
+
for (let i = 0; i < result.recommendations.length; i++) {
|
|
92
|
+
const r = result.recommendations[i];
|
|
93
|
+
const m = r.metadata;
|
|
94
|
+
const tier = m.tier ? `T${m.tier}` : "T?";
|
|
95
|
+
const oa = m.openAccess ? "OA" : "—";
|
|
96
|
+
const ifv = typeof m.citedness2yr === "number"
|
|
97
|
+
? `IF≈${m.citedness2yr.toFixed(1)}`
|
|
98
|
+
: "IF—";
|
|
99
|
+
const predatory = m.isPredatory ? " ⚠ predatory" : "";
|
|
100
|
+
const overlap = r.topicOverlapTerms && r.topicOverlapTerms.length > 0
|
|
101
|
+
? `\n topic overlap: ${r.topicOverlapTerms.slice(0, 5).join(", ")}`
|
|
102
|
+
: "";
|
|
103
|
+
lines.push(` ${i + 1}. ${r.canonical} — ${r.scorePct}% (${r.letterGrade}) · ${tier} · ${oa} · ${ifv} · ${m.publisher ?? "?"}${predatory}${overlap}`);
|
|
104
|
+
if (r.examplePapers && r.examplePapers.length > 0) {
|
|
105
|
+
for (const p of r.examplePapers.slice(0, 2)) {
|
|
106
|
+
const y = p.year ? ` (${p.year})` : "";
|
|
107
|
+
const doi = p.doi ? ` doi:${p.doi}` : "";
|
|
108
|
+
lines.push(` e.g. ${p.title}${y}${doi}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=journal-recommender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-recommender.js","sourceRoot":"","sources":["../../src/tools/journal-recommender.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pre_check_paper — POST /api/scorer
|
|
3
|
+
*
|
|
4
|
+
* Tier 1-5 acceptance probability from title + abstract. Local FTS5
|
|
5
|
+
* BM25 against the 33k-paper library + tier-density aggregation. Zero
|
|
6
|
+
* LLM cost, sub-second response.
|
|
7
|
+
*
|
|
8
|
+
* The route is anonymous on the server side, but we still attach the
|
|
9
|
+
* Bearer token so the activity log can attribute the call to the
|
|
10
|
+
* user's account (and so future per-account rate limits work).
|
|
11
|
+
*/
|
|
12
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
export declare function registerPreCheck(server: McpServer): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pre_check_paper — POST /api/scorer
|
|
3
|
+
*
|
|
4
|
+
* Tier 1-5 acceptance probability from title + abstract. Local FTS5
|
|
5
|
+
* BM25 against the 33k-paper library + tier-density aggregation. Zero
|
|
6
|
+
* LLM cost, sub-second response.
|
|
7
|
+
*
|
|
8
|
+
* The route is anonymous on the server side, but we still attach the
|
|
9
|
+
* Bearer token so the activity log can attribute the call to the
|
|
10
|
+
* user's account (and so future per-account rate limits work).
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { request } from "../http.js";
|
|
14
|
+
const inputShape = {
|
|
15
|
+
title: z.string().min(1).max(600).describe("Paper title (required)."),
|
|
16
|
+
abstract: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1)
|
|
19
|
+
.max(20_000)
|
|
20
|
+
.describe("Paper abstract (required)."),
|
|
21
|
+
keywords: z
|
|
22
|
+
.string()
|
|
23
|
+
.max(600)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Optional author-supplied keywords; comma-separated."),
|
|
26
|
+
fullText: z
|
|
27
|
+
.string()
|
|
28
|
+
.max(200_000)
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Optional full manuscript text. Pass when available; results are more accurate."),
|
|
31
|
+
abstractOnly: z
|
|
32
|
+
.boolean()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Set to false to score against fullText. Default true. Ignored when fullText is absent."),
|
|
35
|
+
};
|
|
36
|
+
export function registerPreCheck(server) {
|
|
37
|
+
server.registerTool("pre_check_paper", {
|
|
38
|
+
title: "Pre-Check Paper",
|
|
39
|
+
description: "Run a free, zero-LLM-cost pre-submission scoring of an academic paper. " +
|
|
40
|
+
"Returns predicted publication tier (Tier 1, 2, or 3 probability), the " +
|
|
41
|
+
"detected research field, and a confidence band. Backed by a local 33,000-paper " +
|
|
42
|
+
"library via FTS5 BM25. Use this when the user wants a fast sanity-check on " +
|
|
43
|
+
"whether a paper is ready to submit, or which tier of journal to target. " +
|
|
44
|
+
"Sub-second response. Free.",
|
|
45
|
+
inputSchema: inputShape,
|
|
46
|
+
}, async (args) => {
|
|
47
|
+
const result = await request({
|
|
48
|
+
method: "POST",
|
|
49
|
+
path: "/api/scorer",
|
|
50
|
+
body: {
|
|
51
|
+
title: args.title,
|
|
52
|
+
abstract: args.abstract,
|
|
53
|
+
keywords: args.keywords,
|
|
54
|
+
fullText: args.fullText,
|
|
55
|
+
abstractOnly: args.abstractOnly,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: formatScorerResult(result),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function formatScorerResult(r) {
|
|
69
|
+
const field = r.field ?? "(unknown)";
|
|
70
|
+
const pct = (v) => typeof v === "number" ? `${Math.round(v * 100)}%` : "—";
|
|
71
|
+
const tier1 = pct(r.tier1);
|
|
72
|
+
const tier2 = pct(r.tier2);
|
|
73
|
+
const tier3 = pct(r.tier3);
|
|
74
|
+
const conf = typeof r.confidence === "number"
|
|
75
|
+
? `${Math.round(r.confidence * 100)}%`
|
|
76
|
+
: "—";
|
|
77
|
+
return (`Pre-Check result\n` +
|
|
78
|
+
` Detected field: ${field}\n` +
|
|
79
|
+
` Tier 1 probability: ${tier1}\n` +
|
|
80
|
+
` Tier 2 probability: ${tier2}\n` +
|
|
81
|
+
` Tier 3 probability: ${tier3}\n` +
|
|
82
|
+
` Confidence: ${conf}\n`);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=pre-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-check.js","sourceRoot":"","sources":["../../src/tools/pre-check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* find_research_gaps — POST /api/research-gaps
|
|
3
|
+
*
|
|
4
|
+
* Returns synthesised research gaps + a field overview + most-cited +
|
|
5
|
+
* most-recent papers around a query. Auth required (consumes credits;
|
|
6
|
+
* rate-limited to 10/hr per IP).
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
export declare function registerResearchGaps(server: McpServer): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* find_research_gaps — POST /api/research-gaps
|
|
3
|
+
*
|
|
4
|
+
* Returns synthesised research gaps + a field overview + most-cited +
|
|
5
|
+
* most-recent papers around a query. Auth required (consumes credits;
|
|
6
|
+
* rate-limited to 10/hr per IP).
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { request } from "../http.js";
|
|
10
|
+
const inputShape = {
|
|
11
|
+
query: z
|
|
12
|
+
.string()
|
|
13
|
+
.min(10)
|
|
14
|
+
.max(2000)
|
|
15
|
+
.describe("Research query (≥ 10 chars). Free-form prose or a title + abstract joined by newline."),
|
|
16
|
+
field: z
|
|
17
|
+
.string()
|
|
18
|
+
.max(80)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Optional field bias (e.g. 'computer_science', 'biology')."),
|
|
21
|
+
departmentId: z
|
|
22
|
+
.string()
|
|
23
|
+
.max(60)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Optional departmentId to enable methodology-alignment hints (boosts gaps whose text matches your discipline's KPI / question vocabulary)."),
|
|
26
|
+
};
|
|
27
|
+
export function registerResearchGaps(server) {
|
|
28
|
+
server.registerTool("find_research_gaps", {
|
|
29
|
+
title: "Find Research Gaps",
|
|
30
|
+
description: "Surface research gaps + the most-cited and most-recent papers around " +
|
|
31
|
+
"a query. Returns cross-paper synthesis gaps first (LLM-derived, grounded " +
|
|
32
|
+
"in ≥ 2 papers), then catalogue-level single-paper gaps. Plus a field " +
|
|
33
|
+
"overview and ≤ 50 top-cited and ≤ 50 most-recent papers. Uses " +
|
|
34
|
+
"Science AI Journal credits; rate-limited to 10 requests/hour per IP. " +
|
|
35
|
+
"Use for early-stage research discovery, literature gap identification, " +
|
|
36
|
+
"and proposal scoping.",
|
|
37
|
+
inputSchema: inputShape,
|
|
38
|
+
}, async (args) => {
|
|
39
|
+
const result = await request({
|
|
40
|
+
method: "POST",
|
|
41
|
+
path: "/api/research-gaps",
|
|
42
|
+
body: {
|
|
43
|
+
query: args.query,
|
|
44
|
+
field: args.field,
|
|
45
|
+
departmentId: args.departmentId,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const lines = [];
|
|
49
|
+
const fo = result.fieldOverview;
|
|
50
|
+
if (fo?.name) {
|
|
51
|
+
lines.push(`Field: ${fo.name}`);
|
|
52
|
+
if (fo.description)
|
|
53
|
+
lines.push(` ${fo.description}`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
lines.push(`## Research gaps (${result.gaps.length})`);
|
|
57
|
+
for (let i = 0; i < result.gaps.length; i++) {
|
|
58
|
+
const g = result.gaps[i];
|
|
59
|
+
const prov = g.provenance ? ` [${g.provenance}]` : "";
|
|
60
|
+
const topic = g.topic ? ` (topic: ${g.topic})` : "";
|
|
61
|
+
const paper = g.paper ? ` — anchor: ${g.paper}` : "";
|
|
62
|
+
lines.push(` ${i + 1}. ${g.text}${prov}${topic}${paper}`);
|
|
63
|
+
}
|
|
64
|
+
lines.push("");
|
|
65
|
+
lines.push(`## Most-cited (${result.mostCited.length})`);
|
|
66
|
+
for (const p of result.mostCited.slice(0, 10)) {
|
|
67
|
+
const yr = p.year ? ` (${p.year})` : "";
|
|
68
|
+
const cit = typeof p.citations === "number" ? ` · ${p.citations} cit` : "";
|
|
69
|
+
const venue = p.venue ? ` · ${p.venue}` : "";
|
|
70
|
+
lines.push(` - ${p.title}${yr}${cit}${venue}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push("");
|
|
73
|
+
lines.push(`## Most-recent (${result.mostRecent.length})`);
|
|
74
|
+
for (const p of result.mostRecent.slice(0, 10)) {
|
|
75
|
+
const yr = p.year ? ` (${p.year})` : "";
|
|
76
|
+
const venue = p.venue ? ` · ${p.venue}` : "";
|
|
77
|
+
lines.push(` - ${p.title}${yr}${venue}`);
|
|
78
|
+
}
|
|
79
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=research-gaps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research-gaps.js","sourceRoot":"","sources":["../../src/tools/research-gaps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC,MAAM,UAAU,GAAG;IACjB,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,CAAC;SACP,GAAG,CAAC,IAAI,CAAC;SACT,QAAQ,CACP,uFAAuF,CACxF;IACH,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CAAC,2DAA2D,CAAC;IACxE,YAAY,EAAE,CAAC;SACZ,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CACP,2IAA2I,CAC5I;CACJ,CAAC;AA8BF,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,uEAAuE;YACvE,2EAA2E;YAC3E,uEAAuE;YACvE,gEAAgE;YAChE,uEAAuE;YACvE,yEAAyE;YACzE,uBAAuB;QACzB,WAAW,EAAE,UAAU;KACxB,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,MAAM,OAAO,CAAe;YACzC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,oBAAoB;YAC1B,IAAI,EAAE;gBACJ,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,YAAY,EAAE,IAAI,CAAC,YAAY;aAChC;SACF,CAAC,CAAC;QAEH,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,aAAa,CAAC;QAChC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YAChC,IAAI,EAAE,CAAC,WAAW;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;YACtD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACzB,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACtD,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QACzD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;YAC9C,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACxC,MAAM,GAAG,GACP,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC;QAClD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;QAC3D,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;YAC/C,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACjE,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "science-ai-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP (Model Context Protocol) server for Science AI Journal tools — Pre-Check, AI Review, Research Gaps, Journal Recommender, Duplicate Publication Checker, Article Writer. Use them inside Claude Desktop, Cursor, Cline, or any MCP-enabled client.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"claude",
|
|
9
|
+
"claude-desktop",
|
|
10
|
+
"cursor",
|
|
11
|
+
"cline",
|
|
12
|
+
"academic",
|
|
13
|
+
"peer-review",
|
|
14
|
+
"research",
|
|
15
|
+
"science-ai-journal"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://scienceaijournal.com/developers/mcp",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/science-ai-journal/science-ai-mcp-server/issues"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Science AI Journal <support@scienceaijournal.com>",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"science-ai-mcp-server": "dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"prepublishOnly": "npm run build",
|
|
36
|
+
"start": "node dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
|
+
"zod": "^3.23.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.11.0",
|
|
47
|
+
"typescript": "^5.4.0"
|
|
48
|
+
}
|
|
49
|
+
}
|