sciverse-mcp-server 0.3.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 +262 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +26 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +30 -0
- package/dist/credentials.d.ts +13 -0
- package/dist/credentials.js +59 -0
- package/dist/generated/tools.d.ts +11 -0
- package/dist/generated/tools.js +231 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +26 -0
- package/dist/tools.d.ts +14 -0
- package/dist/tools.js +183 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# sciverse-mcp-server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/sciverse-mcp-server)
|
|
4
|
+
|
|
5
|
+
[English](#english) | [中文](#中文)
|
|
6
|
+
|
|
7
|
+
Official MCP (Model Context Protocol) server for the SciVerse open platform.
|
|
8
|
+
Exposes five academic-paper retrieval tools to any MCP-compatible coding agent
|
|
9
|
+
— Claude Code, Cursor, Codex CLI, Windsurf, Continue, and more.
|
|
10
|
+
|
|
11
|
+
> Tools: `search_papers` / `semantic_search` / `read_content` / `list_catalog` / `get_resource`
|
|
12
|
+
>
|
|
13
|
+
> 工具:`search_papers`(结构化元数据)/ `semantic_search`(语义检索)/ `read_content`(原文切片)/ `list_catalog`(字段 introspection)/ `get_resource`(论文图片二进制)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## English
|
|
18
|
+
|
|
19
|
+
### Quick start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
export SCIVERSE_API_TOKEN=sv-xxx # get one from https://sciverse.space
|
|
23
|
+
npx -y sciverse-mcp-server # stdio mode; coding agents will spawn this for you
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or skip the env var entirely by running the Python CLI once:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install sciverse && sciverse auth login
|
|
30
|
+
# saves token to ~/.sciverse/credentials.json (file mode 0600)
|
|
31
|
+
# the MCP server will pick it up automatically next time it starts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Connect from coding agents
|
|
35
|
+
|
|
36
|
+
#### Claude Code
|
|
37
|
+
|
|
38
|
+
Project-level `.mcp.json` (or run `claude mcp add sciverse npx -- -y sciverse-mcp-server`):
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"sciverse": {
|
|
44
|
+
"command": "npx",
|
|
45
|
+
"args": ["-y", "sciverse-mcp-server"],
|
|
46
|
+
"env": { "SCIVERSE_API_TOKEN": "sv-xxx" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If you ran `sciverse auth login`, the `env` block can be omitted entirely.
|
|
53
|
+
|
|
54
|
+
#### Cursor
|
|
55
|
+
|
|
56
|
+
`.cursor/mcp.json` (or Settings → MCP Servers), same shape as above.
|
|
57
|
+
|
|
58
|
+
#### Codex CLI
|
|
59
|
+
|
|
60
|
+
`~/.codex/config.toml`:
|
|
61
|
+
|
|
62
|
+
```toml
|
|
63
|
+
[mcp_servers.sciverse]
|
|
64
|
+
command = "npx"
|
|
65
|
+
args = ["-y", "sciverse-mcp-server"]
|
|
66
|
+
env = { SCIVERSE_API_TOKEN = "sv-xxx" }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Windsurf
|
|
70
|
+
|
|
71
|
+
`~/.codeium/windsurf/mcp_config.json`, same JSON shape as Claude Code.
|
|
72
|
+
|
|
73
|
+
See [`docs/integrations/`](https://github.com/opendatalab/SciVerse-agent-tools/tree/main/docs/integrations)
|
|
74
|
+
for full per-agent guides.
|
|
75
|
+
|
|
76
|
+
### Configuration
|
|
77
|
+
|
|
78
|
+
Token / endpoint resolution order: explicit `env` in MCP config →
|
|
79
|
+
`SCIVERSE_API_TOKEN` / `SCIVERSE_BASE_URL` environment → `~/.sciverse/credentials.json` →
|
|
80
|
+
default (`https://api.sciverse.space`).
|
|
81
|
+
|
|
82
|
+
| Var | Required | Default | Notes |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| `SCIVERSE_API_TOKEN` | ✓ (or via login) | — | Bearer token |
|
|
85
|
+
| `SCIVERSE_BASE_URL` | | `https://api.sciverse.space` | Must be a `*.sciverse.space` domain (anti-leak guard) |
|
|
86
|
+
|
|
87
|
+
### Exposed tools
|
|
88
|
+
|
|
89
|
+
| Tool | Purpose |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `search_papers` | Structured metadata search (authors / year / journal / subjects) |
|
|
92
|
+
| `semantic_search` | Natural-language semantic chunk retrieval (RAG) |
|
|
93
|
+
| `read_content` | Byte-range read of original paper text |
|
|
94
|
+
| `list_catalog` | Field introspection (returns all field names + enum sample values, agents should call this first to learn the schema) |
|
|
95
|
+
| `get_resource` | Fetch a paper figure / table image; the server wraps the bytes as an MCP `image` content block (base64 + mimeType) so multimodal models (e.g. Claude) can read the figure inline |
|
|
96
|
+
|
|
97
|
+
Tool descriptions + input schemas are derived from `openapi.yaml` at build time
|
|
98
|
+
(`npm run gen`), kept in sync with the SDK / ClawHub skill / OpenAI / Anthropic
|
|
99
|
+
tool exports.
|
|
100
|
+
|
|
101
|
+
### Hello-world prompt
|
|
102
|
+
|
|
103
|
+
After wiring up the MCP server, try:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
帮我找 3 篇关于 Transformer 注意力机制的论文,附原文片段引用。
|
|
107
|
+
Find 3 papers on Transformer attention mechanism with quoted excerpts.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Or let the agent learn the schema first:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
List the SciVerse fields and the values access_oa_status can take, then find
|
|
114
|
+
2024+ gold-OA Nature papers.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm install
|
|
121
|
+
npm run gen # derive src/generated/tools.ts from ../../openapi.yaml
|
|
122
|
+
npm run build # tsc → dist/
|
|
123
|
+
npm test # vitest (27 tests)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If `npm install` is slow / blocked, switch registry:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npm install --registry https://registry.npmmirror.com
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Links
|
|
133
|
+
|
|
134
|
+
- Source repo: <https://github.com/opendatalab/SciVerse-agent-tools>
|
|
135
|
+
- Python SDK + CLI: <https://pypi.org/project/sciverse>
|
|
136
|
+
- TypeScript SDK: <https://www.npmjs.com/package/sciverse>
|
|
137
|
+
- License: Apache-2.0
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 中文
|
|
142
|
+
|
|
143
|
+
SciVerse 开放平台官方 MCP (Model Context Protocol) server。把 5 个学术文献检索
|
|
144
|
+
工具暴露给任何 MCP 兼容的 coding agent —— Claude Code、Cursor、Codex CLI、
|
|
145
|
+
Windsurf、Continue 等。
|
|
146
|
+
|
|
147
|
+
### 快速开始
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
export SCIVERSE_API_TOKEN=sv-xxx # 从 https://sciverse.space 控制台申请
|
|
151
|
+
npx -y sciverse-mcp-server # stdio 启动;coding agent 通常会代你 spawn
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
也可以跑一次 Python CLI 把 token 持久化,之后 env 都不用配:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pip install sciverse && sciverse auth login
|
|
158
|
+
# 保存到 ~/.sciverse/credentials.json(文件权限 0600)
|
|
159
|
+
# MCP server 启动时自动 fallback 读取
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 在 coding agent 里接入
|
|
163
|
+
|
|
164
|
+
#### Claude Code
|
|
165
|
+
|
|
166
|
+
项目级 `.mcp.json`(也可以用 `claude mcp add sciverse npx -- -y sciverse-mcp-server`):
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"mcpServers": {
|
|
171
|
+
"sciverse": {
|
|
172
|
+
"command": "npx",
|
|
173
|
+
"args": ["-y", "sciverse-mcp-server"],
|
|
174
|
+
"env": { "SCIVERSE_API_TOKEN": "sv-xxx" }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
跑过 `sciverse auth login` 后 `env` 整段可省略。
|
|
181
|
+
|
|
182
|
+
#### Cursor
|
|
183
|
+
|
|
184
|
+
`.cursor/mcp.json` 或 Settings → MCP Servers,格式同上。
|
|
185
|
+
|
|
186
|
+
#### Codex CLI
|
|
187
|
+
|
|
188
|
+
`~/.codex/config.toml`:
|
|
189
|
+
|
|
190
|
+
```toml
|
|
191
|
+
[mcp_servers.sciverse]
|
|
192
|
+
command = "npx"
|
|
193
|
+
args = ["-y", "sciverse-mcp-server"]
|
|
194
|
+
env = { SCIVERSE_API_TOKEN = "sv-xxx" }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### Windsurf
|
|
198
|
+
|
|
199
|
+
`~/.codeium/windsurf/mcp_config.json`,JSON 格式同 Claude Code。
|
|
200
|
+
|
|
201
|
+
完整接入指南见 [`docs/integrations/`](https://github.com/opendatalab/SciVerse-agent-tools/tree/main/docs/integrations)。
|
|
202
|
+
|
|
203
|
+
### 配置
|
|
204
|
+
|
|
205
|
+
Token / endpoint 解析顺序:MCP 配置里显式 `env` → `SCIVERSE_API_TOKEN` /
|
|
206
|
+
`SCIVERSE_BASE_URL` 环境变量 → `~/.sciverse/credentials.json` →
|
|
207
|
+
默认值(`https://api.sciverse.space`)。
|
|
208
|
+
|
|
209
|
+
| 变量 | 必填 | 默认 | 说明 |
|
|
210
|
+
|---|---|---|---|
|
|
211
|
+
| `SCIVERSE_API_TOKEN` | ✓(或通过 login) | — | Bearer Token |
|
|
212
|
+
| `SCIVERSE_BASE_URL` | | `https://api.sciverse.space` | 必须是 `*.sciverse.space` 子域名(防 token 泄漏) |
|
|
213
|
+
|
|
214
|
+
### 暴露的 tool
|
|
215
|
+
|
|
216
|
+
| 名称 | 用途 |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `search_papers` | 结构化元数据检索(作者 / 年份 / 期刊 / 学科) |
|
|
219
|
+
| `semantic_search` | 自然语言语义检索(RAG 用) |
|
|
220
|
+
| `read_content` | 按字节区间读文献原文 |
|
|
221
|
+
| `list_catalog` | 字段 introspection(返回所有字段名 + 枚举值样本,agent 接入时建议先调一次学 schema) |
|
|
222
|
+
| `get_resource` | 取文献附属图片字节流;MCP server 包装为 `image` content block + base64 + mimeType,多模态模型(如 Claude)可直接读图 |
|
|
223
|
+
|
|
224
|
+
Tool 描述和 input schema 均从 `openapi.yaml` 派生(构建期 `npm run gen`),与
|
|
225
|
+
Python/TS SDK、ClawHub skill、OpenAI / Anthropic tool 保持一致。
|
|
226
|
+
|
|
227
|
+
### Hello-world prompt
|
|
228
|
+
|
|
229
|
+
接入完成后试:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
帮我找 3 篇关于 Transformer 注意力机制的论文,附原文片段引用。
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
或让 agent 先学 schema 再发查询:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
列出 SciVerse 有哪些字段、access_oa_status 取值,然后找 2024 年以来 gold OA
|
|
239
|
+
状态的 Nature 期刊论文。
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 开发
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
npm install
|
|
246
|
+
npm run gen # 从 ../../openapi.yaml 派生 src/generated/tools.ts
|
|
247
|
+
npm run build # tsc → dist/
|
|
248
|
+
npm test # vitest(27 测试)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
如 `npm install` 因网络源问题失败,换源:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
npm install --registry https://registry.npmmirror.com
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 链接
|
|
258
|
+
|
|
259
|
+
- 源码仓库:<https://github.com/opendatalab/SciVerse-agent-tools>
|
|
260
|
+
- Python SDK + CLI:<https://pypi.org/project/sciverse>
|
|
261
|
+
- TypeScript SDK:<https://www.npmjs.com/package/sciverse>
|
|
262
|
+
- 协议:Apache-2.0
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 入口:从 stdio 启动 MCP server。
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { ConfigError, loadConfig } from "./config.js";
|
|
5
|
+
import { createServer } from "./server.js";
|
|
6
|
+
async function main() {
|
|
7
|
+
let config;
|
|
8
|
+
try {
|
|
9
|
+
config = loadConfig();
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
if (err instanceof ConfigError) {
|
|
13
|
+
process.stderr.write(`[sciverse-mcp] ${err.message}\n`);
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
const server = createServer(config);
|
|
19
|
+
const transport = new StdioServerTransport();
|
|
20
|
+
await server.connect(transport);
|
|
21
|
+
// 进程长驻;stdio 关闭时 transport 会触发退出。
|
|
22
|
+
}
|
|
23
|
+
main().catch((err) => {
|
|
24
|
+
process.stderr.write(`[sciverse-mcp] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Token / endpoint 解析顺序:env → ~/.sciverse/credentials.json → 默认值。
|
|
2
|
+
// BASE_URL 仅允许 *.sciverse.space 与 sciverse.space,防止 token 被泄漏到
|
|
3
|
+
// 任意域名(与 ClawHub skill 的 _common.mjs 一致)。
|
|
4
|
+
import { resolveEndpoint, resolveToken } from "./credentials.js";
|
|
5
|
+
export class ConfigError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ConfigError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function loadConfig(env = process.env) {
|
|
12
|
+
const token = resolveToken(undefined, env);
|
|
13
|
+
if (!token) {
|
|
14
|
+
throw new ConfigError("未找到 SciVerse API Token。请设 SCIVERSE_API_TOKEN 环境变量,或运行 " +
|
|
15
|
+
"`pip install sciverse && sciverse auth login` 保存凭据到 ~/.sciverse/credentials.json。");
|
|
16
|
+
}
|
|
17
|
+
const baseUrl = resolveEndpoint(undefined, env).replace(/\/$/, "");
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = new URL(baseUrl);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new ConfigError(`SCIVERSE_BASE_URL is not a valid URL: ${baseUrl}`);
|
|
24
|
+
}
|
|
25
|
+
const host = parsed.hostname;
|
|
26
|
+
if (host !== "sciverse.space" && !host.endsWith(".sciverse.space")) {
|
|
27
|
+
throw new ConfigError(`SCIVERSE_BASE_URL must point to a *.sciverse.space domain (got: ${host}). This guards against accidental token leakage.`);
|
|
28
|
+
}
|
|
29
|
+
return { token, baseUrl };
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface StoredCredentials {
|
|
2
|
+
token?: string;
|
|
3
|
+
endpoint?: string;
|
|
4
|
+
saved_at?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULT_ENDPOINT = "https://api.sciverse.space";
|
|
7
|
+
export declare function credentialsPath(): string;
|
|
8
|
+
/** 读凭据文件。文件不存在 / 解析失败 / 不是 object 时返回 null(不抛错)。 */
|
|
9
|
+
export declare function loadStoredCredentials(): StoredCredentials | null;
|
|
10
|
+
/** [显式 → env → 凭据文件] 顺序解析 token。都没有返回 null。 */
|
|
11
|
+
export declare function resolveToken(explicit?: string, env?: NodeJS.ProcessEnv): string | null;
|
|
12
|
+
/** [显式 → env → 凭据文件 → 默认值] 顺序解析 endpoint。 */
|
|
13
|
+
export declare function resolveEndpoint(explicit?: string, env?: NodeJS.ProcessEnv): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// 共享凭据文件读取。
|
|
2
|
+
//
|
|
3
|
+
// 与 Python SDK `sciverse.credentials` 同源契约:
|
|
4
|
+
// - 路径:`~/.sciverse/credentials.json`
|
|
5
|
+
// - 文件内容:{ token: string; endpoint?: string; saved_at?: string }
|
|
6
|
+
// - 解析顺序:[显式参数 → 环境变量 → 凭据文件 → 默认值]
|
|
7
|
+
//
|
|
8
|
+
// 写入由 Python CLI `sciverse auth login` 负责;MCP server / TS SDK
|
|
9
|
+
// 只读不写(让"装一次 Python CLI 后所有客户端形态都免传 token"链路成立)。
|
|
10
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
export const DEFAULT_ENDPOINT = "https://api.sciverse.space";
|
|
14
|
+
/** 返回用户 home 目录。优先读环境变量 HOME/USERPROFILE 以便测试 override。 */
|
|
15
|
+
function getHomeDir() {
|
|
16
|
+
return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
17
|
+
}
|
|
18
|
+
export function credentialsPath() {
|
|
19
|
+
return join(getHomeDir(), ".sciverse", "credentials.json");
|
|
20
|
+
}
|
|
21
|
+
/** 读凭据文件。文件不存在 / 解析失败 / 不是 object 时返回 null(不抛错)。 */
|
|
22
|
+
export function loadStoredCredentials() {
|
|
23
|
+
const path = credentialsPath();
|
|
24
|
+
if (!existsSync(path))
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
const raw = readFileSync(path, "utf8");
|
|
28
|
+
const data = JSON.parse(raw);
|
|
29
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** [显式 → env → 凭据文件] 顺序解析 token。都没有返回 null。 */
|
|
39
|
+
export function resolveToken(explicit, env = process.env) {
|
|
40
|
+
if (explicit)
|
|
41
|
+
return explicit;
|
|
42
|
+
if (env.SCIVERSE_API_TOKEN)
|
|
43
|
+
return env.SCIVERSE_API_TOKEN;
|
|
44
|
+
const creds = loadStoredCredentials();
|
|
45
|
+
if (creds?.token)
|
|
46
|
+
return creds.token;
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/** [显式 → env → 凭据文件 → 默认值] 顺序解析 endpoint。 */
|
|
50
|
+
export function resolveEndpoint(explicit, env = process.env) {
|
|
51
|
+
if (explicit)
|
|
52
|
+
return explicit;
|
|
53
|
+
if (env.SCIVERSE_BASE_URL)
|
|
54
|
+
return env.SCIVERSE_BASE_URL;
|
|
55
|
+
const creds = loadStoredCredentials();
|
|
56
|
+
if (creds?.endpoint)
|
|
57
|
+
return creds.endpoint;
|
|
58
|
+
return DEFAULT_ENDPOINT;
|
|
59
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const TOOLS_VERSION = "0.3.0";
|
|
2
|
+
export interface ToolDef {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export declare const TOOLS: ToolDef[];
|
|
8
|
+
export declare const ENDPOINTS: Record<string, {
|
|
9
|
+
method: "GET" | "POST";
|
|
10
|
+
path: string;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Auto-generated by scripts/gen-tools.mjs. Do not edit.
|
|
2
|
+
// Source of truth: agent-tools/openapi.yaml
|
|
3
|
+
export const TOOLS_VERSION = "0.3.0";
|
|
4
|
+
export const TOOLS = [
|
|
5
|
+
{
|
|
6
|
+
"name": "search_papers",
|
|
7
|
+
"description": "Search academic papers by structured filters (title, authors, journal,\nyear, subjects, etc.).\nUse when: \"find Hinton's papers from 2020-2023\", \"Nature papers on\nCRISPR\".\nNot for: natural-language Q&A retrieval (use semantic_search) or\nfull-text snippets (use read_content).\nReturns: list of papers; each entry has doc_id, title, author, abstract,\npublication_venue_name, publication_published_year.",
|
|
8
|
+
"inputSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"query": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "BM25 全文关键词,匹配标题/摘要/期刊名/关键词字段。留空则纯靠结构化过滤。"
|
|
14
|
+
},
|
|
15
|
+
"title_contains": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "标题中必须包含的词(仅匹配 title 字段)。"
|
|
18
|
+
},
|
|
19
|
+
"abstract_contains": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "摘要中必须包含的词(仅匹配 abstract 字段)。"
|
|
22
|
+
},
|
|
23
|
+
"authors": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"description": "作者名(任一命中即可)。SDK 内部映射到后端 `author` 字段(FILTER_OP_IN)。"
|
|
29
|
+
},
|
|
30
|
+
"year_from": {
|
|
31
|
+
"type": "integer",
|
|
32
|
+
"description": "起始发表年(含)。"
|
|
33
|
+
},
|
|
34
|
+
"year_to": {
|
|
35
|
+
"type": "integer",
|
|
36
|
+
"description": "结束发表年(含)。"
|
|
37
|
+
},
|
|
38
|
+
"journals": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
"description": "期刊名(任一命中即可)。SDK 内部映射到后端 `publication_venue_name` 字段(FILTER_OP_IN)。"
|
|
44
|
+
},
|
|
45
|
+
"subjects": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
"description": "学科分类,如 \"computer science\"、\"biology\"。"
|
|
51
|
+
},
|
|
52
|
+
"filters_advanced": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"description": "高级过滤逃生舱(仅当上述字段不够用时使用)。",
|
|
55
|
+
"items": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"required": [
|
|
58
|
+
"field",
|
|
59
|
+
"value"
|
|
60
|
+
],
|
|
61
|
+
"properties": {
|
|
62
|
+
"field": {
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
"operator": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"enum": [
|
|
68
|
+
"FILTER_OP_EQ",
|
|
69
|
+
"FILTER_OP_NE",
|
|
70
|
+
"FILTER_OP_GT",
|
|
71
|
+
"FILTER_OP_GTE",
|
|
72
|
+
"FILTER_OP_LT",
|
|
73
|
+
"FILTER_OP_LTE",
|
|
74
|
+
"FILTER_OP_IN",
|
|
75
|
+
"FILTER_OP_NIN",
|
|
76
|
+
"FILTER_OP_CONTAINS"
|
|
77
|
+
],
|
|
78
|
+
"default": "FILTER_OP_EQ"
|
|
79
|
+
},
|
|
80
|
+
"value": {}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"sort_by_year": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"enum": [
|
|
87
|
+
"desc",
|
|
88
|
+
"asc",
|
|
89
|
+
"none"
|
|
90
|
+
],
|
|
91
|
+
"default": "desc"
|
|
92
|
+
},
|
|
93
|
+
"page": {
|
|
94
|
+
"type": "integer",
|
|
95
|
+
"default": 1,
|
|
96
|
+
"minimum": 1
|
|
97
|
+
},
|
|
98
|
+
"page_size": {
|
|
99
|
+
"type": "integer",
|
|
100
|
+
"default": 10,
|
|
101
|
+
"minimum": 1,
|
|
102
|
+
"maximum": 50
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "semantic_search",
|
|
109
|
+
"description": "Natural-language semantic search returning relevant paper chunks for\nRAG-style answering.\nUse when: \"How does Transformer attention work?\", \"What are recent\nmethods for protein structure prediction?\".\nNot for: precise field filtering (use search_papers) or fetching full\noriginal text (use read_content).\nReturns: list of chunks; each entry has chunk_id, doc_id, abstract,\nchunk, score, title, offset.\nTypical chain: semantic_search → pick chunk → read_content(doc_id,\noffset).",
|
|
110
|
+
"inputSchema": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"required": [
|
|
113
|
+
"query"
|
|
114
|
+
],
|
|
115
|
+
"properties": {
|
|
116
|
+
"query": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"minLength": 1,
|
|
119
|
+
"maxLength": 4096,
|
|
120
|
+
"description": "自然语言查询,1-200 字最佳。"
|
|
121
|
+
},
|
|
122
|
+
"top_k": {
|
|
123
|
+
"type": "integer",
|
|
124
|
+
"default": 10,
|
|
125
|
+
"minimum": 1,
|
|
126
|
+
"maximum": 30
|
|
127
|
+
},
|
|
128
|
+
"source_types": {
|
|
129
|
+
"type": "array",
|
|
130
|
+
"items": {
|
|
131
|
+
"type": "string",
|
|
132
|
+
"enum": [
|
|
133
|
+
"web",
|
|
134
|
+
"pdf"
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"mode": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"enum": [
|
|
141
|
+
"fast",
|
|
142
|
+
"balanced",
|
|
143
|
+
"quality"
|
|
144
|
+
],
|
|
145
|
+
"default": "balanced",
|
|
146
|
+
"description": "fast = 仅关键词召回 (~200ms);balanced = 混合检索 (~600ms);quality = LLM 改写 + 混合 (~2-4s)。\n"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"name": "list_catalog",
|
|
153
|
+
"description": "Returns the schema catalog for search_papers: every field name, type,\nwhether it's filterable / sortable, default-return status, human\ndescription, and applicable FilterOperators.\nUse when: \"Which field do I filter by DOI?\", \"What values can\naccess_oa_status take?\", \"What's the right enum for metadata_type?\".\nNot for: actually searching papers (use search_papers / semantic_search).\nTypical pattern: call once when first encountering SciVerse or facing\nan ambiguous field need, then construct precise search_papers filters\nfrom the returned schema.\nPass include_sample_values=true to also fetch top-20 values for\nenum-like fields (OpenSearch terms aggregation, 24h cached).",
|
|
154
|
+
"inputSchema": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"properties": {
|
|
157
|
+
"include_sample_values": {
|
|
158
|
+
"type": "boolean",
|
|
159
|
+
"default": false,
|
|
160
|
+
"description": "是否拉取 enum-like 字段的取值样本。false 仅返回静态 schema(毫秒级);true 触发 OpenSearch terms agg(首次几百毫秒,之后 24h 走缓存)。"
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"required": []
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"name": "read_content",
|
|
168
|
+
"description": "Read a UTF-8 byte range of a paper's original text. Typically used with\na doc_id/offset returned by semantic_search to expand context (read\nmore bytes before or after a chunk).\nReturns: text fragment, bytes_returned, next_offset, more (boolean).",
|
|
169
|
+
"inputSchema": {
|
|
170
|
+
"type": "object",
|
|
171
|
+
"properties": {
|
|
172
|
+
"doc_id": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"description": "文献 ID(来自 search_papers / semantic_search)。"
|
|
175
|
+
},
|
|
176
|
+
"offset": {
|
|
177
|
+
"type": "integer",
|
|
178
|
+
"format": "int64",
|
|
179
|
+
"default": 0
|
|
180
|
+
},
|
|
181
|
+
"limit": {
|
|
182
|
+
"type": "integer",
|
|
183
|
+
"format": "int64",
|
|
184
|
+
"default": 4096,
|
|
185
|
+
"maximum": 16384
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"required": [
|
|
189
|
+
"doc_id"
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"name": "get_resource",
|
|
195
|
+
"description": "Returns the binary bytes of a paper figure / table image referenced\ninside read_content's Markdown via `` placeholders.\nUse when the user asks to see / display / describe a figure and\nread_content output contains an image reference.\nInput file_name comes from the Markdown URL part (relative path,\nno `\\\\` or `..`).\nReturns: raw image stream + image/* Content-Type. The SDK / MCP\nserver wraps the bytes as base64 + mimeType so Claude (multimodal)\ncan read the image directly.",
|
|
196
|
+
"inputSchema": {
|
|
197
|
+
"type": "object",
|
|
198
|
+
"properties": {
|
|
199
|
+
"file_name": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"description": "图片相对路径,来自 read_content Markdown 中的 `` 占位。禁止 `\\\\` 与 `..`,不能以 `/` 开头。"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"required": [
|
|
205
|
+
"file_name"
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
export const ENDPOINTS = {
|
|
211
|
+
"search_papers": {
|
|
212
|
+
"method": "POST",
|
|
213
|
+
"path": "/meta-search"
|
|
214
|
+
},
|
|
215
|
+
"semantic_search": {
|
|
216
|
+
"method": "POST",
|
|
217
|
+
"path": "/agentic-search"
|
|
218
|
+
},
|
|
219
|
+
"list_catalog": {
|
|
220
|
+
"method": "GET",
|
|
221
|
+
"path": "/meta-catalog"
|
|
222
|
+
},
|
|
223
|
+
"read_content": {
|
|
224
|
+
"method": "GET",
|
|
225
|
+
"path": "/content"
|
|
226
|
+
},
|
|
227
|
+
"get_resource": {
|
|
228
|
+
"method": "GET",
|
|
229
|
+
"path": "/resource"
|
|
230
|
+
}
|
|
231
|
+
};
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// MCP server:注册 list_tools / call_tool 两个 handler。
|
|
2
|
+
// TODO: 后续可加 SSE / Streamable HTTP 形态(目前只支持 stdio,覆盖主流 coding agent)。
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { TOOLS, TOOLS_VERSION } from "./generated/tools.js";
|
|
6
|
+
import { executeTool } from "./tools.js";
|
|
7
|
+
export function createServer(config) {
|
|
8
|
+
const server = new Server({
|
|
9
|
+
name: "sciverse-mcp",
|
|
10
|
+
version: TOOLS_VERSION,
|
|
11
|
+
}, {
|
|
12
|
+
capabilities: {
|
|
13
|
+
tools: {},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
17
|
+
tools: TOOLS,
|
|
18
|
+
}));
|
|
19
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
20
|
+
const { name, arguments: args } = request.params;
|
|
21
|
+
const result = await executeTool(config, name, (args ?? {}));
|
|
22
|
+
// SDK 的 CallTool 响应 schema 含可选 _meta / structuredContent 等字段,我们只用 content + isError。
|
|
23
|
+
return result;
|
|
24
|
+
});
|
|
25
|
+
return server;
|
|
26
|
+
}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
export type ContentBlock = {
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: "image";
|
|
7
|
+
data: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
};
|
|
10
|
+
export interface ToolResult {
|
|
11
|
+
isError: boolean;
|
|
12
|
+
content: ContentBlock[];
|
|
13
|
+
}
|
|
14
|
+
export declare function executeTool(config: Config, name: string, args: Record<string, unknown>): Promise<ToolResult>;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// HTTP 调用层 + agent-友好参数到后端 canonical payload 的转换。
|
|
2
|
+
//
|
|
3
|
+
// `toBackendPayload` 是从 packages/typescript/src/client.ts 的 `toBackendPayload`
|
|
4
|
+
// 复制过来的(约 50 行)。MCP 包刻意不依赖 TS SDK,避免 monorepo 内部环依赖。
|
|
5
|
+
// 后续如有第 4 处复制,再用 codegen 收敛。
|
|
6
|
+
import { ENDPOINTS } from "./generated/tools.js";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
const PASSTHROUGH = ["query", "page", "page_size", "fields"];
|
|
9
|
+
function toBackendPayload(args) {
|
|
10
|
+
const out = {};
|
|
11
|
+
const filters = [];
|
|
12
|
+
const sort = [];
|
|
13
|
+
for (const k of PASSTHROUGH) {
|
|
14
|
+
if (args[k] !== undefined && args[k] !== null)
|
|
15
|
+
out[k] = args[k];
|
|
16
|
+
}
|
|
17
|
+
const addFilter = (field, operator, value) => {
|
|
18
|
+
filters.push({ field, operator, value });
|
|
19
|
+
};
|
|
20
|
+
if (args.title_contains != null)
|
|
21
|
+
addFilter("title", "FILTER_OP_CONTAINS", args.title_contains);
|
|
22
|
+
if (args.abstract_contains != null)
|
|
23
|
+
addFilter("abstract", "FILTER_OP_CONTAINS", args.abstract_contains);
|
|
24
|
+
if (Array.isArray(args.authors) && args.authors.length > 0) {
|
|
25
|
+
addFilter("author", "FILTER_OP_IN", args.authors);
|
|
26
|
+
}
|
|
27
|
+
if (args.year_from != null)
|
|
28
|
+
addFilter("publication_published_year", "FILTER_OP_GTE", args.year_from);
|
|
29
|
+
if (args.year_to != null)
|
|
30
|
+
addFilter("publication_published_year", "FILTER_OP_LTE", args.year_to);
|
|
31
|
+
if (Array.isArray(args.journals) && args.journals.length > 0) {
|
|
32
|
+
addFilter("publication_venue_name", "FILTER_OP_IN", args.journals);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(args.subjects) && args.subjects.length > 0) {
|
|
35
|
+
addFilter("subjects", "FILTER_OP_IN", args.subjects);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(args.filters_advanced)) {
|
|
38
|
+
for (const item of args.filters_advanced) {
|
|
39
|
+
filters.push({ operator: "FILTER_OP_EQ", ...item });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const sortByYear = args.sort_by_year;
|
|
43
|
+
if (sortByYear && sortByYear !== "none") {
|
|
44
|
+
sort.push({
|
|
45
|
+
field: "publication_published_year",
|
|
46
|
+
order: sortByYear === "desc" ? "SORT_ORDER_DESC" : "SORT_ORDER_ASC",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (filters.length > 0)
|
|
50
|
+
out.filters = filters;
|
|
51
|
+
if (sort.length > 0)
|
|
52
|
+
out.sort = sort;
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
const SKILL_NAME = "sciverse";
|
|
56
|
+
const CHANNEL = "mcp";
|
|
57
|
+
const PLATFORM = process.platform;
|
|
58
|
+
async function call(config, method, path, options = {}) {
|
|
59
|
+
const url = new URL(`${config.baseUrl}${path}`);
|
|
60
|
+
if (options.query) {
|
|
61
|
+
for (const [k, v] of Object.entries(options.query)) {
|
|
62
|
+
if (v !== undefined && v !== null)
|
|
63
|
+
url.searchParams.set(k, String(v));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const init = {
|
|
67
|
+
method,
|
|
68
|
+
headers: {
|
|
69
|
+
authorization: `Bearer ${config.token}`,
|
|
70
|
+
"content-type": "application/json",
|
|
71
|
+
"x-request-id": `${SKILL_NAME}-${PLATFORM}-${CHANNEL}-${randomUUID()}`,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
if (options.body !== undefined) {
|
|
75
|
+
init.body = JSON.stringify(Object.fromEntries(Object.entries(options.body).filter(([, v]) => v !== undefined)));
|
|
76
|
+
}
|
|
77
|
+
const res = await fetch(url, init);
|
|
78
|
+
const text = await res.text();
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
return {
|
|
81
|
+
isError: true,
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: JSON.stringify({ status: res.status, body: safeParse(text) }, null, 2),
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
isError: false,
|
|
92
|
+
content: [{ type: "text", text }],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function safeParse(text) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(text);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return text;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function callBinary(config, path, query) {
|
|
104
|
+
const url = new URL(`${config.baseUrl}${path}`);
|
|
105
|
+
for (const [k, v] of Object.entries(query)) {
|
|
106
|
+
if (v !== undefined && v !== null)
|
|
107
|
+
url.searchParams.set(k, String(v));
|
|
108
|
+
}
|
|
109
|
+
const res = await fetch(url, {
|
|
110
|
+
method: "GET",
|
|
111
|
+
headers: {
|
|
112
|
+
authorization: `Bearer ${config.token}`,
|
|
113
|
+
accept: "image/*",
|
|
114
|
+
"x-request-id": `${SKILL_NAME}-${PLATFORM}-${CHANNEL}-${randomUUID()}`,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
const body = await res.text();
|
|
119
|
+
return {
|
|
120
|
+
isError: true,
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: JSON.stringify({ status: res.status, body: safeParse(body) }, null, 2),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const mimeType = (res.headers.get("content-type") || "application/octet-stream")
|
|
130
|
+
.split(";")[0]
|
|
131
|
+
.trim();
|
|
132
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
133
|
+
return {
|
|
134
|
+
isError: false,
|
|
135
|
+
content: [{ type: "image", data: buf.toString("base64"), mimeType }],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export async function executeTool(config, name, args) {
|
|
139
|
+
const endpoint = ENDPOINTS[name];
|
|
140
|
+
if (!endpoint) {
|
|
141
|
+
return {
|
|
142
|
+
isError: true,
|
|
143
|
+
content: [{ type: "text", text: JSON.stringify({ error: `unknown tool: ${name}` }) }],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
switch (name) {
|
|
147
|
+
case "search_papers":
|
|
148
|
+
return call(config, "POST", endpoint.path, { body: toBackendPayload(args) });
|
|
149
|
+
case "semantic_search":
|
|
150
|
+
return call(config, "POST", endpoint.path, { body: args });
|
|
151
|
+
case "list_catalog": {
|
|
152
|
+
const { include_sample_values } = args;
|
|
153
|
+
return call(config, "GET", endpoint.path, {
|
|
154
|
+
query: { include_sample_values: String(Boolean(include_sample_values)) },
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
case "read_content": {
|
|
158
|
+
const { doc_id, offset, limit } = args;
|
|
159
|
+
if (!doc_id) {
|
|
160
|
+
return {
|
|
161
|
+
isError: true,
|
|
162
|
+
content: [{ type: "text", text: JSON.stringify({ error: "doc_id is required" }) }],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return call(config, "GET", endpoint.path, { query: { doc_id, offset, limit } });
|
|
166
|
+
}
|
|
167
|
+
case "get_resource": {
|
|
168
|
+
const { file_name } = args;
|
|
169
|
+
if (!file_name) {
|
|
170
|
+
return {
|
|
171
|
+
isError: true,
|
|
172
|
+
content: [{ type: "text", text: JSON.stringify({ error: "file_name is required" }) }],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return callBinary(config, endpoint.path, { file_name });
|
|
176
|
+
}
|
|
177
|
+
default:
|
|
178
|
+
return {
|
|
179
|
+
isError: true,
|
|
180
|
+
content: [{ type: "text", text: JSON.stringify({ error: `unhandled tool: ${name}` }) }],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sciverse-mcp-server",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "SciVerse MCP server — exposes academic paper retrieval (search_papers / semantic_search / read_content) to MCP-compatible coding agents (Claude Code, Cursor, Codex CLI, Windsurf, ...).",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://github.com/opendatalab/SciVerse-agent-tools/tree/main/packages/mcp",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/opendatalab/SciVerse-agent-tools.git",
|
|
10
|
+
"directory": "packages/mcp"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/opendatalab/SciVerse-agent-tools/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "SciVerse Platform Team",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/server.js",
|
|
18
|
+
"types": "./dist/server.d.ts",
|
|
19
|
+
"bin": {
|
|
20
|
+
"sciverse-mcp": "./dist/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"gen": "node scripts/gen-tools.mjs",
|
|
28
|
+
"build": "npm run gen && tsc",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"prepack": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"sciverse",
|
|
38
|
+
"mcp",
|
|
39
|
+
"model-context-protocol",
|
|
40
|
+
"agent",
|
|
41
|
+
"academic-papers",
|
|
42
|
+
"rag",
|
|
43
|
+
"claude-code",
|
|
44
|
+
"cursor",
|
|
45
|
+
"codex"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20",
|
|
52
|
+
"typescript": "^5.4",
|
|
53
|
+
"vitest": "^1.4",
|
|
54
|
+
"yaml": "^2.4"
|
|
55
|
+
}
|
|
56
|
+
}
|