hf-papers-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/bin/daily.js +89 -0
- package/package.json +47 -0
- package/scripts/paper_manager.py +1252 -0
- package/server.js +62 -0
- package/src/papers.js +180 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QEbellavita
|
|
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,94 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/header.svg" alt="hf-papers-mcp — Hugging Face Papers, inside your assistant" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
An MCP server that lets your assistant read Hugging Face Papers — search them, pull the
|
|
6
|
+
daily curated list, fetch metadata, and generate citations.
|
|
7
|
+
|
|
8
|
+
Ask *"what shipped on speculative decoding this week?"* and get real papers with upvote
|
|
9
|
+
counts and abstracts, instead of whatever was in the model's training data.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
Clone it, then point your MCP client (Claude Desktop, Claude Code, or any MCP host) at
|
|
14
|
+
`server.js`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/QEbellavita/hf-papers-mcp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"hf-papers": {
|
|
24
|
+
"command": "node",
|
|
25
|
+
"args": ["/absolute/path/to/hf-papers-mcp/server.js"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Not on npm — use the clone above rather than `npx hf-papers-mcp`.
|
|
32
|
+
|
|
33
|
+
**Requires [uv](https://github.com/astral-sh/uv).** The Python side declares its
|
|
34
|
+
dependencies inline (PEP 723), so `uv run` resolves them on first call — no virtualenv to
|
|
35
|
+
create or `pip install` to remember. Without uv it falls back to bare `python3`, which
|
|
36
|
+
lacks `huggingface_hub` and will return degraded results rather than pretending
|
|
37
|
+
everything is fine.
|
|
38
|
+
|
|
39
|
+
No API key needed for reads. Set `HF_TOKEN` only if you want `link` or `claim`.
|
|
40
|
+
|
|
41
|
+
## Tools
|
|
42
|
+
|
|
43
|
+
| Tool | What it does |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `hf_papers_search` | Search papers by keyword |
|
|
46
|
+
| `hf_papers_daily` | The daily curated list, optionally for a past date |
|
|
47
|
+
| `hf_papers_info` | Full metadata for an arXiv ID — title, authors, abstract, URLs |
|
|
48
|
+
| `hf_papers_citation` | BibTeX, APA or MLA |
|
|
49
|
+
| `hf_papers_check` | Whether a paper is indexed on HF |
|
|
50
|
+
| `hf_papers_index` | Trigger indexing of an arXiv paper on HF |
|
|
51
|
+
|
|
52
|
+
## Example
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
> what were the top papers on Hugging Face yesterday?
|
|
56
|
+
|
|
57
|
+
[1] Domino: Decoupling Causal Modeling from Autoregressive Drafting
|
|
58
|
+
in Speculative Decoding
|
|
59
|
+
arXiv: 2605.29707 | Upvotes: 152
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Bulk digests
|
|
63
|
+
|
|
64
|
+
Pull a date range in one go:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node bin/daily.js 2026-07-01 2026-07-14
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Writes JSON keyed by date to `/tmp/`, and records the window in
|
|
71
|
+
`.hf_papers_last_run.json` so a bare `node bin/daily.js` picks up where the last run
|
|
72
|
+
finished.
|
|
73
|
+
|
|
74
|
+
## Tests
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm test # hermetic — no network
|
|
78
|
+
HF_PAPERS_E2E=1 npm test # adds live API tests
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The default suite is offline and covers tool definitions plus a real MCP handshake over
|
|
82
|
+
stdio. The live tests hit the Hugging Face API and assert that responses come back as
|
|
83
|
+
parsed JSON — a regression guard, because diagnostics written to stdout used to corrupt
|
|
84
|
+
the JSON stream and silently degrade structured responses into raw strings.
|
|
85
|
+
|
|
86
|
+
## Notes
|
|
87
|
+
|
|
88
|
+
Everything runs as a fresh subprocess per call — no daemon, no cached state, nothing
|
|
89
|
+
persisted beyond the optional `bin/daily.js` run marker. Diagnostics go to stderr, since
|
|
90
|
+
stdout is the MCP transport and anything else written there breaks the protocol.
|
|
91
|
+
|
|
92
|
+
## Licence
|
|
93
|
+
|
|
94
|
+
MIT — see [LICENSE](LICENSE).
|
package/bin/daily.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
|
|
6
|
+
const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
7
|
+
|
|
8
|
+
// This script lives in <repo>/bin/, so the repo root is one level up.
|
|
9
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
10
|
+
const SERVER = path.join(ROOT, 'server.js');
|
|
11
|
+
|
|
12
|
+
function dateRange(startStr, endStr) {
|
|
13
|
+
const start = new Date(startStr + 'T00:00:00Z');
|
|
14
|
+
const end = new Date(endStr + 'T00:00:00Z');
|
|
15
|
+
const out = [];
|
|
16
|
+
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
|
|
17
|
+
out.push(d.toISOString().slice(0, 10));
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Track the last completed run so subsequent invocations auto-pick up where they left off.
|
|
23
|
+
const STATE_PATH = path.join(ROOT, '.hf_papers_last_run.json');
|
|
24
|
+
|
|
25
|
+
function readLastRun() {
|
|
26
|
+
try { return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')); } catch { return null; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeLastRun(state) {
|
|
30
|
+
try { fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2) + '\n'); } catch (e) {
|
|
31
|
+
process.stderr.write(`warn: could not write ${STATE_PATH}: ${e.message}\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function todayUTC() {
|
|
36
|
+
return new Date().toISOString().slice(0, 10);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function addDaysUTC(dateStr, n) {
|
|
40
|
+
const d = new Date(dateStr + 'T00:00:00Z');
|
|
41
|
+
d.setUTCDate(d.getUTCDate() + n);
|
|
42
|
+
return d.toISOString().slice(0, 10);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
(async () => {
|
|
46
|
+
const today = todayUTC();
|
|
47
|
+
const last = readLastRun();
|
|
48
|
+
// Defaults: start = day after last run's end (or 30 days ago if no state), end = today.
|
|
49
|
+
const defaultStart = last && last.end ? addDaysUTC(last.end, 1) : addDaysUTC(today, -30);
|
|
50
|
+
const start = process.argv[2] || defaultStart;
|
|
51
|
+
const end = process.argv[3] || today;
|
|
52
|
+
const dates = dateRange(start, end);
|
|
53
|
+
process.stderr.write(`window: ${start} -> ${end} (${dates.length} days)\n`);
|
|
54
|
+
|
|
55
|
+
const transport = new StdioClientTransport({
|
|
56
|
+
command: process.execPath,
|
|
57
|
+
args: [SERVER],
|
|
58
|
+
cwd: ROOT,
|
|
59
|
+
env: { ...process.env },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const client = new Client({ name: 'hf-papers-weekly-runner', version: '0.0.1' }, { capabilities: {} });
|
|
63
|
+
await client.connect(transport);
|
|
64
|
+
|
|
65
|
+
const byDay = {};
|
|
66
|
+
for (const date of dates) {
|
|
67
|
+
process.stderr.write(`fetching ${date}...\n`);
|
|
68
|
+
try {
|
|
69
|
+
const result = await client.callTool({
|
|
70
|
+
name: 'hf_papers_daily',
|
|
71
|
+
arguments: { date, limit: 15 },
|
|
72
|
+
});
|
|
73
|
+
try { byDay[date] = JSON.parse(result.content[0].text); }
|
|
74
|
+
catch { byDay[date] = result; }
|
|
75
|
+
} catch (e) {
|
|
76
|
+
byDay[date] = { error: e.message };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const outPath = `/tmp/hf_papers_${start}_to_${end}.json`;
|
|
81
|
+
fs.writeFileSync(outPath, JSON.stringify(byDay, null, 2));
|
|
82
|
+
process.stderr.write(`wrote ${outPath}\n`);
|
|
83
|
+
console.log(outPath);
|
|
84
|
+
writeLastRun({ start, end, completed_at: new Date().toISOString(), output: outPath });
|
|
85
|
+
await client.close();
|
|
86
|
+
})().catch((err) => {
|
|
87
|
+
console.error('FAILED:', err);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hf-papers-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Hugging Face papers \u2014 search, daily digests, metadata, citations, and indexing",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"main": "server.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"hf-papers-mcp": "server.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"server.js",
|
|
13
|
+
"src/",
|
|
14
|
+
"scripts/",
|
|
15
|
+
"bin/"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "node server.js",
|
|
19
|
+
"test": "node --test \"test/**/*.test.js\"",
|
|
20
|
+
"daily": "node bin/daily.js",
|
|
21
|
+
"prepublishOnly": "npm test"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"huggingface",
|
|
27
|
+
"papers",
|
|
28
|
+
"arxiv",
|
|
29
|
+
"research",
|
|
30
|
+
"llm"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/QEbellavita/hf-papers-mcp.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/QEbellavita/hf-papers-mcp#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/QEbellavita/hf-papers-mcp/issues"
|
|
45
|
+
},
|
|
46
|
+
"author": "Belinda Switzer (https://github.com/QEbellavita)"
|
|
47
|
+
}
|