@tensorfeed/mcp-server 1.13.0 → 1.20.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/CHANGELOG.md +37 -0
- package/dist/index.js +286 -8
- package/package.json +1 -1
- package/server.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the [TensorFeed.ai MCP server](https://github.com/RipperMercs/tensorfeed-mcp). Free tools work without configuration; premium tools require a bearer token via the `TENSORFEED_TOKEN` env var. Buy credits at [tensorfeed.ai/developers/agent-payments](https://tensorfeed.ai/developers/agent-payments).
|
|
4
4
|
|
|
5
|
+
## 1.20.0 - 2026-05-04
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- `get_ai_ecosystem_today` now calls the new `/api/today` worker endpoint (single fetch) instead of fanning out 9 individual fetches client-side. Behavior is unchanged from the agent's perspective: same args, same text-rendered output. The win is that the worker's edge cache absorbs the load: a thousand agents calling `get_ai_ecosystem_today` per minute now hit the worker once for 5 minutes (cache TTL) instead of 9000 times. Net cost on the worker stays constant.
|
|
9
|
+
|
|
10
|
+
## 1.19.0 - 2026-05-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `get_ai_ecosystem_today` tool. One-shot composite AI morning brief that internally fans out across 9 free TensorFeed endpoints in parallel (news, 3 paper feeds, HF models/datasets/Spaces, hot GitHub issues, Reddit threads, OpenRouter catalog summary, provider status) and returns a single synthesized text response. Optional `sections` arg (any subset of news / papers / hf / community / inference / status) and `limit_per_section` arg. Graceful degradation: any one feed failing just drops its section. Designed for agents that want a fast snapshot without 8 separate tool calls. Free.
|
|
14
|
+
|
|
15
|
+
## 1.18.0 - 2026-05-04
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `get_hf_daily_papers` tool. Hugging Face Daily Papers (editor-curated AI/ML papers with community upvotes and discussion counts). Different signal from `get_arxiv_recent` and `get_ai_papers_trending`. Optional `limit` arg. Free.
|
|
19
|
+
|
|
20
|
+
## 1.17.0 - 2026-05-04
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `get_openrouter_models` tool. OpenRouter cross-provider model catalog (200+ models across 50+ inference providers) with comparable per-token pricing, context window, modality, and provider metadata. Optional `namespace` filter, `free_only` flag for free-tier models, `cheapest` sort by ascending input price, and `limit` for the rendered list. Companion to `get_model_pricing` (curated frontier-lab catalog) by surfacing the long tail of OSS models on cloud inference. Free.
|
|
24
|
+
|
|
25
|
+
## 1.16.0 - 2026-05-04
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- `get_reddit_trending` tool. Currently-hot threads from 7 AI-relevant subreddits (LocalLLaMA, MachineLearning, ClaudeAI, OpenAI, singularity, artificial, AI_Agents), ranked by score, top 30. Optional `subreddit` arg to filter to one sub, optional `limit` for the rendered list. Companion to `get_hot_issues`: GitHub developer conversation vs Reddit community conversation. Titles pass through the worker's prompt-injection sanitizer at capture time. Free.
|
|
29
|
+
|
|
30
|
+
## 1.15.0 - 2026-05-04
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- `get_hf_trending` tool now covers Hugging Face Spaces in addition to models and datasets. Spaces are ranked by likes (the right signal for hosted apps where downloads is meaningless) and the rendered entry shows id, sdk, likes, runtime stage when available, and hardware tier when available. New `section` enum values: `'spaces'` (just spaces), `'all'` (all three sections, the new default). `'both'` continues to mean models+datasets for backward compat. Output also surfaces top Space SDKs (gradio / streamlit / docker / static) in the header.
|
|
34
|
+
|
|
35
|
+
## 1.14.0 - 2026-05-04
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- `get_hot_issues` tool. Currently-hot GitHub issues across the AI ecosystem, ranked by comment count. Five fan-out search queries on AI topics (llm, ai-agents, large-language-models, machine-learning, transformer), filtered to is:issue is:open archived:false comments>=10 with activity in the last 7 days, deduped, top 30. Optional `limit` arg for the rendered list. Free.
|
|
39
|
+
|
|
40
|
+
Companion to `get_trending_repos`: that surfaces repos gaining stars, this surfaces repos where the active conversations are happening.
|
|
41
|
+
|
|
5
42
|
## 1.13.0 - 2026-05-04
|
|
6
43
|
|
|
7
44
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -775,13 +775,13 @@ registerTool('get_arxiv_recent', 'Most recent arXiv submissions in cs.AI / cs.LG
|
|
|
775
775
|
};
|
|
776
776
|
});
|
|
777
777
|
// ── Tool: get_hf_trending (free) ────────────────────────────────────
|
|
778
|
-
registerTool('get_hf_trending', 'Top
|
|
779
|
-
section: z.enum(['models', 'datasets', 'both']).optional().describe('Which to include in the rendered output (default both
|
|
778
|
+
registerTool('get_hf_trending', 'Top Hugging Face models, datasets, and Spaces. Models + datasets ranked by downloads (top 30 each); Spaces ranked by likes (top 30, since downloads is meaningless for hosted apps). Captured daily at 12:00 UTC against the public HF API (no auth). Each model entry carries id, downloads, likes, pipeline_tag, tags, lastModified, private, gated. Each space entry carries id, author, sdk (gradio/streamlit/static/docker), likes, tags, lastModified, private, runtime_stage, hardware. Use this when an agent wants the current shape of the open model + dataset + app ecosystem. Once enough daily snapshots accumulate, day-over-day deltas become a real trending signal. Free, no auth.', {
|
|
779
|
+
section: z.enum(['models', 'datasets', 'spaces', 'all', 'both']).optional().describe('Which to include in the rendered output (default all). "both" is accepted as alias for "models+datasets" for backward compat.'),
|
|
780
780
|
limit: z.number().min(1).max(30).optional().describe('Max items per section (default 15, max 30)'),
|
|
781
781
|
}, async ({ section, limit }) => {
|
|
782
782
|
const data = (await fetchJSON('/hf/trending'));
|
|
783
783
|
const s = data.snapshot;
|
|
784
|
-
const sec = section ?? '
|
|
784
|
+
const sec = section ?? 'all';
|
|
785
785
|
const n = Math.min(limit ?? 15, 30);
|
|
786
786
|
const fmtCount = (x) => x >= 1_000_000 ? `${(x / 1_000_000).toFixed(1)}M` : x >= 1_000 ? `${(x / 1_000).toFixed(1)}K` : String(x);
|
|
787
787
|
const modelLines = s.models.items.slice(0, n).map((m, i) => {
|
|
@@ -789,20 +789,298 @@ registerTool('get_hf_trending', 'Top 30 most-downloaded models and top 30 most-d
|
|
|
789
789
|
return `${i + 1}. ${m.id}${tag} ${fmtCount(m.downloads)} downloads, ${fmtCount(m.likes)} likes`;
|
|
790
790
|
}).join('\n');
|
|
791
791
|
const datasetLines = s.datasets.items.slice(0, n).map((d, i) => `${i + 1}. ${d.id} ${fmtCount(d.downloads)} downloads, ${fmtCount(d.likes)} likes`).join('\n');
|
|
792
|
+
const spaceLines = (s.spaces?.items ?? []).slice(0, n).map((sp, i) => {
|
|
793
|
+
const sdk = sp.sdk ? ` [${sp.sdk}]` : '';
|
|
794
|
+
const hw = sp.hardware ? `, hw: ${sp.hardware}` : '';
|
|
795
|
+
const stage = sp.runtime_stage ? `, ${sp.runtime_stage.toLowerCase()}` : '';
|
|
796
|
+
return `${i + 1}. ${sp.id}${sdk} ${fmtCount(sp.likes)} likes${stage}${hw}`;
|
|
797
|
+
}).join('\n');
|
|
792
798
|
const pipelines = s.summary.top_pipeline_tags.slice(0, 5).map(t => `${t.tag} (${t.count})`).join(', ');
|
|
793
799
|
const namespaces = s.summary.top_namespaces.slice(0, 5).map(n => `${n.namespace} (${n.count})`).join(', ');
|
|
794
|
-
|
|
800
|
+
const sdks = (s.summary.top_space_sdks ?? []).slice(0, 5).map(t => `${t.sdk} (${t.count})`).join(', ');
|
|
801
|
+
let text = `Hugging Face top assets. ${s.date}.\n`;
|
|
795
802
|
if (pipelines)
|
|
796
803
|
text += `Top pipeline tags (models): ${pipelines}\n`;
|
|
804
|
+
if (sdks)
|
|
805
|
+
text += `Top Space SDKs: ${sdks}\n`;
|
|
797
806
|
if (namespaces)
|
|
798
807
|
text += `Top namespaces: ${namespaces}\n`;
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
808
|
+
const includeModels = sec === 'models' || sec === 'all' || sec === 'both';
|
|
809
|
+
const includeDatasets = sec === 'datasets' || sec === 'all' || sec === 'both';
|
|
810
|
+
const includeSpaces = sec === 'spaces' || sec === 'all';
|
|
811
|
+
if (includeModels)
|
|
812
|
+
text += `\nModels (by downloads):\n${modelLines}\n`;
|
|
813
|
+
if (includeDatasets)
|
|
814
|
+
text += `\nDatasets (by downloads):\n${datasetLines}\n`;
|
|
815
|
+
if (includeSpaces)
|
|
816
|
+
text += `\nSpaces (by likes):\n${spaceLines || ' (none returned)'}\n`;
|
|
803
817
|
text += `\nSource: huggingface.co. Captured ${s.capturedAt}.`;
|
|
804
818
|
return { content: [{ type: 'text', text }] };
|
|
805
819
|
});
|
|
820
|
+
// ── Tool: get_hot_issues (free) ─────────────────────────────────────
|
|
821
|
+
registerTool('get_hot_issues', 'Currently-hot GitHub issues across the AI ecosystem. Five fan-out keyword-phrase searches ("large language model", "AI agent", transformer, "machine learning", LLM) for is:issue is:open archived:false comments>=10 updated within the last 7 days. Deduped by URL, top 30 by comment count. Each issue carries url, repo, number, title, author, state, comments, reactions_total, labels, created_at, updated_at, and matched_topic (the keyword phrase that found it). Refreshed daily at 12:30 UTC. Companion to get_trending_repos: that shows which AI repos are gaining stars, this shows where the active conversations are. Free, no auth.', {
|
|
822
|
+
limit: z.number().min(1).max(30).optional().describe('Max issues to render (default 15, max 30)'),
|
|
823
|
+
}, async ({ limit }) => {
|
|
824
|
+
const data = (await fetchJSON('/issues/hot'));
|
|
825
|
+
const s = data.snapshot;
|
|
826
|
+
const cap = Math.min(limit ?? 15, s.issues.length);
|
|
827
|
+
const lines = s.issues.slice(0, cap).map((i, n) => {
|
|
828
|
+
const meta = `${i.comments} comments, ${i.reactions_total} reactions [${i.matched_topic}]`;
|
|
829
|
+
const author = i.author ? ` by ${i.author}` : '';
|
|
830
|
+
const updated = i.updated_at ? ` updated ${i.updated_at.slice(0, 10)}` : '';
|
|
831
|
+
return `${n + 1}. ${i.repo}: ${i.title}\n ${meta}${updated}${author}\n ${i.url}`;
|
|
832
|
+
}).join('\n\n');
|
|
833
|
+
const repos = s.summary.top_repos.slice(0, 5).map(r => `${r.repo} (${r.count})`).join(', ');
|
|
834
|
+
return {
|
|
835
|
+
content: [
|
|
836
|
+
{
|
|
837
|
+
type: 'text',
|
|
838
|
+
text: `Hot AI GitHub issues. ${s.date}, showing ${cap} of ${s.total_issues}.\n` +
|
|
839
|
+
`Window: last ${s.recent_window_days} days, comments threshold ${s.comments_threshold}.\n` +
|
|
840
|
+
(repos ? `Most-active repos: ${repos}\n\n` : '\n') +
|
|
841
|
+
lines +
|
|
842
|
+
`\n\nSource: GitHub Search API. Captured ${s.capturedAt}.`,
|
|
843
|
+
},
|
|
844
|
+
],
|
|
845
|
+
};
|
|
846
|
+
});
|
|
847
|
+
// ── Tool: get_reddit_trending (free) ────────────────────────────────
|
|
848
|
+
registerTool('get_reddit_trending', 'Currently-hot threads in 7 AI-relevant subreddits (LocalLLaMA, MachineLearning, ClaudeAI, OpenAI, singularity, artificial, AI_Agents). Stickied and NSFW posts filtered, deduped by post id, ranked by score, top 30. Each post carries id, subreddit, title, author, score, upvote_ratio, num_comments, permalink, url, created_utc, flair, is_self, is_video. Refreshed daily at 13:00 UTC. Companion to get_hot_issues: that surfaces developer conversation on GitHub, this surfaces community conversation on Reddit. Titles are sanitized at capture time against prompt-injection tokens. Free, no auth.', {
|
|
849
|
+
subreddit: z.string().optional().describe('Optional filter to a single subreddit name (e.g. "LocalLLaMA"). Case-insensitive. If absent, returns posts from all 7.'),
|
|
850
|
+
limit: z.number().min(1).max(30).optional().describe('Max posts to render (default 15, max 30)'),
|
|
851
|
+
}, async ({ subreddit, limit }) => {
|
|
852
|
+
const data = (await fetchJSON('/reddit/trending'));
|
|
853
|
+
const s = data.snapshot;
|
|
854
|
+
const subFilter = subreddit?.trim().toLowerCase();
|
|
855
|
+
const filtered = subFilter
|
|
856
|
+
? s.posts.filter(p => p.subreddit.toLowerCase() === subFilter)
|
|
857
|
+
: s.posts;
|
|
858
|
+
const cap = Math.min(limit ?? 15, filtered.length);
|
|
859
|
+
const fmtCount = (x) => x >= 1_000 ? `${(x / 1_000).toFixed(1)}K` : String(x);
|
|
860
|
+
const lines = filtered.slice(0, cap).map((p, i) => {
|
|
861
|
+
const author = p.author ? ` by ${p.author}` : '';
|
|
862
|
+
const flair = p.flair ? ` [${p.flair}]` : '';
|
|
863
|
+
return `${i + 1}. r/${p.subreddit}: ${p.title}${flair}\n ${fmtCount(p.score)} pts, ${fmtCount(p.num_comments)} comments${author}\n ${p.permalink}`;
|
|
864
|
+
}).join('\n\n');
|
|
865
|
+
const subs = Object.entries(s.summary.by_subreddit)
|
|
866
|
+
.sort((a, b) => b[1] - a[1])
|
|
867
|
+
.slice(0, 5)
|
|
868
|
+
.map(([sub, n]) => `r/${sub} (${n})`)
|
|
869
|
+
.join(', ');
|
|
870
|
+
let text = `Hot AI Reddit threads. ${s.date}, showing ${cap} of ${filtered.length}`;
|
|
871
|
+
if (subFilter)
|
|
872
|
+
text += ` (filtered to r/${subreddit})`;
|
|
873
|
+
text += '.\n';
|
|
874
|
+
if (subs && !subFilter)
|
|
875
|
+
text += `Most-active subreddits: ${subs}\n`;
|
|
876
|
+
text += '\n' + (lines || ' (no posts)');
|
|
877
|
+
text += `\n\nSource: reddit.com. Captured ${s.capturedAt}.`;
|
|
878
|
+
return { content: [{ type: 'text', text }] };
|
|
879
|
+
});
|
|
880
|
+
// ── Tool: get_openrouter_models (free) ──────────────────────────────
|
|
881
|
+
registerTool('get_openrouter_models', 'OpenRouter cross-provider model catalog (200+ models normalized across 50+ inference providers). Each entry has per-token pricing (prompt + completion + image + request), context window, modality (e.g. text+image->text), instruct_type, tokenizer, top provider metadata (max_completion_tokens, moderation flag), and supported_parameters. Use this to find the long tail of OSS models on cloud inference, including the cheapest model for a given workload, the model with the largest context window, or the free-tier set. Pairs with get_model_pricing (curated frontier-lab catalog). Refreshed daily at 14:00 UTC. Free, no auth.', {
|
|
882
|
+
namespace: z.string().optional().describe('Optional filter to a single provider namespace (e.g. "anthropic", "openai", "meta-llama"). Case-insensitive.'),
|
|
883
|
+
free_only: z.boolean().optional().describe('If true, return only models with prompt+completion both 0 (free-tier).'),
|
|
884
|
+
cheapest: z.boolean().optional().describe('If true, sort by cheapest input price ascending. Free-tier models excluded from this sort.'),
|
|
885
|
+
limit: z.number().min(1).max(50).optional().describe('Max models to render (default 20, max 50)'),
|
|
886
|
+
}, async ({ namespace, free_only, cheapest, limit }) => {
|
|
887
|
+
const data = (await fetchJSON('/openrouter/models'));
|
|
888
|
+
const s = data.snapshot;
|
|
889
|
+
const ns = namespace?.trim().toLowerCase();
|
|
890
|
+
let list = s.models;
|
|
891
|
+
if (ns)
|
|
892
|
+
list = list.filter(m => m.id.toLowerCase().startsWith(ns + '/'));
|
|
893
|
+
if (free_only)
|
|
894
|
+
list = list.filter(m => m.pricing.prompt === 0 && m.pricing.completion === 0);
|
|
895
|
+
if (cheapest) {
|
|
896
|
+
list = list
|
|
897
|
+
.filter(m => typeof m.pricing.prompt === 'number' && m.pricing.prompt > 0)
|
|
898
|
+
.sort((a, b) => (a.pricing.prompt ?? 0) - (b.pricing.prompt ?? 0));
|
|
899
|
+
}
|
|
900
|
+
const cap = Math.min(limit ?? 20, list.length);
|
|
901
|
+
const fmtPrice = (p) => p === null ? '?' : p === 0 ? 'free' : `$${(p * 1_000_000).toFixed(2)}/Mtok`;
|
|
902
|
+
const fmtCtx = (c) => c === null ? 'unknown' : c >= 1_000_000 ? `${(c / 1_000_000).toFixed(0)}M` : `${(c / 1_000).toFixed(0)}K`;
|
|
903
|
+
const lines = list.slice(0, cap).map((m, i) => {
|
|
904
|
+
const modality = m.modality ? ` ${m.modality}` : '';
|
|
905
|
+
return `${i + 1}. ${m.id}${modality}\n in ${fmtPrice(m.pricing.prompt)}, out ${fmtPrice(m.pricing.completion)}, ctx ${fmtCtx(m.context_length)}`;
|
|
906
|
+
}).join('\n\n');
|
|
907
|
+
const namespaces = s.summary.by_namespace.slice(0, 5).map(n => `${n.namespace} (${n.count})`).join(', ');
|
|
908
|
+
let header = `OpenRouter catalog. ${s.date}. ${s.total_models} models total, ${list.length} after filters, showing ${cap}.\n`;
|
|
909
|
+
header += `Top provider namespaces: ${namespaces}\n`;
|
|
910
|
+
if (s.summary.cheapest_input)
|
|
911
|
+
header += `Cheapest input (paid): ${s.summary.cheapest_input.id} at $${s.summary.cheapest_input.usd_per_million.toFixed(2)}/Mtok\n`;
|
|
912
|
+
if (s.summary.largest_context)
|
|
913
|
+
header += `Largest context: ${s.summary.largest_context.id} at ${fmtCtx(s.summary.largest_context.tokens)} tokens\n`;
|
|
914
|
+
header += `Free-tier models available: ${s.summary.free_tier_count}\n`;
|
|
915
|
+
return {
|
|
916
|
+
content: [
|
|
917
|
+
{
|
|
918
|
+
type: 'text',
|
|
919
|
+
text: header + '\n' + (lines || ' (no models match the filters)') + `\n\nSource: openrouter.ai. Captured ${s.capturedAt}.`,
|
|
920
|
+
},
|
|
921
|
+
],
|
|
922
|
+
};
|
|
923
|
+
});
|
|
924
|
+
// ── Tool: get_hf_daily_papers (free) ────────────────────────────────
|
|
925
|
+
registerTool('get_hf_daily_papers', 'Hugging Face Daily Papers: editor-curated AI/ML papers from huggingface.co/papers, layered with community upvotes and discussion counts. Different signal from get_arxiv_recent (firehose) and get_ai_papers_trending (citation-ranked all-time): this is editor picks of-the-day with engagement signal. Each paper carries paperId, title, summary, authors, upvotes, num_comments, hf_url, arxiv_url (when applicable), github_repo, github_stars, ai_keywords. Refreshed daily at 14:15 UTC. Titles sanitized at capture time. Free, no auth.', {
|
|
926
|
+
limit: z.number().min(1).max(30).optional().describe('Max papers to render (default 15, max 30)'),
|
|
927
|
+
}, async ({ limit }) => {
|
|
928
|
+
const data = (await fetchJSON('/papers/hf-daily'));
|
|
929
|
+
const s = data.snapshot;
|
|
930
|
+
const cap = Math.min(limit ?? 15, s.papers.length);
|
|
931
|
+
const lines = s.papers.slice(0, cap).map((p, i) => {
|
|
932
|
+
const authors = p.authors.slice(0, 3).join(', ') + (p.authors.length > 3 ? ' et al.' : '');
|
|
933
|
+
const kw = p.ai_keywords.length > 0 ? `\n keywords: ${p.ai_keywords.slice(0, 5).join(', ')}` : '';
|
|
934
|
+
const arxiv = p.arxiv_url ? `\n arxiv: ${p.arxiv_url}` : '';
|
|
935
|
+
return `${i + 1}. ${p.title}\n ${authors}\n ${p.upvotes} upvotes, ${p.num_comments} comments\n ${p.hf_url}${arxiv}${kw}`;
|
|
936
|
+
}).join('\n\n');
|
|
937
|
+
const kw = s.summary.by_keyword.slice(0, 5).map(k => `${k.keyword} (${k.count})`).join(', ');
|
|
938
|
+
let text = `HF Daily Papers (editor-curated). ${s.date}, showing ${cap} of ${s.total_papers}.\n`;
|
|
939
|
+
if (kw)
|
|
940
|
+
text += `Top keywords: ${kw}\n`;
|
|
941
|
+
if (s.summary.most_upvoted)
|
|
942
|
+
text += `Most upvoted: "${s.summary.most_upvoted.title}" (${s.summary.most_upvoted.upvotes})\n`;
|
|
943
|
+
text += '\n' + lines + `\n\nSource: huggingface.co/papers. Captured ${s.capturedAt}.`;
|
|
944
|
+
return { content: [{ type: 'text', text }] };
|
|
945
|
+
});
|
|
946
|
+
// ── Tool: get_ai_ecosystem_today (free, composite) ──────────────────
|
|
947
|
+
// Single fetch to /api/today which fans out worker-side. The previous
|
|
948
|
+
// implementation made 9 separate fetches per agent invocation; now
|
|
949
|
+
// the worker's edge cache absorbs the load and serves one cached
|
|
950
|
+
// response to all concurrent agents. Output stays text-rendered
|
|
951
|
+
// (MCP returns text content) but the wire shape is the structured
|
|
952
|
+
// JSON the worker produces.
|
|
953
|
+
registerTool('get_ai_ecosystem_today', 'One-shot composite AI morning brief. Calls /api/today which fans out across all daily TensorFeed feeds (news, 3 paper feeds, HF models/datasets/Spaces, hot GitHub issues, Reddit threads, OpenRouter catalog summary, provider status) and returns a single synthesized text response. Optional sections filter (any subset of news, papers, hf, community, inference, status) and limit_per_section (1-10, default 3). Captured-at timestamps from each underlying snapshot are surfaced so the agent knows recency. Free, no auth.', {
|
|
954
|
+
sections: z.array(z.enum([
|
|
955
|
+
'news', 'papers', 'hf', 'community', 'inference', 'status',
|
|
956
|
+
])).optional().describe('Which subsections to include (default: all six). "papers" covers all 3 paper feeds; "hf" covers models + datasets + spaces; "community" covers hot GitHub issues + Reddit threads; "inference" is the OpenRouter catalog summary.'),
|
|
957
|
+
limit_per_section: z.number().min(1).max(10).optional().describe('Max items per subsection (default 3, max 10). The output is intentionally terse so an agent can call this without burning context.'),
|
|
958
|
+
}, async ({ sections, limit_per_section }) => {
|
|
959
|
+
const params = new URLSearchParams();
|
|
960
|
+
if (sections && sections.length > 0)
|
|
961
|
+
params.set('sections', sections.join(','));
|
|
962
|
+
if (typeof limit_per_section === 'number')
|
|
963
|
+
params.set('limit', String(limit_per_section));
|
|
964
|
+
const path = `/today${params.toString() ? `?${params.toString()}` : ''}`;
|
|
965
|
+
let brief;
|
|
966
|
+
try {
|
|
967
|
+
brief = (await fetchJSON(path));
|
|
968
|
+
}
|
|
969
|
+
catch (err) {
|
|
970
|
+
return {
|
|
971
|
+
content: [{ type: 'text', text: `Failed to fetch /api/today: ${err.message}` }],
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
const fmtCount = (x) => x >= 1_000_000 ? `${(x / 1_000_000).toFixed(1)}M`
|
|
975
|
+
: x >= 1_000 ? `${(x / 1_000).toFixed(1)}K`
|
|
976
|
+
: String(x);
|
|
977
|
+
const sectionTexts = [];
|
|
978
|
+
if (brief.news.available && brief.news.data && brief.news.data.items.length > 0) {
|
|
979
|
+
const lines = brief.news.data.items.map((a, i) => ` ${i + 1}. ${a.title} (${a.source})\n ${a.url}`).join('\n');
|
|
980
|
+
sectionTexts.push(`📰 NEWS\n${lines}`);
|
|
981
|
+
}
|
|
982
|
+
if (brief.papers.available && brief.papers.data) {
|
|
983
|
+
const bits = [];
|
|
984
|
+
const ai = brief.papers.data.ai_trending;
|
|
985
|
+
if (ai.available && ai.data && ai.data.items.length > 0) {
|
|
986
|
+
const lines = ai.data.items.map((p, i) => {
|
|
987
|
+
const authors = p.authors.slice(0, 2).join(', ') + (p.authors.length > 2 ? ' et al.' : '');
|
|
988
|
+
const venue = p.venue ? ` ${p.venue}.` : '';
|
|
989
|
+
return ` ${i + 1}. ${p.title}${venue} ${authors}. ${fmtCount(p.citationCount)} citations`;
|
|
990
|
+
}).join('\n');
|
|
991
|
+
bits.push(` By citation count (Semantic Scholar):\n${lines}`);
|
|
992
|
+
}
|
|
993
|
+
const ax = brief.papers.data.arxiv_recent;
|
|
994
|
+
if (ax.available && ax.data && ax.data.items.length > 0) {
|
|
995
|
+
const lines = ax.data.items.map((p, i) => {
|
|
996
|
+
const date = p.publishedAt ? p.publishedAt.slice(0, 10) : '';
|
|
997
|
+
const authors = p.authors.slice(0, 2).join(', ') + (p.authors.length > 2 ? ' et al.' : '');
|
|
998
|
+
return ` ${i + 1}. ${p.title} (arXiv:${p.arxivId}, ${date}). ${authors}`;
|
|
999
|
+
}).join('\n');
|
|
1000
|
+
bits.push(` Just submitted (arXiv recent):\n${lines}`);
|
|
1001
|
+
}
|
|
1002
|
+
const hd = brief.papers.data.hf_daily;
|
|
1003
|
+
if (hd.available && hd.data && hd.data.items.length > 0) {
|
|
1004
|
+
const lines = hd.data.items.map((p, i) => ` ${i + 1}. ${p.title} (${p.upvotes} upvotes, ${p.num_comments} comments)`).join('\n');
|
|
1005
|
+
bits.push(` HF editor picks today:\n${lines}`);
|
|
1006
|
+
}
|
|
1007
|
+
if (bits.length > 0)
|
|
1008
|
+
sectionTexts.push(`📄 PAPERS\n${bits.join('\n')}`);
|
|
1009
|
+
}
|
|
1010
|
+
if (brief.hf.available && brief.hf.data) {
|
|
1011
|
+
const bits = [];
|
|
1012
|
+
const { models, datasets, spaces } = brief.hf.data;
|
|
1013
|
+
if (models.length > 0) {
|
|
1014
|
+
bits.push(` Models (top by downloads):\n` + models.map((x, i) => ` ${i + 1}. ${x.id} (${fmtCount(x.downloads)} downloads)`).join('\n'));
|
|
1015
|
+
}
|
|
1016
|
+
if (datasets.length > 0) {
|
|
1017
|
+
bits.push(` Datasets (top by downloads):\n` + datasets.map((x, i) => ` ${i + 1}. ${x.id} (${fmtCount(x.downloads)} downloads)`).join('\n'));
|
|
1018
|
+
}
|
|
1019
|
+
if (spaces.length > 0) {
|
|
1020
|
+
bits.push(` Spaces (top by likes):\n` + spaces.map((x, i) => ` ${i + 1}. ${x.id} (${fmtCount(x.likes)} likes)`).join('\n'));
|
|
1021
|
+
}
|
|
1022
|
+
if (bits.length > 0)
|
|
1023
|
+
sectionTexts.push(`🤗 HUGGING FACE\n${bits.join('\n')}`);
|
|
1024
|
+
}
|
|
1025
|
+
if (brief.community.available && brief.community.data) {
|
|
1026
|
+
const bits = [];
|
|
1027
|
+
const { github_issues, reddit } = brief.community.data;
|
|
1028
|
+
if (github_issues.length > 0) {
|
|
1029
|
+
const lines = github_issues.map((it, i) => ` ${i + 1}. ${it.repo}: ${it.title} (${it.comments} comments)\n ${it.url}`).join('\n');
|
|
1030
|
+
bits.push(` GitHub hot issues:\n${lines}`);
|
|
1031
|
+
}
|
|
1032
|
+
if (reddit.length > 0) {
|
|
1033
|
+
const lines = reddit.map((p, i) => ` ${i + 1}. r/${p.subreddit}: ${p.title} (${p.score} pts, ${p.num_comments} comments)\n ${p.permalink}`).join('\n');
|
|
1034
|
+
bits.push(` Reddit hot threads:\n${lines}`);
|
|
1035
|
+
}
|
|
1036
|
+
if (bits.length > 0)
|
|
1037
|
+
sectionTexts.push(`💬 COMMUNITY\n${bits.join('\n')}`);
|
|
1038
|
+
}
|
|
1039
|
+
if (brief.inference.available && brief.inference.data) {
|
|
1040
|
+
const d = brief.inference.data;
|
|
1041
|
+
const lines = [];
|
|
1042
|
+
lines.push(` ${d.total_models} models across providers (OpenRouter)`);
|
|
1043
|
+
if (d.cheapest_input)
|
|
1044
|
+
lines.push(` Cheapest input: ${d.cheapest_input.id} at $${d.cheapest_input.usd_per_million.toFixed(2)}/Mtok`);
|
|
1045
|
+
if (d.largest_context)
|
|
1046
|
+
lines.push(` Largest context: ${d.largest_context.id} at ${fmtCount(d.largest_context.tokens)} tokens`);
|
|
1047
|
+
lines.push(` Free-tier models available: ${d.free_tier_count}`);
|
|
1048
|
+
if (d.top_namespaces.length > 0) {
|
|
1049
|
+
lines.push(` Top namespaces: ${d.top_namespaces.map(n => `${n.namespace} (${n.count})`).join(', ')}`);
|
|
1050
|
+
}
|
|
1051
|
+
sectionTexts.push(`⚙️ INFERENCE CATALOG\n${lines.join('\n')}`);
|
|
1052
|
+
}
|
|
1053
|
+
if (brief.status.available && brief.status.data) {
|
|
1054
|
+
const d = brief.status.data;
|
|
1055
|
+
if (d.all_operational) {
|
|
1056
|
+
sectionTexts.push(`✓ STATUS: all tracked AI providers operational (${d.service_count} services)`);
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
const lines = d.issues.map(s => ` ${s.name} (${s.provider}): ${s.status.toUpperCase()}`).join('\n');
|
|
1060
|
+
sectionTexts.push(`⚠️ STATUS\n${lines}`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (sectionTexts.length === 0) {
|
|
1064
|
+
return {
|
|
1065
|
+
content: [
|
|
1066
|
+
{
|
|
1067
|
+
type: 'text',
|
|
1068
|
+
text: 'No data available yet. The daily snapshots have not run, or all upstream feeds errored. Try again in a few minutes.',
|
|
1069
|
+
},
|
|
1070
|
+
],
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
const header = `TensorFeed AI ecosystem brief, generated ${brief.generated_at}`;
|
|
1074
|
+
const body = sectionTexts.join('\n\n');
|
|
1075
|
+
return {
|
|
1076
|
+
content: [
|
|
1077
|
+
{
|
|
1078
|
+
type: 'text',
|
|
1079
|
+
text: `${header}\n\n${body}\n\nFor any subsection, call the underlying tool directly: get_ai_news, get_ai_papers_trending, get_arxiv_recent, get_hf_daily_papers, get_hf_trending, get_hot_issues, get_reddit_trending, get_openrouter_models, get_ai_status.`,
|
|
1080
|
+
},
|
|
1081
|
+
],
|
|
1082
|
+
};
|
|
1083
|
+
});
|
|
806
1084
|
// ── Start ───────────────────────────────────────────────────────────
|
|
807
1085
|
async function main() {
|
|
808
1086
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tensorfeed/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"mcpName": "ai.tensorfeed/mcp-server",
|
|
5
5
|
"description": "MCP server for TensorFeed.ai - AI news, status, model data, and premium endpoints (routing, cost projection, forecast, history series, news search, webhook watches with digest tier, enriched directory) for AI agents",
|
|
6
6
|
"main": "dist/index.js",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "ai.tensorfeed/mcp-server",
|
|
4
4
|
"title": "TensorFeed",
|
|
5
5
|
"description": "AI news, model pricing, service status, and machine-payable premium tools. AFTA-certified.",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.20.0",
|
|
7
7
|
"websiteUrl": "https://tensorfeed.ai/developers/agent-payments",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/RipperMercs/tensorfeed-mcp",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
16
16
|
"identifier": "@tensorfeed/mcp-server",
|
|
17
|
-
"version": "1.
|
|
17
|
+
"version": "1.20.0",
|
|
18
18
|
"runtimeHint": "npx",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|
|
@@ -47,7 +47,12 @@
|
|
|
47
47
|
"probe_latest",
|
|
48
48
|
"get_ai_papers_trending",
|
|
49
49
|
"get_arxiv_recent",
|
|
50
|
-
"get_hf_trending"
|
|
50
|
+
"get_hf_trending",
|
|
51
|
+
"get_hot_issues",
|
|
52
|
+
"get_reddit_trending",
|
|
53
|
+
"get_openrouter_models",
|
|
54
|
+
"get_hf_daily_papers",
|
|
55
|
+
"get_ai_ecosystem_today"
|
|
51
56
|
],
|
|
52
57
|
"premiumTools": [
|
|
53
58
|
"premium_routing",
|