@0xquinto/rss-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/README.md +99 -0
- package/dist/content.d.ts +3 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +33 -0
- package/dist/content.js.map +1 -0
- package/dist/db.d.ts +69 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +213 -0
- package/dist/db.js.map +1 -0
- package/dist/feeds.d.ts +14 -0
- package/dist/feeds.d.ts.map +1 -0
- package/dist/feeds.js +54 -0
- package/dist/feeds.js.map +1 -0
- package/dist/hn.d.ts +7 -0
- package/dist/hn.d.ts.map +1 -0
- package/dist/hn.js +28 -0
- package/dist/hn.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +298 -0
- package/dist/index.js.map +1 -0
- package/dist/opml.d.ts +7 -0
- package/dist/opml.d.ts.map +1 -0
- package/dist/opml.js +24 -0
- package/dist/opml.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# RSS MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP server that lets AI assistants subscribe to, fetch, search, and manage RSS feeds. Built with TypeScript and SQLite with full-text search.
|
|
4
|
+
|
|
5
|
+
## Inspiration
|
|
6
|
+
|
|
7
|
+
Inspired by [Andrej Karpathy's post](https://x.com/karpathy/status/2018043254986703167) on reclaiming your information diet:
|
|
8
|
+
|
|
9
|
+
> "Finding myself going back to RSS/Atom feeds a lot more recently. There's a lot more higher quality longform and a lot less slop intended to provoke... We should bring back RSS - it's open, pervasive, hackable."
|
|
10
|
+
|
|
11
|
+
**Quick start with curated feeds**: Import the [Most Popular Blogs of Hacker News 2025](https://gist.github.com/emschwartz/e6d2bf860ccc367fe37ff953ba6de66b) OPML file to get 92 high-quality tech blogs.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Subscribe to RSS/Atom feeds and fetch new posts
|
|
16
|
+
- Full-text search across titles, summaries, and content (FTS5)
|
|
17
|
+
- Extract clean article content from web pages (Readability)
|
|
18
|
+
- Import feeds in bulk from OPML files
|
|
19
|
+
- Track read/unread state
|
|
20
|
+
- Daily digest for compact summaries
|
|
21
|
+
- HackerNews popularity ranking for posts
|
|
22
|
+
- Conditional HTTP requests (ETag/Last-Modified) and per-feed rate limiting
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### As a Claude Code Plugin (Recommended)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
/plugin marketplace add 0xQuinto/rss-mcp
|
|
30
|
+
/plugin install rss-mcp@0xquinto-rss-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Via bunx
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bunx @0xquinto/rss-mcp
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Via npx
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx @0xquinto/rss-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Tools
|
|
46
|
+
|
|
47
|
+
| Tool | Description |
|
|
48
|
+
| ------------------- | ------------------------------------------------------------------ |
|
|
49
|
+
| `list_feeds` | List all subscribed feeds |
|
|
50
|
+
| `add_feed` | Subscribe to a feed by URL |
|
|
51
|
+
| `remove_feed` | Unsubscribe and delete all posts for a feed |
|
|
52
|
+
| `import_opml` | Bulk import feeds from an OPML file |
|
|
53
|
+
| `refresh_feeds` | Fetch latest posts (all feeds or a specific one) |
|
|
54
|
+
| `get_posts` | Query posts with filtering, pagination, and full-text search |
|
|
55
|
+
| `get_post_content` | Retrieve full article content (fetched and cached on first access) |
|
|
56
|
+
| `get_daily_digest` | Get compact digest of recent posts for synthesis |
|
|
57
|
+
| `get_popular_posts` | Rank recent posts by HackerNews engagement |
|
|
58
|
+
| `mark_read` | Mark posts as read |
|
|
59
|
+
| `mark_unread` | Mark posts as unread |
|
|
60
|
+
|
|
61
|
+
## Data
|
|
62
|
+
|
|
63
|
+
Posts are stored in `~/.rss-mcp/rss.db` (SQLite). The database and FTS5 index are created automatically on first run.
|
|
64
|
+
|
|
65
|
+
## MCP Configuration
|
|
66
|
+
|
|
67
|
+
### Claude Code
|
|
68
|
+
|
|
69
|
+
Add to your project or user MCP settings:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"rss-mcp": {
|
|
75
|
+
"command": "bunx",
|
|
76
|
+
"args": ["@0xquinto/rss-mcp"]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Claude Desktop
|
|
83
|
+
|
|
84
|
+
Add to `claude_desktop_config.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"rss-mcp": {
|
|
90
|
+
"command": "bunx",
|
|
91
|
+
"args": ["@0xquinto/rss-mcp"]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU1D;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBzE"}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Readability } from "@mozilla/readability";
|
|
2
|
+
import { parseHTML } from "linkedom";
|
|
3
|
+
export function extractContent(html) {
|
|
4
|
+
try {
|
|
5
|
+
const { document } = parseHTML(html);
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
const reader = new Readability(document);
|
|
8
|
+
const article = reader.parse();
|
|
9
|
+
return article?.textContent ?? null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function fetchAndExtract(url) {
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(url, {
|
|
18
|
+
redirect: "follow",
|
|
19
|
+
signal: AbortSignal.timeout(30000),
|
|
20
|
+
headers: {
|
|
21
|
+
"User-Agent": "Mozilla/5.0 (compatible; RSS-MCP/1.0)",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok)
|
|
25
|
+
return null;
|
|
26
|
+
const html = await response.text();
|
|
27
|
+
return extractContent(html);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QACrC,8DAA8D;QAC9D,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,QAAe,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QAC/B,OAAO,OAAO,EAAE,WAAW,IAAI,IAAI,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;YAClC,OAAO,EAAE;gBACP,YAAY,EAAE,uCAAuC;aACtD;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAE9B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
export declare function getDb(): Database.Database;
|
|
3
|
+
export interface Feed {
|
|
4
|
+
id: number;
|
|
5
|
+
url: string;
|
|
6
|
+
title: string | null;
|
|
7
|
+
site_url: string | null;
|
|
8
|
+
last_fetched: string | null;
|
|
9
|
+
etag: string | null;
|
|
10
|
+
last_modified: string | null;
|
|
11
|
+
created_at: string;
|
|
12
|
+
}
|
|
13
|
+
export interface Post {
|
|
14
|
+
id: number;
|
|
15
|
+
feed_id: number;
|
|
16
|
+
guid: string;
|
|
17
|
+
title: string | null;
|
|
18
|
+
url: string | null;
|
|
19
|
+
summary: string | null;
|
|
20
|
+
content: string | null;
|
|
21
|
+
author: string | null;
|
|
22
|
+
published_at: string | null;
|
|
23
|
+
fetched_at: string;
|
|
24
|
+
is_read: number;
|
|
25
|
+
read_at: string | null;
|
|
26
|
+
starred: number;
|
|
27
|
+
feed_title?: string;
|
|
28
|
+
feed_url?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function addFeed(url: string, title?: string, siteUrl?: string): Feed;
|
|
31
|
+
export declare function listFeeds(): Feed[];
|
|
32
|
+
export declare function removeFeed(feedId: number): boolean;
|
|
33
|
+
export declare function getFeed(feedId: number): Feed | null;
|
|
34
|
+
export declare function updateFeedMeta(feedId: number, title: string | null, siteUrl: string | null, etag: string | null, lastModified: string | null): void;
|
|
35
|
+
export interface PostEntry {
|
|
36
|
+
guid: string;
|
|
37
|
+
title?: string;
|
|
38
|
+
url?: string;
|
|
39
|
+
summary?: string;
|
|
40
|
+
author?: string;
|
|
41
|
+
published_at?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function upsertPosts(feedId: number, entries: PostEntry[]): number;
|
|
44
|
+
export interface GetPostsOptions {
|
|
45
|
+
feedId?: number;
|
|
46
|
+
limit?: number;
|
|
47
|
+
offset?: number;
|
|
48
|
+
unreadOnly?: boolean;
|
|
49
|
+
search?: string;
|
|
50
|
+
since?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function getPosts(options?: GetPostsOptions): Post[];
|
|
53
|
+
export declare function markRead(postIds: number[]): number;
|
|
54
|
+
export declare function markUnread(postIds: number[]): number;
|
|
55
|
+
export interface DigestPost {
|
|
56
|
+
id: number;
|
|
57
|
+
feed: string;
|
|
58
|
+
title: string | null;
|
|
59
|
+
summary: string | null;
|
|
60
|
+
url: string | null;
|
|
61
|
+
published_at: string | null;
|
|
62
|
+
}
|
|
63
|
+
export declare function getDailyDigest(hours?: number, maxSummaryLength?: number): DigestPost[];
|
|
64
|
+
export declare function updatePostContent(postId: number, content: string): void;
|
|
65
|
+
export interface PostContent extends Post {
|
|
66
|
+
truncated: boolean;
|
|
67
|
+
}
|
|
68
|
+
export declare function getPostContent(postId: number, full?: boolean): PostContent | null;
|
|
69
|
+
//# sourceMappingURL=db.d.ts.map
|
package/dist/db.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { mkdirSync, existsSync } from "fs";
|
|
5
|
+
const DB_DIR = join(homedir(), ".rss-mcp");
|
|
6
|
+
const DB_PATH = join(DB_DIR, "rss.db");
|
|
7
|
+
const SCHEMA = `
|
|
8
|
+
CREATE TABLE IF NOT EXISTS feeds (
|
|
9
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10
|
+
url TEXT UNIQUE NOT NULL,
|
|
11
|
+
title TEXT,
|
|
12
|
+
site_url TEXT,
|
|
13
|
+
last_fetched TEXT,
|
|
14
|
+
etag TEXT,
|
|
15
|
+
last_modified TEXT,
|
|
16
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE TABLE IF NOT EXISTS posts (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
feed_id INTEGER NOT NULL REFERENCES feeds(id) ON DELETE CASCADE,
|
|
22
|
+
guid TEXT NOT NULL,
|
|
23
|
+
title TEXT,
|
|
24
|
+
url TEXT,
|
|
25
|
+
summary TEXT,
|
|
26
|
+
content TEXT,
|
|
27
|
+
author TEXT,
|
|
28
|
+
published_at TEXT,
|
|
29
|
+
fetched_at TEXT DEFAULT (datetime('now')),
|
|
30
|
+
is_read INTEGER DEFAULT 0,
|
|
31
|
+
read_at TEXT,
|
|
32
|
+
starred INTEGER DEFAULT 0,
|
|
33
|
+
UNIQUE(feed_id, guid)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5(
|
|
37
|
+
title, summary, content,
|
|
38
|
+
content='posts',
|
|
39
|
+
content_rowid='id'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE TRIGGER IF NOT EXISTS posts_ai AFTER INSERT ON posts BEGIN
|
|
43
|
+
INSERT INTO posts_fts(rowid, title, summary, content)
|
|
44
|
+
VALUES (new.id, new.title, new.summary, new.content);
|
|
45
|
+
END;
|
|
46
|
+
|
|
47
|
+
CREATE TRIGGER IF NOT EXISTS posts_ad AFTER DELETE ON posts BEGIN
|
|
48
|
+
INSERT INTO posts_fts(posts_fts, rowid, title, summary, content)
|
|
49
|
+
VALUES ('delete', old.id, old.title, old.summary, old.content);
|
|
50
|
+
END;
|
|
51
|
+
|
|
52
|
+
CREATE TRIGGER IF NOT EXISTS posts_au AFTER UPDATE ON posts BEGIN
|
|
53
|
+
INSERT INTO posts_fts(posts_fts, rowid, title, summary, content)
|
|
54
|
+
VALUES ('delete', old.id, old.title, old.summary, old.content);
|
|
55
|
+
INSERT INTO posts_fts(rowid, title, summary, content)
|
|
56
|
+
VALUES (new.id, new.title, new.summary, new.content);
|
|
57
|
+
END;
|
|
58
|
+
`;
|
|
59
|
+
let db = null;
|
|
60
|
+
export function getDb() {
|
|
61
|
+
if (db)
|
|
62
|
+
return db;
|
|
63
|
+
if (!existsSync(DB_DIR)) {
|
|
64
|
+
mkdirSync(DB_DIR, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
db = new Database(DB_PATH);
|
|
67
|
+
db.pragma("journal_mode = WAL");
|
|
68
|
+
db.pragma("foreign_keys = ON");
|
|
69
|
+
db.exec(SCHEMA);
|
|
70
|
+
return db;
|
|
71
|
+
}
|
|
72
|
+
export function addFeed(url, title, siteUrl) {
|
|
73
|
+
const db = getDb();
|
|
74
|
+
const stmt = db.prepare("INSERT INTO feeds (url, title, site_url) VALUES (?, ?, ?)");
|
|
75
|
+
const result = stmt.run(url, title ?? null, siteUrl ?? null);
|
|
76
|
+
const feed = db
|
|
77
|
+
.prepare("SELECT * FROM feeds WHERE id = ?")
|
|
78
|
+
.get(result.lastInsertRowid);
|
|
79
|
+
return feed;
|
|
80
|
+
}
|
|
81
|
+
export function listFeeds() {
|
|
82
|
+
const db = getDb();
|
|
83
|
+
return db
|
|
84
|
+
.prepare("SELECT * FROM feeds ORDER BY created_at DESC")
|
|
85
|
+
.all();
|
|
86
|
+
}
|
|
87
|
+
export function removeFeed(feedId) {
|
|
88
|
+
const db = getDb();
|
|
89
|
+
const result = db.prepare("DELETE FROM feeds WHERE id = ?").run(feedId);
|
|
90
|
+
return result.changes > 0;
|
|
91
|
+
}
|
|
92
|
+
export function getFeed(feedId) {
|
|
93
|
+
const db = getDb();
|
|
94
|
+
return (db.prepare("SELECT * FROM feeds WHERE id = ?").get(feedId) ?? null);
|
|
95
|
+
}
|
|
96
|
+
export function updateFeedMeta(feedId, title, siteUrl, etag, lastModified) {
|
|
97
|
+
const db = getDb();
|
|
98
|
+
db.prepare(`UPDATE feeds
|
|
99
|
+
SET title = COALESCE(?, title),
|
|
100
|
+
site_url = COALESCE(?, site_url),
|
|
101
|
+
last_fetched = datetime('now'),
|
|
102
|
+
etag = ?,
|
|
103
|
+
last_modified = ?
|
|
104
|
+
WHERE id = ?`).run(title, siteUrl, etag, lastModified, feedId);
|
|
105
|
+
}
|
|
106
|
+
export function upsertPosts(feedId, entries) {
|
|
107
|
+
const db = getDb();
|
|
108
|
+
const stmt = db.prepare(`INSERT OR IGNORE INTO posts (feed_id, guid, title, url, summary, author, published_at)
|
|
109
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
110
|
+
let count = 0;
|
|
111
|
+
const insertMany = db.transaction((entries) => {
|
|
112
|
+
for (const e of entries) {
|
|
113
|
+
const result = stmt.run(feedId, e.guid, e.title ?? null, e.url ?? null, e.summary ?? null, e.author ?? null, e.published_at ?? null);
|
|
114
|
+
if (result.changes > 0)
|
|
115
|
+
count++;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
insertMany(entries);
|
|
119
|
+
return count;
|
|
120
|
+
}
|
|
121
|
+
export function getPosts(options = {}) {
|
|
122
|
+
const db = getDb();
|
|
123
|
+
const { feedId, limit = 50, offset = 0, unreadOnly = false, search, since, } = options;
|
|
124
|
+
const conditions = [];
|
|
125
|
+
const params = [];
|
|
126
|
+
if (feedId !== undefined) {
|
|
127
|
+
conditions.push("p.feed_id = ?");
|
|
128
|
+
params.push(feedId);
|
|
129
|
+
}
|
|
130
|
+
if (unreadOnly) {
|
|
131
|
+
conditions.push("p.is_read = 0");
|
|
132
|
+
}
|
|
133
|
+
if (since) {
|
|
134
|
+
conditions.push("p.published_at >= ?");
|
|
135
|
+
params.push(since);
|
|
136
|
+
}
|
|
137
|
+
if (search) {
|
|
138
|
+
conditions.push("p.id IN (SELECT rowid FROM posts_fts WHERE posts_fts MATCH ?)");
|
|
139
|
+
params.push(search);
|
|
140
|
+
}
|
|
141
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
142
|
+
const query = `
|
|
143
|
+
SELECT p.*, f.title as feed_title, f.url as feed_url
|
|
144
|
+
FROM posts p
|
|
145
|
+
JOIN feeds f ON p.feed_id = f.id
|
|
146
|
+
${whereClause}
|
|
147
|
+
ORDER BY p.published_at DESC NULLS LAST
|
|
148
|
+
LIMIT ? OFFSET ?
|
|
149
|
+
`;
|
|
150
|
+
params.push(limit, offset);
|
|
151
|
+
return db.prepare(query).all(...params);
|
|
152
|
+
}
|
|
153
|
+
export function markRead(postIds) {
|
|
154
|
+
if (postIds.length === 0)
|
|
155
|
+
return 0;
|
|
156
|
+
const db = getDb();
|
|
157
|
+
const placeholders = postIds.map(() => "?").join(",");
|
|
158
|
+
const now = new Date().toISOString();
|
|
159
|
+
const result = db
|
|
160
|
+
.prepare(`UPDATE posts SET is_read = 1, read_at = ? WHERE id IN (${placeholders})`)
|
|
161
|
+
.run(now, ...postIds);
|
|
162
|
+
return result.changes;
|
|
163
|
+
}
|
|
164
|
+
export function markUnread(postIds) {
|
|
165
|
+
if (postIds.length === 0)
|
|
166
|
+
return 0;
|
|
167
|
+
const db = getDb();
|
|
168
|
+
const placeholders = postIds.map(() => "?").join(",");
|
|
169
|
+
const result = db
|
|
170
|
+
.prepare(`UPDATE posts SET is_read = 0, read_at = NULL WHERE id IN (${placeholders})`)
|
|
171
|
+
.run(...postIds);
|
|
172
|
+
return result.changes;
|
|
173
|
+
}
|
|
174
|
+
export function getDailyDigest(hours = 24, maxSummaryLength = 300) {
|
|
175
|
+
const db = getDb();
|
|
176
|
+
const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
|
|
177
|
+
const rows = db
|
|
178
|
+
.prepare(`SELECT p.id, f.title AS feed, p.title, p.summary, p.url, p.published_at
|
|
179
|
+
FROM posts p JOIN feeds f ON p.feed_id = f.id
|
|
180
|
+
WHERE p.published_at >= ?
|
|
181
|
+
ORDER BY p.published_at DESC`)
|
|
182
|
+
.all(cutoff);
|
|
183
|
+
return rows.map((row) => ({
|
|
184
|
+
...row,
|
|
185
|
+
summary: row.summary && row.summary.length > maxSummaryLength
|
|
186
|
+
? row.summary.slice(0, maxSummaryLength) + "..."
|
|
187
|
+
: row.summary,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
const MAX_CONTENT_LENGTH = 5000;
|
|
191
|
+
const TRUNCATION_MARKER = "\n\n[Content truncated. Use full=true for complete article.]";
|
|
192
|
+
export function updatePostContent(postId, content) {
|
|
193
|
+
const db = getDb();
|
|
194
|
+
db.prepare("UPDATE posts SET content = ? WHERE id = ?").run(content, postId);
|
|
195
|
+
}
|
|
196
|
+
export function getPostContent(postId, full = false) {
|
|
197
|
+
const db = getDb();
|
|
198
|
+
const row = db
|
|
199
|
+
.prepare(`SELECT p.*, f.title as feed_title
|
|
200
|
+
FROM posts p JOIN feeds f ON p.feed_id = f.id
|
|
201
|
+
WHERE p.id = ?`)
|
|
202
|
+
.get(postId);
|
|
203
|
+
if (!row)
|
|
204
|
+
return null;
|
|
205
|
+
let content = row.content ?? "";
|
|
206
|
+
let truncated = false;
|
|
207
|
+
if (!full && content.length > MAX_CONTENT_LENGTH) {
|
|
208
|
+
content = content.slice(0, MAX_CONTENT_LENGTH) + TRUNCATION_MARKER;
|
|
209
|
+
truncated = true;
|
|
210
|
+
}
|
|
211
|
+
return { ...row, content, truncated };
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
package/dist/feeds.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PostEntry } from "./db.js";
|
|
2
|
+
export interface FetchResult {
|
|
3
|
+
xml: string | null;
|
|
4
|
+
etag: string | null;
|
|
5
|
+
lastModified: string | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function fetchFeed(url: string, etag?: string | null, lastModified?: string | null): Promise<FetchResult>;
|
|
8
|
+
export interface ParsedFeed {
|
|
9
|
+
title: string | null;
|
|
10
|
+
siteUrl: string | null;
|
|
11
|
+
entries: PostEntry[];
|
|
12
|
+
}
|
|
13
|
+
export declare function parseFeed(xml: string): ParsedFeed;
|
|
14
|
+
//# sourceMappingURL=feeds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,OAAO,CAAC,WAAW,CAAC,CAyBtB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAkBD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CA6BjD"}
|
package/dist/feeds.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { parseFeed as parseRssFeed } from "feedsmith";
|
|
2
|
+
export async function fetchFeed(url, etag, lastModified) {
|
|
3
|
+
const headers = {};
|
|
4
|
+
if (etag)
|
|
5
|
+
headers["If-None-Match"] = etag;
|
|
6
|
+
if (lastModified)
|
|
7
|
+
headers["If-Modified-Since"] = lastModified;
|
|
8
|
+
const response = await fetch(url, {
|
|
9
|
+
headers,
|
|
10
|
+
redirect: "follow",
|
|
11
|
+
signal: AbortSignal.timeout(30000),
|
|
12
|
+
});
|
|
13
|
+
if (response.status === 304) {
|
|
14
|
+
return { xml: null, etag: null, lastModified: null };
|
|
15
|
+
}
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
18
|
+
}
|
|
19
|
+
const xml = await response.text();
|
|
20
|
+
return {
|
|
21
|
+
xml,
|
|
22
|
+
etag: response.headers.get("etag"),
|
|
23
|
+
lastModified: response.headers.get("last-modified"),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function parseFeed(xml) {
|
|
27
|
+
const result = parseRssFeed(xml);
|
|
28
|
+
const feed = result.feed;
|
|
29
|
+
const entries = (feed.items ?? []).map((item) => {
|
|
30
|
+
let authorStr;
|
|
31
|
+
if (typeof item.author === "string") {
|
|
32
|
+
authorStr = item.author;
|
|
33
|
+
}
|
|
34
|
+
else if (item.author) {
|
|
35
|
+
authorStr = item.author.name ?? item.author.email;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
guid: item.id ?? item.link ?? "",
|
|
39
|
+
title: item.title,
|
|
40
|
+
url: item.link,
|
|
41
|
+
summary: item.description ?? item.content,
|
|
42
|
+
author: authorStr,
|
|
43
|
+
published_at: item.published
|
|
44
|
+
? new Date(item.published).toISOString()
|
|
45
|
+
: undefined,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
title: feed.title ?? null,
|
|
50
|
+
siteUrl: feed.link ?? null,
|
|
51
|
+
entries,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=feeds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feeds.js","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,WAAW,CAAC;AAStD,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAAW,EACX,IAAoB,EACpB,YAA4B;IAE5B,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,IAAI;QAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC;IAC1C,IAAI,YAAY;QAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,YAAY,CAAC;IAE9D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,OAAO;QACP,QAAQ,EAAE,QAAQ;QAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;KACnC,CAAC,CAAC;IAEH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IACvD,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAClC,OAAO;QACL,GAAG;QACH,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;QAClC,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;KACpD,CAAC;AACJ,CAAC;AAwBD,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAgB,CAAC;IAErC,MAAM,OAAO,GAAgB,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAc,EAAE,EAAE;QACrE,IAAI,SAA6B,CAAC;QAClC,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACpC,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QACpD,CAAC;QAED,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE;YAChC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,EAAE,IAAI,CAAC,IAAI;YACd,OAAO,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO;YACzC,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,IAAI,CAAC,SAAS;gBAC1B,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;gBACxC,CAAC,CAAC,SAAS;SACd,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI;QACzB,OAAO,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;QAC1B,OAAO;KACR,CAAC;AACJ,CAAC"}
|
package/dist/hn.d.ts
ADDED
package/dist/hn.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hn.d.ts","sourceRoot":"","sources":["../src/hn.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA2BvE"}
|
package/dist/hn.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const HN_SEARCH_URL = "https://hn.algolia.com/api/v1/search";
|
|
2
|
+
export async function fetchHNScore(url) {
|
|
3
|
+
try {
|
|
4
|
+
const searchUrl = new URL(HN_SEARCH_URL);
|
|
5
|
+
searchUrl.searchParams.set("query", url);
|
|
6
|
+
searchUrl.searchParams.set("restrictSearchableAttributes", "url");
|
|
7
|
+
searchUrl.searchParams.set("hitsPerPage", "1");
|
|
8
|
+
const response = await fetch(searchUrl.toString(), {
|
|
9
|
+
signal: AbortSignal.timeout(10000),
|
|
10
|
+
});
|
|
11
|
+
if (!response.ok)
|
|
12
|
+
return null;
|
|
13
|
+
const data = (await response.json());
|
|
14
|
+
const hits = data.hits ?? [];
|
|
15
|
+
if (hits.length === 0)
|
|
16
|
+
return null;
|
|
17
|
+
const hit = hits[0];
|
|
18
|
+
return {
|
|
19
|
+
score: hit.points ?? 0,
|
|
20
|
+
comments: hit.num_comments ?? 0,
|
|
21
|
+
hn_url: `https://news.ycombinator.com/item?id=${hit.objectID}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=hn.js.map
|
package/dist/hn.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hn.js","sourceRoot":"","sources":["../src/hn.ts"],"names":[],"mappings":"AAAA,MAAM,aAAa,GAAG,sCAAsC,CAAC;AAgB7D,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW;IAC5C,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC;QACzC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACzC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;QAClE,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAE/C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE;YACjD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAE9B,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAqB,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAE7B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEnC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO;YACL,KAAK,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC;YACtB,QAAQ,EAAE,GAAG,CAAC,YAAY,IAAI,CAAC;YAC/B,MAAM,EAAE,wCAAwC,GAAG,CAAC,QAAQ,EAAE;SAC/D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import * as db from "./db.js";
|
|
7
|
+
import { fetchFeed, parseFeed } from "./feeds.js";
|
|
8
|
+
import { fetchHNScore } from "./hn.js";
|
|
9
|
+
import { parseOpml } from "./opml.js";
|
|
10
|
+
import { fetchAndExtract } from "./content.js";
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: "rss-mcp",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
});
|
|
15
|
+
// Tool: list_feeds
|
|
16
|
+
server.tool("list_feeds", {}, async () => {
|
|
17
|
+
const feeds = db.listFeeds();
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(feeds, null, 2) }],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
// Tool: add_feed
|
|
23
|
+
server.tool("add_feed", { url: z.string().describe("RSS/Atom feed URL to subscribe to") }, async ({ url }) => {
|
|
24
|
+
try {
|
|
25
|
+
const feed = db.addFeed(url);
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: JSON.stringify(feed, null, 2) }],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
33
|
+
isError: true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// Tool: remove_feed
|
|
38
|
+
server.tool("remove_feed", { feed_id: z.number().describe("ID of the feed to remove") }, async ({ feed_id }) => {
|
|
39
|
+
const removed = db.removeFeed(feed_id);
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: JSON.stringify({ removed }) }],
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
// Tool: import_opml
|
|
45
|
+
server.tool("import_opml", { file_path: z.string().describe("Path to OPML file") }, async ({ file_path }) => {
|
|
46
|
+
try {
|
|
47
|
+
const text = readFileSync(file_path, "utf-8");
|
|
48
|
+
const feeds = parseOpml(text);
|
|
49
|
+
let imported = 0;
|
|
50
|
+
for (const f of feeds) {
|
|
51
|
+
try {
|
|
52
|
+
db.addFeed(f.url, f.title, f.siteUrl);
|
|
53
|
+
imported++;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Skip duplicates
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: JSON.stringify({ imported, total_in_file: feeds.length }),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
71
|
+
isError: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// Tool: refresh_feeds
|
|
76
|
+
server.tool("refresh_feeds", {
|
|
77
|
+
feed_id: z
|
|
78
|
+
.number()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("Optional specific feed ID to refresh"),
|
|
81
|
+
}, async ({ feed_id }) => {
|
|
82
|
+
const feeds = feed_id
|
|
83
|
+
? [db.getFeed(feed_id)].filter(Boolean)
|
|
84
|
+
: db.listFeeds();
|
|
85
|
+
const minInterval = 15 * 60 * 1000; // 15 minutes
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const results = {
|
|
88
|
+
refreshed: 0,
|
|
89
|
+
new_posts: 0,
|
|
90
|
+
skipped: 0,
|
|
91
|
+
errors: [],
|
|
92
|
+
};
|
|
93
|
+
for (const feed of feeds) {
|
|
94
|
+
if (!feed)
|
|
95
|
+
continue;
|
|
96
|
+
// Check rate limit
|
|
97
|
+
if (feed.last_fetched) {
|
|
98
|
+
const lastFetched = new Date(feed.last_fetched).getTime();
|
|
99
|
+
if (now - lastFetched < minInterval) {
|
|
100
|
+
results.skipped++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const { xml, etag, lastModified } = await fetchFeed(feed.url, feed.etag, feed.last_modified);
|
|
106
|
+
if (xml === null) {
|
|
107
|
+
results.skipped++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const { title, siteUrl, entries } = parseFeed(xml);
|
|
111
|
+
const count = db.upsertPosts(feed.id, entries);
|
|
112
|
+
db.updateFeedMeta(feed.id, title, siteUrl, etag, lastModified);
|
|
113
|
+
results.refreshed++;
|
|
114
|
+
results.new_posts += count;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
results.errors.push({
|
|
118
|
+
feed_id: feed.id,
|
|
119
|
+
url: feed.url,
|
|
120
|
+
error: String(error),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
// Tool: get_posts
|
|
129
|
+
server.tool("get_posts", {
|
|
130
|
+
feed_id: z.number().optional().describe("Filter by feed ID"),
|
|
131
|
+
limit: z
|
|
132
|
+
.number()
|
|
133
|
+
.optional()
|
|
134
|
+
.default(50)
|
|
135
|
+
.describe("Maximum posts to return"),
|
|
136
|
+
offset: z.number().optional().default(0).describe("Pagination offset"),
|
|
137
|
+
unread_only: z
|
|
138
|
+
.boolean()
|
|
139
|
+
.optional()
|
|
140
|
+
.default(false)
|
|
141
|
+
.describe("Only return unread posts"),
|
|
142
|
+
search: z.string().optional().describe("FTS5 full-text search query"),
|
|
143
|
+
since: z
|
|
144
|
+
.string()
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("ISO 8601 date to filter posts after"),
|
|
147
|
+
}, async ({ feed_id, limit, offset, unread_only, search, since }) => {
|
|
148
|
+
const posts = db.getPosts({
|
|
149
|
+
feedId: feed_id,
|
|
150
|
+
limit,
|
|
151
|
+
offset,
|
|
152
|
+
unreadOnly: unread_only,
|
|
153
|
+
search,
|
|
154
|
+
since,
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
content: [{ type: "text", text: JSON.stringify(posts, null, 2) }],
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
// Tool: get_post_content
|
|
161
|
+
server.tool("get_post_content", {
|
|
162
|
+
post_id: z.number().describe("ID of the post"),
|
|
163
|
+
full: z
|
|
164
|
+
.boolean()
|
|
165
|
+
.optional()
|
|
166
|
+
.default(false)
|
|
167
|
+
.describe("Return full content without truncation"),
|
|
168
|
+
}, async ({ post_id, full }) => {
|
|
169
|
+
let result = db.getPostContent(post_id, full);
|
|
170
|
+
if (!result) {
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: JSON.stringify({ error: `Post ${post_id} not found` }),
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
isError: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// If no content yet, fetch it
|
|
182
|
+
if (!result.content && result.url) {
|
|
183
|
+
const extractedContent = await fetchAndExtract(result.url);
|
|
184
|
+
if (extractedContent) {
|
|
185
|
+
db.updatePostContent(post_id, extractedContent);
|
|
186
|
+
result = db.getPostContent(post_id, full);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
// Tool: get_daily_digest
|
|
194
|
+
server.tool("get_daily_digest", {
|
|
195
|
+
hours: z.number().optional().default(24).describe("Hours to look back"),
|
|
196
|
+
max_summary_length: z
|
|
197
|
+
.number()
|
|
198
|
+
.optional()
|
|
199
|
+
.default(300)
|
|
200
|
+
.describe("Max chars per summary"),
|
|
201
|
+
}, async ({ hours, max_summary_length }) => {
|
|
202
|
+
const posts = db.getDailyDigest(hours, max_summary_length);
|
|
203
|
+
const feeds = new Set(posts.map((p) => p.feed));
|
|
204
|
+
return {
|
|
205
|
+
content: [
|
|
206
|
+
{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: JSON.stringify({
|
|
209
|
+
period: `last ${hours}h`,
|
|
210
|
+
total_posts: posts.length,
|
|
211
|
+
feeds: feeds.size,
|
|
212
|
+
posts,
|
|
213
|
+
}, null, 2),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
// Tool: mark_read
|
|
219
|
+
server.tool("mark_read", {
|
|
220
|
+
post_ids: z.array(z.number()).describe("Array of post IDs to mark as read"),
|
|
221
|
+
}, async ({ post_ids }) => {
|
|
222
|
+
const marked = db.markRead(post_ids);
|
|
223
|
+
return {
|
|
224
|
+
content: [{ type: "text", text: JSON.stringify({ marked }) }],
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
// Tool: mark_unread
|
|
228
|
+
server.tool("mark_unread", {
|
|
229
|
+
post_ids: z
|
|
230
|
+
.array(z.number())
|
|
231
|
+
.describe("Array of post IDs to mark as unread"),
|
|
232
|
+
}, async ({ post_ids }) => {
|
|
233
|
+
const marked = db.markUnread(post_ids);
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: "text", text: JSON.stringify({ marked }) }],
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
// Tool: get_popular_posts
|
|
239
|
+
server.tool("get_popular_posts", {
|
|
240
|
+
days: z.number().optional().default(7).describe("Days to look back"),
|
|
241
|
+
limit: z.number().optional().default(10).describe("Max posts to return"),
|
|
242
|
+
}, async ({ days, limit }) => {
|
|
243
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
244
|
+
const posts = db.getPosts({ since, limit: 500 });
|
|
245
|
+
const results = [];
|
|
246
|
+
// Fetch HN scores concurrently (max 5 at a time)
|
|
247
|
+
const chunks = [];
|
|
248
|
+
for (let i = 0; i < posts.length; i += 5) {
|
|
249
|
+
chunks.push(posts.slice(i, i + 5));
|
|
250
|
+
}
|
|
251
|
+
for (const chunk of chunks) {
|
|
252
|
+
const chunkResults = await Promise.all(chunk.map(async (post) => {
|
|
253
|
+
if (!post.url)
|
|
254
|
+
return null;
|
|
255
|
+
const hn = await fetchHNScore(post.url);
|
|
256
|
+
if (!hn)
|
|
257
|
+
return null;
|
|
258
|
+
return {
|
|
259
|
+
id: post.id,
|
|
260
|
+
feed: post.feed_title ?? "",
|
|
261
|
+
title: post.title,
|
|
262
|
+
url: post.url,
|
|
263
|
+
published_at: post.published_at,
|
|
264
|
+
hn_score: hn.score,
|
|
265
|
+
hn_comments: hn.comments,
|
|
266
|
+
hn_url: hn.hn_url,
|
|
267
|
+
};
|
|
268
|
+
}));
|
|
269
|
+
results.push(...chunkResults.filter((r) => r !== null));
|
|
270
|
+
}
|
|
271
|
+
// Sort by HN score and take top N
|
|
272
|
+
const ranked = results
|
|
273
|
+
.sort((a, b) => b.hn_score - a.hn_score)
|
|
274
|
+
.slice(0, limit);
|
|
275
|
+
return {
|
|
276
|
+
content: [
|
|
277
|
+
{
|
|
278
|
+
type: "text",
|
|
279
|
+
text: JSON.stringify({
|
|
280
|
+
period: `last ${days} days`,
|
|
281
|
+
total_checked: posts.length,
|
|
282
|
+
posts: ranked,
|
|
283
|
+
}, null, 2),
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
// Start server
|
|
289
|
+
async function main() {
|
|
290
|
+
const transport = new StdioServerTransport();
|
|
291
|
+
await server.connect(transport);
|
|
292
|
+
console.error("RSS MCP Server running on stdio");
|
|
293
|
+
}
|
|
294
|
+
main().catch((error) => {
|
|
295
|
+
console.error("Fatal error:", error);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
});
|
|
298
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/opml.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opml.d.ts","sourceRoot":"","sources":["../src/opml.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAUD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,CAsBtD"}
|
package/dist/opml.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { parseOpml as parseOpmlFeed } from "feedsmith";
|
|
2
|
+
export function parseOpml(opmlText) {
|
|
3
|
+
const result = parseOpmlFeed(opmlText);
|
|
4
|
+
const feeds = [];
|
|
5
|
+
function extractFeeds(outlines) {
|
|
6
|
+
if (!outlines)
|
|
7
|
+
return;
|
|
8
|
+
for (const outline of outlines) {
|
|
9
|
+
if (outline.xmlUrl) {
|
|
10
|
+
feeds.push({
|
|
11
|
+
url: outline.xmlUrl,
|
|
12
|
+
title: outline.text ?? outline.title,
|
|
13
|
+
siteUrl: outline.htmlUrl,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (outline.outlines) {
|
|
17
|
+
extractFeeds(outline.outlines);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
extractFeeds(result.body?.outlines);
|
|
22
|
+
return feeds;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=opml.js.map
|
package/dist/opml.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opml.js","sourceRoot":"","sources":["../src/opml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,WAAW,CAAC;AAgBvD,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,KAAK,GAAe,EAAE,CAAC;IAE7B,SAAS,YAAY,CAAC,QAAmC;QACvD,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;gBACnB,KAAK,CAAC,IAAI,CAAC;oBACT,GAAG,EAAE,OAAO,CAAC,MAAM;oBACnB,KAAK,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK;oBACpC,OAAO,EAAE,OAAO,CAAC,OAAO;iBACzB,CAAC,CAAC;YACL,CAAC;YACD,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACrB,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,QAAqC,CAAC,CAAC;IACjE,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@0xquinto/rss-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for RSS feed management with full-text search and HackerNews ranking",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rss-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "bun run tsc",
|
|
12
|
+
"start": "bun dist/index.js",
|
|
13
|
+
"dev": "bun --watch src/index.ts",
|
|
14
|
+
"prepublishOnly": "bun run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"rss",
|
|
19
|
+
"feeds",
|
|
20
|
+
"hackernews",
|
|
21
|
+
"claude",
|
|
22
|
+
"ai",
|
|
23
|
+
"model-context-protocol"
|
|
24
|
+
],
|
|
25
|
+
"author": "Diego Gomez <diego.gomez210@gmail.com>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/0xquinto/rss-mcp-ts"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
|
+
"@mozilla/readability": "^0.6.0",
|
|
40
|
+
"better-sqlite3": "^12.0.0",
|
|
41
|
+
"feedsmith": "^1.0.0",
|
|
42
|
+
"linkedom": "^0.18.0",
|
|
43
|
+
"zod": "^3.25.76"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"tsx": "^4.0.0",
|
|
49
|
+
"typescript": "^5.7.0"
|
|
50
|
+
}
|
|
51
|
+
}
|