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 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
- # Temporary Holding Version
1
+ # ai-resources-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createServer } from './server.js';
4
+
5
+ await createServer().connect(new StdioServerTransport());
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "ai-resources-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }