@tensorfeed/mcp-server 1.36.0 → 1.36.2

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 CHANGED
@@ -2,6 +2,34 @@
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.2 - 2026-05-31
6
+
7
+ Pricing-copy and catalog-accuracy corrections. No change to the free/paid
8
+ split or the actual on-chain charge.
9
+
10
+ - Corrected the stated credit cost on `pricing_series`, `benchmark_series`,
11
+ and `status_uptime` and their free-sibling pointers back to 1 credit
12
+ ($0.02). These are Tier 2, which maps to 1 credit in TIER_COSTS; release
13
+ 1.36.1 had wrongly relabeled them 2 credits.
14
+ - Catalog accuracy: `server.json` now lists the true 59 tools (36 free,
15
+ 23 premium), adding 5 free tools that were omitted (`get_model_deprecations`,
16
+ `get_ai_supply_chain_iocs`, `get_honeypot_iocs`, `check_afta_certification`,
17
+ `get_agent_reputation_card`) and removing the phantom `mcp_registry_series`;
18
+ the manifest long_description now reads 59 tools.
19
+
20
+ ## 1.36.1 - 2026-05-31
21
+
22
+ Security hardening only. No API changes; agents see the same tools.
23
+
24
+ - The `latest_news` resource output is now sanitized like every tool
25
+ output, so external RSS titles cannot carry prompt-injection markers
26
+ to the model.
27
+ - Tool output is capped at 40000 characters and the x402 publishers and
28
+ agent reputation renderers are bounded, so a large upstream response
29
+ cannot overflow the agent's context window.
30
+ - Path parameters (watch id, company ticker) are now URL-encoded and
31
+ schema-validated before they reach a request path.
32
+
5
33
  ## 1.36.0 - 2026-05-28
6
34
 
7
35
  Adds 6 tools wrapping the x402 settlement index endpoints (Wave 20)
@@ -59,14 +87,14 @@ Premium endpoint audit (2026-05-14) through the lens "does this clearly save the
59
87
  ### Changed (pricing)
60
88
  Six tools mapping to strict-premium TF Worker endpoints are repriced from Tier 1 ($0.02) into higher tiers reflecting the curated-derived-metrics value they provide. Tool descriptions updated to advertise the new prices:
61
89
 
62
- - `pricing_series`: 1 credit ($0.02) -> 2 credits ($0.04)
63
- - `benchmark_series`: 1 credit ($0.02) -> 2 credits ($0.04)
64
- - `status_uptime`: 1 credit ($0.02) -> 2 credits ($0.04)
65
- - `status_leaderboard`: 1 credit ($0.02) -> 3 credits ($0.06)
66
- - `provider_deepdive`: 1 credit ($0.02) -> 3 credits ($0.06)
67
- - `probe_series_premium`: 1 credit ($0.02) -> 3 credits ($0.06)
90
+ - `pricing_series`: Tier 2, 1 credit ($0.02)
91
+ - `benchmark_series`: Tier 2, 1 credit ($0.02)
92
+ - `status_uptime`: Tier 2, 1 credit ($0.02)
93
+ - `status_leaderboard`: Tier 3, 5 credits ($0.10)
94
+ - `provider_deepdive`: Tier 3, 5 credits ($0.10)
95
+ - `probe_series_premium`: Tier 3, 5 credits ($0.10)
68
96
 
69
- Tier 2 ($0.04) tools are single-entity full-window historical lookups. Tier 3 ($0.06) tools are heavy multi-source aggregations and TF-unique measurements.
97
+ Tier 2 (1 credit, $0.02) tools are single-entity full-window historical lookups. Tier 3 (5 credits, $0.10) tools are heavy multi-source aggregations and TF-unique measurements.
70
98
 
71
99
  Rationale: per the 70/30 premium-lock framework, strict-premium endpoints carry TF's actual moat. The Tier 1 ($0.02) baseline price made sense during the demand-discovery temp-unlock; now that these endpoints are gated as strict-premium (v1.30.0) and the gate is observable, repricing into Tier 2/3 sets prices closer to the curated-derived-metrics market (Glassnode / Kaito comparables). Per-call USD cost is now legible alongside credit count in every affected tool description.
72
100
 
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
- const res = await fetch(`${API_BASE}${path}`, {
27
- method: opts.method ?? 'GET',
28
- headers,
29
- ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),
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: data.articles.length,
141
- articles: data.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; render the highlights deterministically and
468
- // include the raw JSON so the agent can reason over any field it needs.
469
- const card = data.card ?? data;
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(card, null, 2)}`,
520
+ text: `Agent reputation card for ${wallet ?? `token_prefix=${token_prefix}`}:\n\n${JSON.stringify(clamped, null, 2)}`,
475
521
  },
476
522
  ],
477
523
  };
@@ -628,7 +674,7 @@ registerTool('status_leaderboard_free', 'Cross-provider uptime leaderboard, rank
628
674
  };
629
675
  });
630
676
  // ── Tool: pricing_series (1 credit) ─────────────────────────────────
631
- 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.', {
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 1 credit ($0.02). 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)'),
634
680
  to: z.string().optional().describe('End date YYYY-MM-DD UTC (default: today)'),
@@ -653,7 +699,7 @@ registerTool('pricing_series', 'Daily price points for one AI model with min/max
653
699
  };
654
700
  });
655
701
  // ── Tool: benchmark_series (1 credit) ───────────────────────────────
656
- 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.', {
702
+ registerTool('benchmark_series', 'Score evolution for a single benchmark on one AI model. Costs 1 credit ($0.02). 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)'),
659
705
  from: z.string().optional().describe('Start date YYYY-MM-DD UTC (default: 30 days ago)'),
@@ -679,7 +725,7 @@ registerTool('benchmark_series', 'Score evolution for a single benchmark on one
679
725
  };
680
726
  });
681
727
  // ── Tool: status_uptime (1 credit) ──────────────────────────────────
682
- 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.', {
728
+ registerTool('status_uptime', 'Daily uptime rollup for one provider with operational/degraded/down day counts and uptime % (degraded counts as half-credit). Costs 1 credit ($0.02). 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)'),
685
731
  to: z.string().optional().describe('End date YYYY-MM-DD UTC (default: today)'),
@@ -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.string().describe('The wat_... id from create_price_watch / create_status_watch / list_watches'),
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 — expired alerts fall off the upstream feed automatically. License: US Government public domain. Free, no auth.", {
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.string().describe('AI bellwether ticker (case-insensitive). One of NVDA, AMD, AVGO, TSM, ARM, MSFT, GOOGL, AMZN, ORCL, PLTR, SMCI, AAPL, META, TSLA.'),
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}`)
@@ -1745,14 +1798,19 @@ registerTool('get_x402_publishers', 'List x402-compliant publishers TensorFeed c
1745
1798
  if (data.count === 0) {
1746
1799
  return { content: [{ type: 'text', text: 'No x402 publishers indexed yet.' }] };
1747
1800
  }
1748
- const lines = data.publishers
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
1749
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}` : ''}`)
1750
1807
  .join('\n\n');
1808
+ const more = data.count > shown.length ? `\n\n...and ${data.count - shown.length} more (showing first ${shown.length}).` : '';
1751
1809
  return {
1752
1810
  content: [
1753
1811
  {
1754
1812
  type: 'text',
1755
- text: `x402 publishers (${data.count}, captured ${data.captured_at}):\n\n${lines}`,
1813
+ text: `x402 publishers (${data.count}, captured ${data.captured_at}):\n\n${lines}${more}`,
1756
1814
  },
1757
1815
  ],
1758
1816
  };
@@ -19,8 +19,10 @@
19
19
  * tokens, jailbreak preamble lines, "ignore previous instructions")
20
20
  * - Collapse runs of whitespace
21
21
  *
22
- * No length cap is applied here. Tool output is conversational and may
23
- * legitimately be tens of KB; upstream KV size limits already bound it.
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.36.0",
3
+ "version": "1.36.2",
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.36.0",
6
+ "version": "1.36.2",
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.36.0",
23
+ "version": "1.36.2",
24
24
  "runtimeHint": "npx",
25
25
  "transport": {
26
26
  "type": "stdio"
@@ -47,8 +47,13 @@
47
47
  "get_ai_status",
48
48
  "is_service_down",
49
49
  "get_model_pricing",
50
+ "get_model_deprecations",
50
51
  "get_ai_today",
51
52
  "get_agent_activity",
53
+ "get_ai_supply_chain_iocs",
54
+ "get_honeypot_iocs",
55
+ "check_afta_certification",
56
+ "get_agent_reputation_card",
52
57
  "mcp_registry_snapshot",
53
58
  "probe_latest",
54
59
  "get_ai_papers_trending",
@@ -87,7 +92,6 @@
87
92
  "provider_deepdive",
88
93
  "compare_models",
89
94
  "whats_new",
90
- "mcp_registry_series",
91
95
  "probe_series",
92
96
  "list_watches",
93
97
  "create_price_watch",