ai-resources-mcp 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +73 -2
- package/index.js +5 -0
- package/package.json +44 -4
- package/server.js +108 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Drishtant Ghosh
|
|
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
CHANGED
|
@@ -1,3 +1,74 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ai-resources-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An MCP server that gives any AI agent the [AI Resources](https://github.com/Drix10/ai-resources) news layer: daily briefings on AI models, companies, funding rounds, tools and research, searchable and citable. It reads the public API of [blogs.drix10.com](https://blogs.drix10.com); no key needed.
|
|
4
|
+
|
|
5
|
+
| Tool | What it does |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `search_briefings(query, category?, limit?, since?)` | Keyword search, newest first. All words must match. `since` is `YYYY-MM-DD`. |
|
|
8
|
+
| `latest(category?, limit?)` | The newest briefings, overall or for one category. |
|
|
9
|
+
| `get_briefing(id)` | Full Markdown of one briefing (URL or slug), with every source link. |
|
|
10
|
+
| `list_categories()` | Every category slug with its briefing count. |
|
|
11
|
+
|
|
12
|
+
Every result ends with its source link and `Data: github.com/Drix10/ai-resources (CC BY 4.0)`, so answers stay citable.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
> The npm package `ai-resources-mcp` is not published yet. Until it is, run it from a clone:
|
|
17
|
+
> `git clone --depth 1 https://github.com/Drix10/ai-resources && cd ai-resources/mcp && npm ci`,
|
|
18
|
+
> then use `node /full/path/to/ai-resources/mcp/index.js` wherever the snippets below say `npx -y ai-resources-mcp`.
|
|
19
|
+
|
|
20
|
+
**Claude Code**
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
claude mcp add ai-resources -- npx -y ai-resources-mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Claude Desktop** (`claude_desktop_config.json`), **Cursor** (`~/.cursor/mcp.json` or `.cursor/mcp.json`) and **Windsurf** (`~/.codeium/windsurf/mcp_config.json`):
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"ai-resources": { "command": "npx", "args": ["-y", "ai-resources-mcp"] }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**VS Code** (`.vscode/mcp.json`):
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"servers": {
|
|
41
|
+
"ai-resources": { "type": "stdio", "command": "npx", "args": ["-y", "ai-resources-mcp"] }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
or from a terminal: `code --add-mcp "{\"name\":\"ai-resources\",\"command\":\"npx\",\"args\":[\"-y\",\"ai-resources-mcp\"]}"`
|
|
47
|
+
|
|
48
|
+
On Windows, if a client cannot start `npx`, use `"command": "cmd", "args": ["/c", "npx", "-y", "ai-resources-mcp"]`.
|
|
49
|
+
|
|
50
|
+
## Settings
|
|
51
|
+
|
|
52
|
+
`AI_RESOURCES_API_URL`: base URL of the API (default `https://blogs.drix10.com`). Node 18 or later.
|
|
53
|
+
|
|
54
|
+
## The API underneath
|
|
55
|
+
|
|
56
|
+
Usable without MCP, from any HTTP client (CORS open, JSON):
|
|
57
|
+
|
|
58
|
+
- `GET /api/articles?q=&category=&since=&limit=&page=`: `{ items: [{ title, url, slug, category, categorySlug, date, description, headlines, sources: [{ title, url }] }], total, page, limit, totalPages, license }`
|
|
59
|
+
- `GET /api/articles?slug=<slug or URL>`: one item, plus `content` (full Markdown)
|
|
60
|
+
- `GET /api/categories`: `{ items: [{ name, slug, count, url, llms }] }`
|
|
61
|
+
- `GET /llms.txt` and `GET /llms/<category-slug>.txt`: the latest 50 briefings of a category in llms.txt form
|
|
62
|
+
|
|
63
|
+
## Develop
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/Drix10/ai-resources
|
|
67
|
+
cd ai-resources/mcp
|
|
68
|
+
npm ci
|
|
69
|
+
npm test # node:test against a stubbed fetch
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Issues labelled [`mcp`](https://github.com/Drix10/ai-resources/issues?q=is%3Aissue+is%3Aopen+label%3Amcp) are open for contributors; see [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
73
|
+
|
|
74
|
+
Code: [MIT](LICENSE). Data: [Drix10/ai-resources](https://github.com/Drix10/ai-resources), CC BY 4.0.
|
package/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-resources-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"mcpName": "io.github.Drix10/ai-resources",
|
|
5
|
+
"description": "MCP server for the AI Resources knowledge base: search and read daily AI news briefings (models, companies, funding, tools) from any AI agent.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Drix10",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"ai-resources-mcp": "index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js",
|
|
14
|
+
"server.js",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"ai-news",
|
|
21
|
+
"llm",
|
|
22
|
+
"agents",
|
|
23
|
+
"claude",
|
|
24
|
+
"cursor"
|
|
25
|
+
],
|
|
26
|
+
"homepage": "https://github.com/Drix10/ai-resources/tree/main/mcp#readme",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/Drix10/ai-resources.git",
|
|
30
|
+
"directory": "mcp"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/Drix10/ai-resources/issues"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"start": "node index.js",
|
|
40
|
+
"test": "node --test test/*.test.js"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
44
|
+
"zod": "^4.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export const VERSION = '1.0.0';
|
|
5
|
+
export const DATA_LINE = 'Data: github.com/Drix10/ai-resources (CC BY 4.0)';
|
|
6
|
+
export const DEFAULT_BASE = 'https://blogs.drix10.com';
|
|
7
|
+
|
|
8
|
+
const text = (s) => ({ content: [{ type: 'text', text: s }] });
|
|
9
|
+
const fail = (s) => ({ ...text(`${s}\n${DATA_LINE}`), isError: true });
|
|
10
|
+
|
|
11
|
+
function formatItem(a, i) {
|
|
12
|
+
const lines = [`${i + 1}. ${a.title} (${a.date}, ${a.category})`, ` ${a.description}`, ` ${a.url}`];
|
|
13
|
+
const more = (a.headlines || []).slice(1, 6);
|
|
14
|
+
if (more.length) lines.push(` Also in this briefing: ${more.join('; ')}`);
|
|
15
|
+
const src = (a.sources || []).slice(0, 3).map((s) => s.url);
|
|
16
|
+
if (src.length) lines.push(` Sources: ${src.join(' ')}`);
|
|
17
|
+
return lines.join('\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatList(data, pageUrl) {
|
|
21
|
+
if (!data.items?.length) return text(`No briefings found.\nSource: ${pageUrl}\n${DATA_LINE}`);
|
|
22
|
+
const body = data.items.map(formatItem).join('\n\n');
|
|
23
|
+
return text(`${data.items.length} of ${data.total} briefings, newest first:\n\n${body}\n\nSource: ${pageUrl}\n${DATA_LINE}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// All tools read the public JSON API of the blog (see the repository README).
|
|
27
|
+
export function createServer({ base = process.env.AI_RESOURCES_API_URL || DEFAULT_BASE, fetchImpl = globalThis.fetch } = {}) {
|
|
28
|
+
base = base.replace(/\/+$/, '');
|
|
29
|
+
|
|
30
|
+
async function api(path, params = {}) {
|
|
31
|
+
const url = new URL(path, `${base}/`);
|
|
32
|
+
for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== '') url.searchParams.set(k, String(v));
|
|
33
|
+
const res = await fetchImpl(url, { headers: { Accept: 'application/json', 'User-Agent': `ai-resources-mcp/${VERSION}` } });
|
|
34
|
+
const body = await res.json().catch(() => ({}));
|
|
35
|
+
if (!res.ok) throw new Error(body.error || `HTTP ${res.status} from ${url}`);
|
|
36
|
+
return body;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const guard = (fn) => async (args) => {
|
|
40
|
+
try {
|
|
41
|
+
return await fn(args);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return fail(`ai-resources: ${e.message}`);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const server = new McpServer({ name: 'ai-resources', version: VERSION });
|
|
48
|
+
const category = z.string().max(80).optional().describe('Category slug or name, e.g. "ai-developer-tools" (see list_categories)');
|
|
49
|
+
const limit = z.number().int().min(1).max(100).optional().describe('How many briefings (1-100, default 10)');
|
|
50
|
+
|
|
51
|
+
server.registerTool(
|
|
52
|
+
'search_briefings',
|
|
53
|
+
{
|
|
54
|
+
title: 'Search AI briefings',
|
|
55
|
+
description: 'Search daily AI news briefings (models, companies, funding rounds, tools, research) by keywords. All words must match. Newest first.',
|
|
56
|
+
inputSchema: { query: z.string().min(1).max(100).describe('Keywords, e.g. "anthropic claude" or "series a"'), category, limit, since: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe('Only briefings on or after this date (YYYY-MM-DD)') },
|
|
57
|
+
},
|
|
58
|
+
guard(async ({ query, category, limit = 10, since }) => {
|
|
59
|
+
const data = await api('api/articles', { q: query, category, limit, since });
|
|
60
|
+
return formatList(data, `${base}/search?q=${encodeURIComponent(query)}`);
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
server.registerTool(
|
|
65
|
+
'latest',
|
|
66
|
+
{
|
|
67
|
+
title: 'Latest AI briefings',
|
|
68
|
+
description: 'The newest AI news briefings, optionally for one category. Use for "what is new in AI" questions.',
|
|
69
|
+
inputSchema: { category, limit },
|
|
70
|
+
},
|
|
71
|
+
guard(async ({ category, limit = 10 }) => {
|
|
72
|
+
const data = await api('api/articles', { category, limit });
|
|
73
|
+
// (the category's own slug from the API: a name passed in would give a link that 404s)
|
|
74
|
+
const slug = data.items?.[0]?.categorySlug;
|
|
75
|
+
return formatList(data, category && slug ? `${base}/categories/${slug}` : base);
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
server.registerTool(
|
|
80
|
+
'get_briefing',
|
|
81
|
+
{
|
|
82
|
+
title: 'Read one briefing',
|
|
83
|
+
description: 'Full Markdown of one briefing, with every item, key points and source links. Pass its URL or slug from search_briefings/latest.',
|
|
84
|
+
inputSchema: { id: z.string().min(1).max(300).describe('Briefing URL (https://blogs.drix10.com/articles/...) or slug ("category/article-slug")') },
|
|
85
|
+
},
|
|
86
|
+
guard(async ({ id }) => {
|
|
87
|
+
const a = (await api('api/articles', { slug: id })).items?.[0];
|
|
88
|
+
if (!a) return fail(`No briefing found for "${id}".`);
|
|
89
|
+
return text(`# ${a.title}\n${a.date} | ${a.category}\n\n${a.content || a.description}\n\nSource: ${a.url}\n${DATA_LINE}`);
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
server.registerTool(
|
|
94
|
+
'list_categories',
|
|
95
|
+
{
|
|
96
|
+
title: 'List categories',
|
|
97
|
+
description: 'Every briefing category with its slug and count. Use a slug as the category of search_briefings or latest.',
|
|
98
|
+
inputSchema: {},
|
|
99
|
+
},
|
|
100
|
+
guard(async () => {
|
|
101
|
+
const data = await api('api/categories');
|
|
102
|
+
const body = (data.items || []).map((c) => `- ${c.slug}: ${c.name} (${c.count})`).join('\n');
|
|
103
|
+
return text(`${body}\n\nSource: ${base}/categories\n${DATA_LINE}`);
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return server;
|
|
108
|
+
}
|