@tensorfeed/mcp-server 1.35.0 → 1.36.1
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 +40 -0
- package/dist/index.js +210 -24
- package/dist/sanitize.d.ts +4 -2
- package/dist/sanitize.js +0 -0
- package/package.json +1 -1
- package/server.json +10 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
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.36.1 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
Security hardening only. No API changes; agents see the same tools.
|
|
8
|
+
|
|
9
|
+
- The `latest_news` resource output is now sanitized like every tool
|
|
10
|
+
output, so external RSS titles cannot carry prompt-injection markers
|
|
11
|
+
to the model.
|
|
12
|
+
- Tool output is capped at 40000 characters and the x402 publishers and
|
|
13
|
+
agent reputation renderers are bounded, so a large upstream response
|
|
14
|
+
cannot overflow the agent's context window.
|
|
15
|
+
- Path parameters (watch id, company ticker) are now URL-encoded and
|
|
16
|
+
schema-validated before they reach a request path.
|
|
17
|
+
- Reconciled the stated credit cost on the free pricing, benchmark, and
|
|
18
|
+
status series pointers (they correctly read 2 credits now).
|
|
19
|
+
|
|
20
|
+
## 1.36.0 - 2026-05-28
|
|
21
|
+
|
|
22
|
+
Adds 6 tools wrapping the x402 settlement index endpoints (Wave 20)
|
|
23
|
+
that shipped earlier today as the first-mover ecosystem-level index
|
|
24
|
+
of x402 USDC settlements on Base mainnet.
|
|
25
|
+
|
|
26
|
+
Free tools:
|
|
27
|
+
- `get_x402_summary`: ecosystem-level rollup across 24h/7d/30d window.
|
|
28
|
+
- `get_x402_publishers`: canonical list of x402-compliant publishers
|
|
29
|
+
TensorFeed is currently indexing, auto-discovered from
|
|
30
|
+
/.well-known/x402.json crawls.
|
|
31
|
+
- `get_x402_leaderboard`: top publishers by volume in the window.
|
|
32
|
+
- `get_x402_recent`: most recent settlement events newest-first.
|
|
33
|
+
|
|
34
|
+
Premium tools (1 credit, AFTA-signed):
|
|
35
|
+
- `premium_x402_publisher_receipts`: per-publisher feed with daily
|
|
36
|
+
series + avg_amount + attribution.
|
|
37
|
+
- `premium_x402_series`: time-series of ecosystem or per-publisher
|
|
38
|
+
volume / count across a date range.
|
|
39
|
+
|
|
40
|
+
Each premium tool's description includes an explicit VS FREE
|
|
41
|
+
differentiator (per the upsell-copy memory): Anthropic-side LLMs
|
|
42
|
+
repeat tool descriptions verbatim when deciding whether to recommend
|
|
43
|
+
paying.
|
|
44
|
+
|
|
5
45
|
## 1.33.0 - 2026-05-25 (AI-CVE intelligence tools, unified versioning)
|
|
6
46
|
|
|
7
47
|
Versions across npm + MCP Registry + MCPB manifest are now all 1.33.0
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,13 @@ import { z } from 'zod';
|
|
|
5
5
|
import { readFileSync } from 'node:fs';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
|
-
import { sanitizeToolResponse } from './sanitize.js';
|
|
8
|
+
import { sanitizeToolResponse, sanitizeForLLM } from './sanitize.js';
|
|
9
9
|
const API_BASE = 'https://tensorfeed.ai/api';
|
|
10
|
+
// Per-request upstream timeout. fetchJSON is the single HTTP helper behind every
|
|
11
|
+
// MCP tool and the latest_news resource, so a missing timeout would let one
|
|
12
|
+
// stalled upstream hang an agent's tool call. 20s is generous for the slowest
|
|
13
|
+
// premium endpoint while still failing fast on a genuine stall.
|
|
14
|
+
const REQUEST_TIMEOUT_MS = 20000;
|
|
10
15
|
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
11
16
|
const SDK_VERSION = JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
|
|
12
17
|
async function fetchJSON(path, opts = {}) {
|
|
@@ -23,11 +28,28 @@ async function fetchJSON(path, opts = {}) {
|
|
|
23
28
|
}
|
|
24
29
|
headers['Authorization'] = `Bearer ${token}`;
|
|
25
30
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
// Bound every request with a timeout so a stalled upstream (TLS or connect
|
|
32
|
+
// hang, slow Worker, KV or origin stall) cannot hang the agent's tool call
|
|
33
|
+
// indefinitely. An aborted fetch surfaces as a clean Error below instead of
|
|
34
|
+
// an open-ended await.
|
|
35
|
+
let res;
|
|
36
|
+
try {
|
|
37
|
+
res = await fetch(`${API_BASE}${path}`, {
|
|
38
|
+
method: opts.method ?? 'GET',
|
|
39
|
+
headers,
|
|
40
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
41
|
+
...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
// AbortSignal.timeout fires a TimeoutError (DOMException name 'TimeoutError');
|
|
46
|
+
// a manual abort surfaces as 'AbortError'. Either way, rethrow a clean Error
|
|
47
|
+
// so it reaches the agent as a normal MCP tool error rather than a raw abort.
|
|
48
|
+
if (err instanceof Error && (err.name === 'TimeoutError' || err.name === 'AbortError')) {
|
|
49
|
+
throw new Error(`TensorFeed upstream request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
31
53
|
if (!res.ok) {
|
|
32
54
|
let errPayload;
|
|
33
55
|
try {
|
|
@@ -129,6 +151,17 @@ server.registerResource('latest_news', 'tensorfeed://news/latest', {
|
|
|
129
151
|
mimeType: 'application/json',
|
|
130
152
|
}, async (uri) => {
|
|
131
153
|
const data = (await fetchJSON('/news?limit=20'));
|
|
154
|
+
// This resource is registered directly on the SDK, NOT through the
|
|
155
|
+
// registerTool wrapper, so its payload does not pass through
|
|
156
|
+
// sanitizeToolResponse. Scrub the external RSS title/snippet/source
|
|
157
|
+
// fields here so untrusted article text gets the same role-confusion
|
|
158
|
+
// and control-char scrub every tool output gets.
|
|
159
|
+
const articles = data.articles.map((a) => ({
|
|
160
|
+
...a,
|
|
161
|
+
title: sanitizeForLLM(a.title),
|
|
162
|
+
snippet: sanitizeForLLM(a.snippet),
|
|
163
|
+
source: sanitizeForLLM(a.source),
|
|
164
|
+
}));
|
|
132
165
|
return {
|
|
133
166
|
contents: [
|
|
134
167
|
{
|
|
@@ -137,8 +170,8 @@ server.registerResource('latest_news', 'tensorfeed://news/latest', {
|
|
|
137
170
|
text: JSON.stringify({
|
|
138
171
|
source: 'TensorFeed.ai',
|
|
139
172
|
fetched_at: new Date().toISOString(),
|
|
140
|
-
count:
|
|
141
|
-
articles
|
|
173
|
+
count: articles.length,
|
|
174
|
+
articles,
|
|
142
175
|
}, null, 2),
|
|
143
176
|
},
|
|
144
177
|
],
|
|
@@ -464,14 +497,27 @@ registerTool('get_agent_reputation_card', 'Get a TensorFeed Agent Reputation Bur
|
|
|
464
497
|
}
|
|
465
498
|
throw e;
|
|
466
499
|
}
|
|
467
|
-
// The card schema is rich;
|
|
468
|
-
//
|
|
469
|
-
|
|
500
|
+
// The card schema is rich; select the documented top-level fields and
|
|
501
|
+
// clamp any array so a large or inflated upstream card cannot blow the
|
|
502
|
+
// agent's context. Unknown fields still pass through (so the agent can
|
|
503
|
+
// reason over them) but every array is capped and the whole payload is
|
|
504
|
+
// bounded by the downstream sanitize char cap as a backstop.
|
|
505
|
+
const rawCard = data.card ?? data;
|
|
506
|
+
const MAX_CARD_ARRAY = 25;
|
|
507
|
+
const clamped = {};
|
|
508
|
+
for (const [k, v] of Object.entries(rawCard)) {
|
|
509
|
+
if (Array.isArray(v) && v.length > MAX_CARD_ARRAY) {
|
|
510
|
+
clamped[k] = [...v.slice(0, MAX_CARD_ARRAY), `...[${v.length - MAX_CARD_ARRAY} more omitted]`];
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
clamped[k] = v;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
470
516
|
return {
|
|
471
517
|
content: [
|
|
472
518
|
{
|
|
473
519
|
type: 'text',
|
|
474
|
-
text: `Agent reputation card for ${wallet ?? `token_prefix=${token_prefix}`}:\n\n${JSON.stringify(
|
|
520
|
+
text: `Agent reputation card for ${wallet ?? `token_prefix=${token_prefix}`}:\n\n${JSON.stringify(clamped, null, 2)}`,
|
|
475
521
|
},
|
|
476
522
|
],
|
|
477
523
|
};
|
|
@@ -539,7 +585,7 @@ registerTool('premium_routing', 'Get a ranked list of recommended AI models for
|
|
|
539
585
|
return { content: [{ type: 'text', text: `Routing for "${data.task}":\n\n${list}${billing}` }] };
|
|
540
586
|
});
|
|
541
587
|
// ── Tool: pricing_series_free (free, 7-day cap) ─────────────────────
|
|
542
|
-
registerTool('pricing_series_free', 'Daily price points for one AI model over the last 1 to 7 days, free. For windows up to 90 days use pricing_series (
|
|
588
|
+
registerTool('pricing_series_free', 'Daily price points for one AI model over the last 1 to 7 days, free. For windows up to 90 days use pricing_series (2 credits).', {
|
|
543
589
|
model: z.string().describe('Model id or display name (e.g. "Claude Opus 4.7" or "claude-opus-4-7")'),
|
|
544
590
|
days: z.number().min(1).max(7).optional().describe('Rolling window length 1 to 7 days (default 7)'),
|
|
545
591
|
}, async ({ model, days }) => {
|
|
@@ -555,13 +601,13 @@ registerTool('pricing_series_free', 'Daily price points for one AI model over th
|
|
|
555
601
|
content: [
|
|
556
602
|
{
|
|
557
603
|
type: 'text',
|
|
558
|
-
text: `${data.model} (${data.provider ?? 'unknown'}) ${data.points.length} points over ${data.points.length} days\n${summary}\nFor up to 90 days, use the pricing_series tool (
|
|
604
|
+
text: `${data.model} (${data.provider ?? 'unknown'}) ${data.points.length} points over ${data.points.length} days\n${summary}\nFor up to 90 days, use the pricing_series tool (2 credits).`,
|
|
559
605
|
},
|
|
560
606
|
],
|
|
561
607
|
};
|
|
562
608
|
});
|
|
563
609
|
// ── Tool: benchmark_series_free (free, 7-day cap) ───────────────────
|
|
564
|
-
registerTool('benchmark_series_free', 'Daily benchmark scores for one model+benchmark over the last 1 to 7 days, free. Benchmark keys: swe_bench, mmlu_pro, gpqa_diamond, math, human_eval. For windows up to 90 days use benchmark_series (
|
|
610
|
+
registerTool('benchmark_series_free', 'Daily benchmark scores for one model+benchmark over the last 1 to 7 days, free. Benchmark keys: swe_bench, mmlu_pro, gpqa_diamond, math, human_eval. For windows up to 90 days use benchmark_series (2 credits).', {
|
|
565
611
|
model: z.string().describe('Model id or display name'),
|
|
566
612
|
benchmark: z.string().describe('Benchmark key'),
|
|
567
613
|
days: z.number().min(1).max(7).optional().describe('Rolling window length 1 to 7 days (default 7)'),
|
|
@@ -584,7 +630,7 @@ registerTool('benchmark_series_free', 'Daily benchmark scores for one model+benc
|
|
|
584
630
|
};
|
|
585
631
|
});
|
|
586
632
|
// ── Tool: status_uptime_free (free, 7-day cap) ──────────────────────
|
|
587
|
-
registerTool('status_uptime_free', 'Daily uptime rollup for one provider over the last 1 to 7 days, free. For windows up to 90 days use status_uptime (
|
|
633
|
+
registerTool('status_uptime_free', 'Daily uptime rollup for one provider over the last 1 to 7 days, free. For windows up to 90 days use status_uptime (2 credits).', {
|
|
588
634
|
provider: z.string().describe('Provider name (e.g. anthropic, openai, google)'),
|
|
589
635
|
days: z.number().min(1).max(7).optional().describe('Rolling window length 1 to 7 days (default 7)'),
|
|
590
636
|
}, async ({ provider, days }) => {
|
|
@@ -627,7 +673,7 @@ registerTool('status_leaderboard_free', 'Cross-provider uptime leaderboard, rank
|
|
|
627
673
|
],
|
|
628
674
|
};
|
|
629
675
|
});
|
|
630
|
-
// ── Tool: pricing_series (
|
|
676
|
+
// ── Tool: pricing_series (2 credits) ────────────────────────────────
|
|
631
677
|
registerTool('pricing_series', 'Daily price points for one AI model with min/max/delta summary. Default range = last 30 days, max 90 days. Costs 2 credits ($0.04). Strict premium, no free trial; the 7-day-capped sibling pricing_series_free is the discovery option.', {
|
|
632
678
|
model: z.string().describe('Model id or display name (e.g. "Claude Opus 4.7" or "claude-opus-4-7")'),
|
|
633
679
|
from: z.string().optional().describe('Start date YYYY-MM-DD UTC (default: 30 days ago)'),
|
|
@@ -652,7 +698,7 @@ registerTool('pricing_series', 'Daily price points for one AI model with min/max
|
|
|
652
698
|
],
|
|
653
699
|
};
|
|
654
700
|
});
|
|
655
|
-
// ── Tool: benchmark_series (
|
|
701
|
+
// ── Tool: benchmark_series (2 credits) ──────────────────────────────
|
|
656
702
|
registerTool('benchmark_series', 'Score evolution for a single benchmark on one AI model. Costs 2 credits ($0.04). Strict premium, no free trial; the 7-day-capped sibling benchmark_series_free is the discovery option. Benchmark keys: swe_bench, mmlu_pro, gpqa_diamond, math, human_eval.', {
|
|
657
703
|
model: z.string().describe('Model id or display name'),
|
|
658
704
|
benchmark: z.string().describe('Benchmark key (e.g. swe_bench, mmlu_pro, gpqa_diamond, math, human_eval)'),
|
|
@@ -678,7 +724,7 @@ registerTool('benchmark_series', 'Score evolution for a single benchmark on one
|
|
|
678
724
|
],
|
|
679
725
|
};
|
|
680
726
|
});
|
|
681
|
-
// ── Tool: status_uptime (
|
|
727
|
+
// ── Tool: status_uptime (2 credits) ─────────────────────────────────
|
|
682
728
|
registerTool('status_uptime', 'Daily uptime rollup for one provider with operational/degraded/down day counts and uptime % (degraded counts as half-credit). Costs 2 credits ($0.04). Strict premium, no free trial; the 7-day-capped sibling status_uptime_free is the discovery option.', {
|
|
683
729
|
provider: z.string().describe('Provider name (e.g. anthropic, openai, google)'),
|
|
684
730
|
from: z.string().optional().describe('Start date YYYY-MM-DD UTC (default: 30 days ago)'),
|
|
@@ -1046,9 +1092,13 @@ registerTool('create_leaderboard_rank_watch', 'Register a webhook that fires whe
|
|
|
1046
1092
|
}, CREATE_TOOL);
|
|
1047
1093
|
// ── Tool: delete_watch ──────────────────────────────────────────────
|
|
1048
1094
|
registerTool('delete_watch', 'Delete one of your active webhook watches by id. Free, requires TENSORFEED_TOKEN.', {
|
|
1049
|
-
watch_id: z
|
|
1095
|
+
watch_id: z
|
|
1096
|
+
.string()
|
|
1097
|
+
.regex(/^[A-Za-z0-9_-]+$/, 'watch_id may only contain letters, digits, underscores, and hyphens')
|
|
1098
|
+
.max(128)
|
|
1099
|
+
.describe('The wat_... id from create_price_watch / create_status_watch / list_watches'),
|
|
1050
1100
|
}, async ({ watch_id }) => {
|
|
1051
|
-
await fetchJSON(`/premium/watches/${watch_id}`, { method: 'DELETE', auth: true });
|
|
1101
|
+
await fetchJSON(`/premium/watches/${encodeURIComponent(watch_id)}`, { method: 'DELETE', auth: true });
|
|
1052
1102
|
return { content: [{ type: 'text', text: `Deleted watch ${watch_id}.` }] };
|
|
1053
1103
|
}, DELETE_TOOL);
|
|
1054
1104
|
// ── Tool: probe_latest (free) ───────────────────────────────────────
|
|
@@ -1387,7 +1437,7 @@ registerTool('get_recent_earthquakes', "Recent earthquakes from the USGS Earthqu
|
|
|
1387
1437
|
return { content: [{ type: 'text', text: `${header}\n\n${lines || ' (no events in this window)'}\n\nSource: USGS Earthquake Hazards Program. Public domain (17 USC §105).` }] };
|
|
1388
1438
|
});
|
|
1389
1439
|
// ── Tool: get_weather_alerts (free, US only) ────────────────────────
|
|
1390
|
-
registerTool('get_weather_alerts', "Active US weather alerts from the National Weather Service. US-only. Filter by 2-letter state code (area), exact NWS event name, severity, urgency, status. Returns id, event, severity, urgency, headline, description, areaDesc, sent/effective/expires/ends, sender_name, web URL. Active alerts only
|
|
1440
|
+
registerTool('get_weather_alerts', "Active US weather alerts from the National Weather Service. US-only. Filter by 2-letter state code (area), exact NWS event name, severity, urgency, status. Returns id, event, severity, urgency, headline, description, areaDesc, sent/effective/expires/ends, sender_name, web URL. Active alerts only. Expired alerts fall off the upstream feed automatically. License: US Government public domain. Free, no auth.", {
|
|
1391
1441
|
area: z.string().regex(/^[A-Za-z]{2}$/).optional().describe('2-letter US state or territory code (CA, TX, PR, etc)'),
|
|
1392
1442
|
event: z.string().optional().describe('Exact NWS event name (e.g. "Tornado Warning", "Heat Advisory")'),
|
|
1393
1443
|
severity: z.enum(['Extreme', 'Severe', 'Moderate', 'Minor', 'Unknown']).optional().describe('Severity filter'),
|
|
@@ -1697,10 +1747,13 @@ registerTool('get_ai_company_filings', 'Get recent SEC filings (10-K, 10-Q, 8-K,
|
|
|
1697
1747
|
});
|
|
1698
1748
|
// ── Tool: premium_ai_company (1 credit) ─────────────────────────────
|
|
1699
1749
|
registerTool('premium_ai_company', 'Get the per-ticker AI intelligence envelope for one AI bellwether: latest 10 SEC filings, latest 10 AI-tagged news mentions filtered by curated aliases, strategic and equity funding rounds where the company is a lead or notable investor, and cohort metadata. One call replaces four free siblings (sec/filings, news, funding, cohort) plus the agent-side alias filter. Costs 1 credit ($0.02). Strict-premium; ticker must be in the cohort.', {
|
|
1700
|
-
ticker: z
|
|
1750
|
+
ticker: z
|
|
1751
|
+
.string()
|
|
1752
|
+
.regex(/^[A-Za-z.\-]{1,8}$/, 'ticker may only contain letters, dots, and hyphens (1 to 8 chars)')
|
|
1753
|
+
.describe('AI bellwether ticker (case-insensitive). One of NVDA, AMD, AVGO, TSM, ARM, MSFT, GOOGL, AMZN, ORCL, PLTR, SMCI, AAPL, META, TSLA.'),
|
|
1701
1754
|
}, async ({ ticker }) => {
|
|
1702
1755
|
const upper = ticker.toUpperCase();
|
|
1703
|
-
const data = (await fetchJSON(`/premium/ai-companies/${upper}`, { auth: true }));
|
|
1756
|
+
const data = (await fetchJSON(`/premium/ai-companies/${encodeURIComponent(upper)}`, { auth: true }));
|
|
1704
1757
|
const filingsList = data.filings.items
|
|
1705
1758
|
.slice(0, 10)
|
|
1706
1759
|
.map((f, i) => ` ${i + 1}. ${f.form} on ${f.filing_date}: ${f.primary_doc_description}`)
|
|
@@ -1724,6 +1777,139 @@ registerTool('premium_ai_company', 'Get the per-ticker AI intelligence envelope
|
|
|
1724
1777
|
],
|
|
1725
1778
|
};
|
|
1726
1779
|
});
|
|
1780
|
+
// ── Tool: get_x402_summary (free) ───────────────────────────────────
|
|
1781
|
+
registerTool('get_x402_summary', 'Get ecosystem-level x402 USDC settlement rollup on Base mainnet. Returns volume_usdc, count, unique_publishers, and change_vs_prior_window across a 24h, 7d, or 30d window. TensorFeed indexes Base USDC Transfer events filtered to wallets self-published by x402-compliant publishers in their /.well-known/x402.json manifests. Forward-only from 2026-05-28; ~6 min worst-case freshness (10 min SLA). Free, capped to one window per call.', {
|
|
1782
|
+
window: z.enum(['24h', '7d', '30d']).optional().describe('Aggregation window. Defaults to 24h if omitted.'),
|
|
1783
|
+
}, async ({ window }) => {
|
|
1784
|
+
const w = window ?? '24h';
|
|
1785
|
+
const data = (await fetchJSON(`/x402-index/summary?window=${w}`));
|
|
1786
|
+
return {
|
|
1787
|
+
content: [
|
|
1788
|
+
{
|
|
1789
|
+
type: 'text',
|
|
1790
|
+
text: `x402 settlement rollup (window ${data.window}, captured ${data.captured_at}):\n volume_usdc: ${data.volume_usdc}\n count: ${data.count}\n unique_publishers: ${data.unique_publishers}\n change vs prior window: volume ${data.change_vs_prior_window.volume_pct}%, count ${data.change_vs_prior_window.count_pct}%`,
|
|
1791
|
+
},
|
|
1792
|
+
],
|
|
1793
|
+
};
|
|
1794
|
+
});
|
|
1795
|
+
// ── Tool: get_x402_publishers (free) ────────────────────────────────
|
|
1796
|
+
registerTool('get_x402_publishers', 'List x402-compliant publishers TensorFeed currently indexes. Each entry: domain, pay_to_wallets, first_seen, last_crawled, last_event_at, last_crawl_error. Publishers are auto-discovered daily via /.well-known/x402.json crawls. Use this to enumerate which publishers can then be queried in depth via premium_x402_publisher_receipts. Free, no input.', {}, async () => {
|
|
1797
|
+
const data = (await fetchJSON('/x402-index/publishers'));
|
|
1798
|
+
if (data.count === 0) {
|
|
1799
|
+
return { content: [{ type: 'text', text: 'No x402 publishers indexed yet.' }] };
|
|
1800
|
+
}
|
|
1801
|
+
// Cap the rendered list so a large index cannot flood the agent's
|
|
1802
|
+
// context. Sanitize layer also enforces a hard char cap downstream.
|
|
1803
|
+
const MAX_PUBLISHERS = 50;
|
|
1804
|
+
const shown = data.publishers.slice(0, MAX_PUBLISHERS);
|
|
1805
|
+
const lines = shown
|
|
1806
|
+
.map((p, i) => `${i + 1}. ${p.domain}\n wallets: ${p.pay_to_wallets.join(', ')}\n first_seen: ${p.first_seen}${p.last_event_at ? `\n last_event_at: ${p.last_event_at}` : ''}${p.last_crawled ? `\n last_crawled: ${p.last_crawled}` : ''}${p.last_crawl_error ? `\n last_crawl_error: ${p.last_crawl_error}` : ''}`)
|
|
1807
|
+
.join('\n\n');
|
|
1808
|
+
const more = data.count > shown.length ? `\n\n...and ${data.count - shown.length} more (showing first ${shown.length}).` : '';
|
|
1809
|
+
return {
|
|
1810
|
+
content: [
|
|
1811
|
+
{
|
|
1812
|
+
type: 'text',
|
|
1813
|
+
text: `x402 publishers (${data.count}, captured ${data.captured_at}):\n\n${lines}${more}`,
|
|
1814
|
+
},
|
|
1815
|
+
],
|
|
1816
|
+
};
|
|
1817
|
+
});
|
|
1818
|
+
// ── Tool: get_x402_leaderboard (free) ───────────────────────────────
|
|
1819
|
+
registerTool('get_x402_leaderboard', 'Top publishers by x402 USDC settlement volume across a 24h, 7d, or 30d window. Each leader: rank, domain, volume_usdc, count, share_pct of the sliced total. Limit clamped 1 to 25. Useful for surfacing which x402 services are receiving the most agent traffic. Free.', {
|
|
1820
|
+
window: z.enum(['24h', '7d', '30d']).optional().describe('Aggregation window. Defaults to 24h.'),
|
|
1821
|
+
limit: z.number().min(1).max(25).optional().describe('Top N publishers to return. Default 10, max 25.'),
|
|
1822
|
+
}, async ({ window, limit }) => {
|
|
1823
|
+
const w = window ?? '24h';
|
|
1824
|
+
const l = limit ?? 10;
|
|
1825
|
+
const data = (await fetchJSON(`/x402-index/leaderboard?window=${w}&limit=${l}`));
|
|
1826
|
+
if (data.leaders.length === 0) {
|
|
1827
|
+
return { content: [{ type: 'text', text: `No x402 leaders for window ${data.window}.` }] };
|
|
1828
|
+
}
|
|
1829
|
+
const lines = data.leaders
|
|
1830
|
+
.map((e) => ` ${e.rank}. ${e.domain}: $${e.volume_usdc} across ${e.count} settlements (${e.share_pct}% share)`)
|
|
1831
|
+
.join('\n');
|
|
1832
|
+
return {
|
|
1833
|
+
content: [
|
|
1834
|
+
{
|
|
1835
|
+
type: 'text',
|
|
1836
|
+
text: `x402 leaderboard (window ${data.window}, captured ${data.captured_at}):\n${lines}`,
|
|
1837
|
+
},
|
|
1838
|
+
],
|
|
1839
|
+
};
|
|
1840
|
+
});
|
|
1841
|
+
// ── Tool: get_x402_recent (free) ────────────────────────────────────
|
|
1842
|
+
registerTool('get_x402_recent', 'Most recent x402 USDC settlement events newest-first. Each event: tx_hash, block, ts, from_address, to_address, amount_usdc, publisher_domain, base_explorer_url (basescan.org link). Backed by a 100-event ring buffer updated on every indexer cron tick. Limit clamped 1 to 50, default 20. Free.', {
|
|
1843
|
+
limit: z.number().min(1).max(50).optional().describe('Number of most-recent events. Default 20, max 50.'),
|
|
1844
|
+
}, async ({ limit }) => {
|
|
1845
|
+
const l = limit ?? 20;
|
|
1846
|
+
const data = (await fetchJSON(`/x402-index/recent?limit=${l}`));
|
|
1847
|
+
if (data.count === 0) {
|
|
1848
|
+
return { content: [{ type: 'text', text: 'No recent x402 settlement events.' }] };
|
|
1849
|
+
}
|
|
1850
|
+
const lines = data.events
|
|
1851
|
+
.map((e, i) => `${i + 1}. ${e.ts} block ${e.block}\n ${e.publisher_domain}: $${e.amount_usdc} USDC\n from ${e.from_address} to ${e.to_address}\n ${e.base_explorer_url}`)
|
|
1852
|
+
.join('\n\n');
|
|
1853
|
+
return {
|
|
1854
|
+
content: [
|
|
1855
|
+
{
|
|
1856
|
+
type: 'text',
|
|
1857
|
+
text: `Recent x402 settlements (${data.count}, captured ${data.captured_at}):\n\n${lines}`,
|
|
1858
|
+
},
|
|
1859
|
+
],
|
|
1860
|
+
};
|
|
1861
|
+
});
|
|
1862
|
+
// ── Tool: premium_x402_publisher_receipts (1 credit) ────────────────
|
|
1863
|
+
registerTool('premium_x402_publisher_receipts', 'Per-publisher receipt feed for one x402-compliant domain across a from/to date range. Returns publisher meta (domain, pay_to_wallets, first_seen), window rollup (volume_usdc, count, avg_amount, daily_series), and attribution. \n\nVS FREE: get_x402_publishers tells you WHICH publishers exist but only with rolled-up scalar counters. This endpoint composes the per-day rollup across a date range with avg_amount, daily series for charts, and structured attribution license metadata in one paid call. An agent building a per-publisher analytics dashboard would otherwise need to call get_x402_publishers + reconstruct daily series from get_x402_summary per-day (impossible because /summary returns ecosystem totals not per-publisher) or wait for the next free-tier expansion. The premium endpoint saves the integration overhead and serves a forensic / compliance-grade payload for $0.02. \n\nForward-only index. Costs 1 credit ($0.02). Strict-premium; domain must exist in get_x402_publishers, otherwise 404.', {
|
|
1864
|
+
domain: z.string().describe('Publisher domain to query, e.g. tensorfeed.ai. Must exist in get_x402_publishers result.'),
|
|
1865
|
+
from: z.string().describe('Inclusive start date YYYY-MM-DD.'),
|
|
1866
|
+
to: z.string().describe('Inclusive end date YYYY-MM-DD.'),
|
|
1867
|
+
}, async ({ domain, from, to }) => {
|
|
1868
|
+
const params = new URLSearchParams({ from, to });
|
|
1869
|
+
const data = (await fetchJSON(`/premium/x402-index/publisher/${encodeURIComponent(domain)}?${params}`, { auth: true }));
|
|
1870
|
+
const series = data.rollup.daily_series
|
|
1871
|
+
.map((d) => ` ${d.date}: $${d.volume_usdc} across ${d.count}`)
|
|
1872
|
+
.join('\n');
|
|
1873
|
+
const billing = data.billing
|
|
1874
|
+
? `\n\nCharged ${data.billing.credits_charged} credit. Remaining: ${data.billing.credits_remaining ?? '?'}.`
|
|
1875
|
+
: '';
|
|
1876
|
+
return {
|
|
1877
|
+
content: [
|
|
1878
|
+
{
|
|
1879
|
+
type: 'text',
|
|
1880
|
+
text: `${data.publisher.domain} (first_seen ${data.publisher.first_seen})\nwallets: ${data.publisher.pay_to_wallets.join(', ')}\nwindow: ${data.window.from} to ${data.window.to} (${data.window.days} days)\n\nRollup:\n volume_usdc: ${data.rollup.volume_usdc}\n count: ${data.rollup.count}\n avg_amount: ${data.rollup.avg_amount}\n\nDaily series:\n${series || ' none'}\n\nAttribution: ${data.attribution}\nLicense: ${data.license}${billing}`,
|
|
1881
|
+
},
|
|
1882
|
+
],
|
|
1883
|
+
};
|
|
1884
|
+
});
|
|
1885
|
+
// ── Tool: premium_x402_series (1 credit) ────────────────────────────
|
|
1886
|
+
registerTool('premium_x402_series', 'Time-series of ecosystem or per-publisher x402 settlement volume or count across a from/to date range, chart-feeding. Required: metric (volume or count), granularity (day or hour), from + to (YYYY-MM-DD). Optional: domain filter (omit for ecosystem). \n\nVS FREE: get_x402_summary returns one rolled-up scalar per call for a fixed window length (24h / 7d / 30d). For ANY chart or dashboard, the caller needs a series, not a scalar. The premium endpoint returns the full series in one call instead of N calls + N rate-limit budget burns. For a publisher-scoped series, it is the ONLY way to get per-publisher daily data (get_x402_summary is ecosystem-only). \n\nMVP supports granularity=day; hour returns an empty series with an attribution note (hourly rollups land in a future indexer version). Costs 1 credit ($0.02).', {
|
|
1887
|
+
metric: z.enum(['volume', 'count']).describe('Which series to return.'),
|
|
1888
|
+
granularity: z.enum(['day', 'hour']).describe('Bucket size. MVP: day only; hour returns empty series.'),
|
|
1889
|
+
from: z.string().describe('Inclusive start date YYYY-MM-DD.'),
|
|
1890
|
+
to: z.string().describe('Inclusive end date YYYY-MM-DD.'),
|
|
1891
|
+
domain: z.string().optional().describe('Optional publisher domain filter. Omit for ecosystem-wide series.'),
|
|
1892
|
+
}, async ({ metric, granularity, from, to, domain }) => {
|
|
1893
|
+
const params = new URLSearchParams({ metric, granularity, from, to });
|
|
1894
|
+
if (domain)
|
|
1895
|
+
params.set('domain', domain);
|
|
1896
|
+
const data = (await fetchJSON(`/premium/x402-index/series?${params}`, { auth: true }));
|
|
1897
|
+
const scope = domain ? `domain=${domain}` : 'ecosystem-wide';
|
|
1898
|
+
const seriesLines = data.series.length
|
|
1899
|
+
? data.series.map((p) => ` ${p.ts}: ${p.value}`).join('\n')
|
|
1900
|
+
: ' (empty)';
|
|
1901
|
+
const billing = data.billing
|
|
1902
|
+
? `\n\nCharged ${data.billing.credits_charged} credit. Remaining: ${data.billing.credits_remaining ?? '?'}.`
|
|
1903
|
+
: '';
|
|
1904
|
+
return {
|
|
1905
|
+
content: [
|
|
1906
|
+
{
|
|
1907
|
+
type: 'text',
|
|
1908
|
+
text: `x402 ${data.metric} series (${data.granularity}, ${scope})\nwindow: ${data.window.from} to ${data.window.to}\n\nSeries:\n${seriesLines}\n\nAttribution: ${data.attribution}${billing}`,
|
|
1909
|
+
},
|
|
1910
|
+
],
|
|
1911
|
+
};
|
|
1912
|
+
});
|
|
1727
1913
|
// ── Start ───────────────────────────────────────────────────────────
|
|
1728
1914
|
async function main() {
|
|
1729
1915
|
const transport = new StdioServerTransport();
|
package/dist/sanitize.d.ts
CHANGED
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
* tokens, jailbreak preamble lines, "ignore previous instructions")
|
|
20
20
|
* - Collapse runs of whitespace
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* A hard per-response character cap is applied (see MAX_RESPONSE_CHARS).
|
|
23
|
+
* Tool output is conversational and usually small, but a large or inflated
|
|
24
|
+
* upstream response would otherwise blow the host agent's context window.
|
|
25
|
+
* Anything past the cap is dropped and a clear marker is appended.
|
|
24
26
|
*/
|
|
25
27
|
/**
|
|
26
28
|
* Scrub a single string for return to a host LLM. Idempotent and pure.
|
package/dist/sanitize.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tensorfeed/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.1",
|
|
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.36.1",
|
|
7
7
|
"websiteUrl": "https://tensorfeed.ai/developers/agent-payments",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/RipperMercs/tensorfeed-mcp",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"registryType": "npm",
|
|
21
21
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
22
22
|
"identifier": "@tensorfeed/mcp-server",
|
|
23
|
-
"version": "1.
|
|
23
|
+
"version": "1.36.1",
|
|
24
24
|
"runtimeHint": "npx",
|
|
25
25
|
"transport": {
|
|
26
26
|
"type": "stdio"
|
|
@@ -69,7 +69,11 @@
|
|
|
69
69
|
"get_ai_cves_latest",
|
|
70
70
|
"get_ai_cves_feed",
|
|
71
71
|
"get_ai_cves_stats",
|
|
72
|
-
"get_ai_company_filings"
|
|
72
|
+
"get_ai_company_filings",
|
|
73
|
+
"get_x402_summary",
|
|
74
|
+
"get_x402_publishers",
|
|
75
|
+
"get_x402_leaderboard",
|
|
76
|
+
"get_x402_recent"
|
|
73
77
|
],
|
|
74
78
|
"premiumTools": [
|
|
75
79
|
"premium_routing",
|
|
@@ -93,7 +97,9 @@
|
|
|
93
97
|
"delete_watch",
|
|
94
98
|
"get_account_balance",
|
|
95
99
|
"get_account_usage",
|
|
96
|
-
"premium_ai_company"
|
|
100
|
+
"premium_ai_company",
|
|
101
|
+
"premium_x402_publisher_receipts",
|
|
102
|
+
"premium_x402_series"
|
|
97
103
|
]
|
|
98
104
|
}
|
|
99
105
|
}
|