@tensorfeed/mcp-server 1.11.0 → 1.12.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 CHANGED
@@ -1,6 +1,16 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to the [TensorFeed.ai MCP server](https://github.com/RipperMercs/tensorfeed/tree/main/mcp-server). 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).
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
+
5
+ ## 1.12.0 - 2026-05-04
6
+
7
+ ### Added
8
+ - Defense-in-depth output sanitizer (`src/sanitize.ts`). Every tool's text output passes through a scrub pass before reaching the host LLM (Claude Desktop, Cursor, Cline, Roo, etc): strips ASCII control chars (preserves newline, carriage return, tab), strips bidi control + zero-width spoofing chars, and neutralizes role-confusion markers (ChatML / Llama / Mistral chat tokens, "ignore previous instructions", line-leading "system:" / "developer:" / "assistant:" preambles). Wired via a new `registerTool()` helper that wraps `server.tool()`. The TensorFeed worker already runs the same scrub on its agent-facing endpoints; this layer protects against any new endpoint that might forget it, plus drift between worker rules and what the MCP server should consider safe. No behavior change for legitimate text. No new dependencies.
9
+
10
+ ## 1.11.1 - 2026-05-04
11
+
12
+ ### Changed
13
+ - Repository URL updated to point at the dedicated [`RipperMercs/tensorfeed-mcp`](https://github.com/RipperMercs/tensorfeed-mcp) repo. The MCP server now has its own GitHub home for issues, stars, and discoverability. Source-of-truth for builds remains in the main `tensorfeed` monorepo's `mcp-server/` folder; the standalone repo is mirrored on release. No code changes; no behavior changes; npm install / npx unaffected.
4
14
 
5
15
  ## 1.11.0 - 2026-04-29
6
16
 
package/README.md CHANGED
@@ -1,118 +1,34 @@
1
1
  # TensorFeed MCP Server
2
2
 
3
- A [Model Context Protocol](https://modelcontextprotocol.io) server that gives AI agents access to real-time AI industry data and premium endpoints from [TensorFeed.ai](https://tensorfeed.ai).
3
+ > **The MCP server has its own repo: https://github.com/RipperMercs/tensorfeed-mcp**
4
+ >
5
+ > User-facing docs, install instructions, and the full tool reference live there. Star and watch that repo to follow MCP server updates.
4
6
 
5
- ## Free Tools
7
+ This subfolder remains in the main `tensorfeed` repo as the **publishing source** for the npm package (`@tensorfeed/mcp-server`) and the [official MCP registry](https://registry.modelcontextprotocol.io/v0/servers/ai.tensorfeed/mcp-server) entry. Edits to `src/`, `server.json`, `package.json`, etc. happen here and get pushed to the standalone repo on release.
6
8
 
7
- | Tool | Description |
8
- |------|-------------|
9
- | `get_ai_news` | Latest AI news from 15+ sources (filterable by category) |
10
- | `get_ai_status` | Real-time status of Claude, OpenAI, Gemini, Mistral, and more |
11
- | `is_service_down` | Check if a specific AI service is down |
12
- | `get_model_pricing` | Compare pricing across all major AI model providers |
13
- | `get_ai_today` | Summary of top AI stories from the last 24 hours |
14
- | `get_agent_activity` | Live AI bot traffic on TensorFeed.ai with top-bot breakdown |
15
- | `mcp_registry_snapshot` | Today's summary of the official MCP server registry: total servers, top namespaces, newly added/reactivated/deprecated names |
16
- | `probe_latest` | Last 24h of measured LLM endpoint latency and availability per provider (ttfb/total p50/p95/p99). Unique because measured, not self-reported |
9
+ ## Publish a new version
17
10
 
18
- ## Premium Tools (paid in USDC on Base)
11
+ From the main tensorfeed repo:
19
12
 
20
- These tools require a `TENSORFEED_TOKEN` env var. Buy credits at [tensorfeed.ai/developers/agent-payments](https://tensorfeed.ai/developers/agent-payments) and pass the returned `tf_live_...` token to your MCP client.
13
+ ```powershell
14
+ # 1. Bump the version in mcp-server/package.json + mcp-server/server.json
15
+ # 2. Build + npm publish from the mcp-server/ folder
16
+ cd mcp-server
17
+ npm run build
18
+ npm publish --access public
21
19
 
22
- | Tool | Cost | Description |
23
- |------|------|-------------|
24
- | `get_account_balance` | Free | Show credits remaining on the configured token |
25
- | `get_account_usage` | Free | Per-endpoint usage history for the configured token |
26
- | `premium_routing` | 1 credit | Top-N ranked AI model recommendations with full score breakdown |
27
- | `pricing_series` | 1 credit | Daily price points for one model with min/max/delta summary |
28
- | `benchmark_series` | 1 credit | Score evolution for a benchmark on one model |
29
- | `status_uptime` | 1 credit | Daily uptime % for one provider with incident-day list |
30
- | `premium_agents_directory` | 1 credit | Enriched agents catalog with live status, news, traffic, trending_score |
31
- | `news_search` | 1 credit | Full-text news search with date/provider/category filters and relevance scoring |
32
- | `cost_projection` | 1 credit | Project workload cost across 1-10 AI models with 4 time horizons and cheapest-monthly ranking |
33
- | `provider_deepdive` | 1 credit | One provider's full profile: status, all models with benchmarks joined, recent news, traffic |
34
- | `compare_models` | 1 credit | Side-by-side comparison of 2-5 models with normalized benchmarks and rankings |
35
- | `whats_new` | 1 credit | Agent morning brief: pricing changes, incidents, top news from last 1-7 days |
36
- | `mcp_registry_series` | 1 credit | Multi-day MCP registry growth and churn series, 90-day max range |
37
- | `probe_series` | 1 credit | Daily SLA series for one LLM provider with ttfb/total p50/p95/p99 and incident-hour count, 90-day max range |
38
- | `list_watches` | Free | List active webhook watches owned by the token |
39
- | `create_price_watch` | 1 credit | Register a webhook watch on a model price change |
40
- | `create_status_watch` | 1 credit | Register a webhook watch on a service status transition |
41
- | `create_digest_watch` | 1 credit | Register a daily/weekly digest webhook (pricing-changes summary, set-and-forget) |
42
- | `delete_watch` | Free | Remove an active watch |
20
+ # 3. Republish to the official MCP registry
21
+ .\scripts\mcp-publish.ps1
43
22
 
44
- ## Setup with Claude Code
45
-
46
- Add to your Claude Code MCP config (`~/.claude/claude_desktop_config.json`):
47
-
48
- ```json
49
- {
50
- "mcpServers": {
51
- "tensorfeed": {
52
- "command": "npx",
53
- "args": ["-y", "@tensorfeed/mcp-server"]
54
- }
55
- }
56
- }
57
- ```
58
-
59
- To enable premium tools, add the `env` block:
60
-
61
- ```json
62
- {
63
- "mcpServers": {
64
- "tensorfeed": {
65
- "command": "npx",
66
- "args": ["-y", "@tensorfeed/mcp-server"],
67
- "env": {
68
- "TENSORFEED_TOKEN": "tf_live_..."
69
- }
70
- }
71
- }
72
- }
73
- ```
74
-
75
- ## Setup with Claude Desktop
76
-
77
- Add to Claude Desktop settings (same shape as above).
78
-
79
- ## Run Locally
80
-
81
- ```bash
82
- npx @tensorfeed/mcp-server
83
- # or with a token for premium endpoints
84
- TENSORFEED_TOKEN=tf_live_... npx @tensorfeed/mcp-server
23
+ # 4. Mirror the new version to the standalone repo
24
+ # (manual until we wire it as a release-time GitHub Action)
85
25
  ```
86
26
 
87
- ## Example Queries
88
-
89
- Free tier:
90
-
91
- - "What's happening in AI today?"
92
- - "Is Claude down right now?"
93
- - "Compare pricing between Claude Opus and GPT-4o"
94
- - "Show me the latest AI research papers"
95
- - "What's the status of all AI services?"
96
-
97
- Premium tier (with `TENSORFEED_TOKEN` set):
98
-
99
- - "Recommend the best AI model for code under $5 per million tokens"
100
- - "Show the price history of Claude Opus 4.7 over the last 30 days"
101
- - "What's Anthropic's uptime this month?"
102
- - "Compare model pricing between April 1 and today"
103
- - "Set up a webhook to my server when Claude goes down"
104
- - "What's my credit balance?"
105
-
106
- ## Pricing
107
-
108
- Premium tools cost 1 credit per call. Credits are 50 per $1 USDC at base rate, with volume discounts at $5 (10%), $30 (25%), and $200 (40%). See [tensorfeed.ai/developers/agent-payments](https://tensorfeed.ai/developers/agent-payments) for the full payment flow and trust attestation.
109
-
110
- No refunds: all credit purchases are final per Section 17.5 of the [Terms](https://tensorfeed.ai/terms#premium). Credits do not expire and are jointly redeemable on tensorfeed.ai and terminalfeed.io, so the safe pattern is to buy small ($1 USDC for 50 credits) and top up as call volume is calibrated. Premium access is unavailable in OFAC-sanctioned jurisdictions; see Section 17.9.
111
-
112
- ## Data Source
113
-
114
- All data comes from the [TensorFeed.ai API](https://tensorfeed.ai/developers), which aggregates from 15+ sources including Anthropic, OpenAI, Google AI, TechCrunch, The Verge, arXiv, Hacker News, and more. Updated every 10 minutes.
115
-
116
- ## License
27
+ ## Quick links
117
28
 
118
- MIT - Built by [TensorFeed.ai](https://tensorfeed.ai/about)
29
+ - **User-facing repo (please star):** https://github.com/RipperMercs/tensorfeed-mcp
30
+ - **npm package:** https://www.npmjs.com/package/@tensorfeed/mcp-server
31
+ - **Official MCP registry:** https://registry.modelcontextprotocol.io/v0/servers/ai.tensorfeed/mcp-server
32
+ - **TensorFeed.ai:** https://tensorfeed.ai
33
+ - **Premium / payments:** https://tensorfeed.ai/developers/agent-payments
34
+ - **AFTA standard:** https://tensorfeed.ai/agent-fair-trade
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { z } from 'zod';
5
+ import { sanitizeToolResponse } from './sanitize.js';
5
6
  const API_BASE = 'https://tensorfeed.ai/api';
6
7
  const SDK_VERSION = '1.9.0';
7
8
  async function fetchJSON(path, opts = {}) {
@@ -46,8 +47,26 @@ const server = new McpServer({
46
47
  name: 'tensorfeed',
47
48
  version: SDK_VERSION,
48
49
  });
50
+ // ── Defense-in-depth tool wrapper ───────────────────────────────────
51
+ // Every tool's text output passes through sanitizeToolResponse before
52
+ // reaching the host LLM. Strips control chars, bidi/zero-width spoofing,
53
+ // and neutralizes role-confusion tokens (ChatML / Llama / Mistral chat
54
+ // markers, "ignore previous instructions", etc). The TensorFeed worker
55
+ // already runs the same scrub on its agent-facing endpoints; this layer
56
+ // covers any new endpoint that might forget it, plus protects against
57
+ // drift between worker rules and what the MCP server should consider
58
+ // safe. See ./sanitize.ts.
59
+ function registerTool(name, description, paramsSchema, cb) {
60
+ const wrapped = (async (args, extra) => {
61
+ const result = await cb(args, extra);
62
+ return sanitizeToolResponse(result);
63
+ });
64
+ // Direct dispatch to the SDK's underlying registration. Do not call
65
+ // registerTool() here or it will recurse forever.
66
+ return server.tool(name, description, paramsSchema, wrapped);
67
+ }
49
68
  // ── Tool: get_ai_news ───────────────────────────────────────────────
50
- server.tool('get_ai_news', 'Get the latest AI news articles from TensorFeed.ai. Aggregates from 15+ sources including Anthropic, OpenAI, Google, TechCrunch, The Verge, arXiv, and more.', {
69
+ registerTool('get_ai_news', 'Get the latest AI news articles from TensorFeed.ai. Aggregates from 15+ sources including Anthropic, OpenAI, Google, TechCrunch, The Verge, arXiv, and more.', {
51
70
  category: z.string().optional().describe('Filter by category (e.g. "anthropic", "openai", "research", "tools")'),
52
71
  limit: z.number().min(1).max(50).optional().describe('Number of articles to return (default 10, max 50)'),
53
72
  }, async ({ category, limit }) => {
@@ -62,7 +81,7 @@ server.tool('get_ai_news', 'Get the latest AI news articles from TensorFeed.ai.
62
81
  return { content: [{ type: 'text', text: text || 'No articles found.' }] };
63
82
  });
64
83
  // ── Tool: get_ai_status ─────────────────────────────────────────────
65
- server.tool('get_ai_status', 'Get real-time operational status of major AI services (Claude, OpenAI, Gemini, Mistral, Cohere, Replicate, Hugging Face).', {}, async () => {
84
+ registerTool('get_ai_status', 'Get real-time operational status of major AI services (Claude, OpenAI, Gemini, Mistral, Cohere, Replicate, Hugging Face).', {}, async () => {
66
85
  const data = await fetchJSON('/status');
67
86
  const text = data.services
68
87
  .map(s => {
@@ -75,7 +94,7 @@ server.tool('get_ai_status', 'Get real-time operational status of major AI servi
75
94
  return { content: [{ type: 'text', text: `AI Service Status:\n${text}` }] };
76
95
  });
77
96
  // ── Tool: is_service_down ───────────────────────────────────────────
78
- server.tool('is_service_down', 'Check if a specific AI service is currently down or experiencing issues.', {
97
+ registerTool('is_service_down', 'Check if a specific AI service is currently down or experiencing issues.', {
79
98
  service: z.string().describe('Service name to check (e.g. "claude", "openai", "gemini", "mistral", "cohere", "hugging face", "replicate")'),
80
99
  }, async ({ service }) => {
81
100
  const data = await fetchJSON('/status');
@@ -96,7 +115,7 @@ server.tool('is_service_down', 'Check if a specific AI service is currently down
96
115
  };
97
116
  });
98
117
  // ── Tool: get_model_pricing ─────────────────────────────────────────
99
- server.tool('get_model_pricing', 'Get AI model pricing comparison across all major providers (Anthropic, OpenAI, Google, Meta, Mistral, Cohere). Prices per 1M tokens.', {}, async () => {
118
+ registerTool('get_model_pricing', 'Get AI model pricing comparison across all major providers (Anthropic, OpenAI, Google, Meta, Mistral, Cohere). Prices per 1M tokens.', {}, async () => {
100
119
  const data = await fetchJSON('/models');
101
120
  const text = data.providers
102
121
  .map(p => {
@@ -116,7 +135,7 @@ server.tool('get_model_pricing', 'Get AI model pricing comparison across all maj
116
135
  return { content: [{ type: 'text', text: `AI Model Pricing (per 1M tokens):\n\n${text}` }] };
117
136
  });
118
137
  // ── Tool: get_ai_today ──────────────────────────────────────────────
119
- server.tool('get_ai_today', 'Get a summary of what happened in AI today. Returns the top stories from the last 24 hours.', {
138
+ registerTool('get_ai_today', 'Get a summary of what happened in AI today. Returns the top stories from the last 24 hours.', {
120
139
  limit: z.number().min(1).max(20).optional().describe('Number of stories (default 5)'),
121
140
  }, async ({ limit }) => {
122
141
  const data = await fetchJSON(`/news?limit=${limit || 5}`);
@@ -126,7 +145,7 @@ server.tool('get_ai_today', 'Get a summary of what happened in AI today. Returns
126
145
  return { content: [{ type: 'text', text: `Today in AI:\n\n${text}` }] };
127
146
  });
128
147
  // ── Tool: get_agent_activity ────────────────────────────────────────
129
- server.tool('get_agent_activity', 'Get live AI bot traffic on TensorFeed.ai. Returns today\'s bot hit count, the most recent 50 hits with bot name + endpoint + timestamp, and a derived top-bots breakdown. Useful when an agent wants to see which crawlers are pulling AI ecosystem data and how that distribution shifts. Free, no auth.', {}, async () => {
148
+ registerTool('get_agent_activity', 'Get live AI bot traffic on TensorFeed.ai. Returns today\'s bot hit count, the most recent 50 hits with bot name + endpoint + timestamp, and a derived top-bots breakdown. Useful when an agent wants to see which crawlers are pulling AI ecosystem data and how that distribution shifts. Free, no auth.', {}, async () => {
130
149
  const data = (await fetchJSON('/agents/activity'));
131
150
  const tally = new Map();
132
151
  for (const hit of data.recent) {
@@ -158,7 +177,7 @@ server.tool('get_agent_activity', 'Get live AI bot traffic on TensorFeed.ai. Ret
158
177
  // PREMIUM TOOLS (require TENSORFEED_TOKEN env var, paid in USDC on Base)
159
178
  // ════════════════════════════════════════════════════════════════════
160
179
  // ── Tool: get_account_balance ───────────────────────────────────────
161
- server.tool('get_account_balance', 'Check the credit balance for the configured TensorFeed bearer token. Free, but requires TENSORFEED_TOKEN to be set.', {}, async () => {
180
+ registerTool('get_account_balance', 'Check the credit balance for the configured TensorFeed bearer token. Free, but requires TENSORFEED_TOKEN to be set.', {}, async () => {
162
181
  const data = (await fetchJSON('/payment/balance', { auth: true }));
163
182
  return {
164
183
  content: [
@@ -170,7 +189,7 @@ server.tool('get_account_balance', 'Check the credit balance for the configured
170
189
  };
171
190
  });
172
191
  // ── Tool: get_account_usage ─────────────────────────────────────────
173
- server.tool('get_account_usage', 'Show per-endpoint usage for the configured TensorFeed token (last 100 calls aggregated). Free, but requires TENSORFEED_TOKEN.', {}, async () => {
192
+ registerTool('get_account_usage', 'Show per-endpoint usage for the configured TensorFeed token (last 100 calls aggregated). Free, but requires TENSORFEED_TOKEN.', {}, async () => {
174
193
  const data = (await fetchJSON('/payment/usage', { auth: true }));
175
194
  if (data.total_calls === 0) {
176
195
  return { content: [{ type: 'text', text: 'No premium API calls on this token yet.' }] };
@@ -189,7 +208,7 @@ server.tool('get_account_usage', 'Show per-endpoint usage for the configured Ten
189
208
  };
190
209
  });
191
210
  // ── Tool: premium_routing (1 credit) ────────────────────────────────
192
- server.tool('premium_routing', 'Get a ranked list of recommended AI models for a task with full score breakdown (quality, availability, cost, latency). Costs 1 credit.', {
211
+ registerTool('premium_routing', 'Get a ranked list of recommended AI models for a task with full score breakdown (quality, availability, cost, latency). Costs 1 credit.', {
193
212
  task: z.enum(['code', 'reasoning', 'creative', 'general']).optional().describe('Task type the model needs to be good at (default: general)'),
194
213
  budget: z.number().optional().describe('Max blended USD per 1M tokens'),
195
214
  min_quality: z.number().min(0).max(1).optional().describe('Minimum quality score in [0, 1]'),
@@ -217,7 +236,7 @@ server.tool('premium_routing', 'Get a ranked list of recommended AI models for a
217
236
  return { content: [{ type: 'text', text: `Routing for "${data.task}":\n\n${list}${billing}` }] };
218
237
  });
219
238
  // ── Tool: pricing_series (1 credit) ─────────────────────────────────
220
- server.tool('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.', {
239
+ 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.', {
221
240
  model: z.string().describe('Model id or display name (e.g. "Claude Opus 4.7" or "claude-opus-4-7")'),
222
241
  from: z.string().optional().describe('Start date YYYY-MM-DD UTC (default: 30 days ago)'),
223
242
  to: z.string().optional().describe('End date YYYY-MM-DD UTC (default: today)'),
@@ -242,7 +261,7 @@ server.tool('pricing_series', 'Daily price points for one AI model with min/max/
242
261
  };
243
262
  });
244
263
  // ── Tool: benchmark_series (1 credit) ───────────────────────────────
245
- server.tool('benchmark_series', 'Score evolution for a single benchmark on one AI model. Costs 1 credit. Benchmark keys: swe_bench, mmlu_pro, gpqa_diamond, math, human_eval.', {
264
+ registerTool('benchmark_series', 'Score evolution for a single benchmark on one AI model. Costs 1 credit. Benchmark keys: swe_bench, mmlu_pro, gpqa_diamond, math, human_eval.', {
246
265
  model: z.string().describe('Model id or display name'),
247
266
  benchmark: z.string().describe('Benchmark key (e.g. swe_bench, mmlu_pro, gpqa_diamond, math, human_eval)'),
248
267
  from: z.string().optional().describe('Start date YYYY-MM-DD UTC (default: 30 days ago)'),
@@ -268,7 +287,7 @@ server.tool('benchmark_series', 'Score evolution for a single benchmark on one A
268
287
  };
269
288
  });
270
289
  // ── Tool: status_uptime (1 credit) ──────────────────────────────────
271
- server.tool('status_uptime', 'Daily uptime rollup for one provider with operational/degraded/down day counts and uptime % (degraded counts as half-credit). Costs 1 credit.', {
290
+ 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.', {
272
291
  provider: z.string().describe('Provider name (e.g. anthropic, openai, google)'),
273
292
  from: z.string().optional().describe('Start date YYYY-MM-DD UTC (default: 30 days ago)'),
274
293
  to: z.string().optional().describe('End date YYYY-MM-DD UTC (default: today)'),
@@ -295,7 +314,7 @@ server.tool('status_uptime', 'Daily uptime rollup for one provider with operatio
295
314
  };
296
315
  });
297
316
  // ── Tool: premium_agents_directory (1 credit) ───────────────────────
298
- server.tool('premium_agents_directory', 'Enriched AI agents catalog joined with live status, recent news, agent traffic, flagship pricing, and a 0-100 trending_score. Costs 1 credit.', {
317
+ registerTool('premium_agents_directory', 'Enriched AI agents catalog joined with live status, recent news, agent traffic, flagship pricing, and a 0-100 trending_score. Costs 1 credit.', {
299
318
  category: z.string().optional().describe('coding, research, general, creative, frameworks'),
300
319
  status: z.enum(['operational', 'degraded', 'down', 'unknown']).optional(),
301
320
  open_source: z.boolean().optional(),
@@ -328,7 +347,7 @@ server.tool('premium_agents_directory', 'Enriched AI agents catalog joined with
328
347
  };
329
348
  });
330
349
  // ── Tool: whats_new (1 credit) ──────────────────────────────────────
331
- server.tool('whats_new', 'Agent morning brief: pricing changes, new/removed models, status incidents, and top news from the last 1-7 days. The tool to call when your agent boots up. Costs 1 credit.', {
350
+ registerTool('whats_new', 'Agent morning brief: pricing changes, new/removed models, status incidents, and top news from the last 1-7 days. The tool to call when your agent boots up. Costs 1 credit.', {
332
351
  days: z.number().min(1).max(7).optional().describe('Window length in days (default 1)'),
333
352
  news_limit: z.number().min(1).max(25).optional().describe('Max news headlines (default 10)'),
334
353
  }, async ({ days, news_limit }) => {
@@ -364,7 +383,7 @@ server.tool('whats_new', 'Agent morning brief: pricing changes, new/removed mode
364
383
  };
365
384
  });
366
385
  // ── Tool: compare_models (1 credit) ─────────────────────────────────
367
- server.tool('compare_models', 'Side-by-side comparison of 2-5 AI models. Returns pricing, benchmarks (normalized to a union of keys with null for missing scores), provider status, and recent news per model, plus rankings (cheapest blended, most context, per-benchmark leaderboard). Costs 1 credit.', {
386
+ registerTool('compare_models', 'Side-by-side comparison of 2-5 AI models. Returns pricing, benchmarks (normalized to a union of keys with null for missing scores), provider status, and recent news per model, plus rankings (cheapest blended, most context, per-benchmark leaderboard). Costs 1 credit.', {
368
387
  ids: z.string().describe('Comma-separated list of 2-5 model ids or display names. Examples: "Claude Opus 4.7,GPT-5.5,Gemini 3" or "opus-4-7,gpt-5-5"'),
369
388
  }, async ({ ids }) => {
370
389
  const data = (await fetchJSON(`/premium/compare/models?ids=${encodeURIComponent(ids)}`, { auth: true }));
@@ -391,7 +410,7 @@ server.tool('compare_models', 'Side-by-side comparison of 2-5 AI models. Returns
391
410
  };
392
411
  });
393
412
  // ── Tool: provider_deepdive (1 credit) ──────────────────────────────
394
- server.tool('provider_deepdive', 'Everything about an AI provider in one call: live status, all models with pricing + tier + benchmark scores joined in, recent news mentions, and agent traffic. Costs 1 credit. Aggregation IS the value; doing this from free endpoints would take 4 round-trips.', {
413
+ registerTool('provider_deepdive', 'Everything about an AI provider in one call: live status, all models with pricing + tier + benchmark scores joined in, recent news mentions, and agent traffic. Costs 1 credit. Aggregation IS the value; doing this from free endpoints would take 4 round-trips.', {
395
414
  provider: z.string().describe('Provider id or display name (case-insensitive). Examples: anthropic, openai, google, mistral, cohere'),
396
415
  }, async ({ provider }) => {
397
416
  const data = (await fetchJSON(`/premium/providers/${encodeURIComponent(provider)}`, { auth: true }));
@@ -421,7 +440,7 @@ server.tool('provider_deepdive', 'Everything about an AI provider in one call: l
421
440
  };
422
441
  });
423
442
  // ── Tool: cost_projection (1 credit) ────────────────────────────────
424
- server.tool('cost_projection', 'Project the cost of a token-usage workload across 1-10 AI models. Returns daily/weekly/monthly/yearly totals per model and a ranking by cheapest monthly. Costs 1 credit.', {
443
+ registerTool('cost_projection', 'Project the cost of a token-usage workload across 1-10 AI models. Returns daily/weekly/monthly/yearly totals per model and a ranking by cheapest monthly. Costs 1 credit.', {
425
444
  models: z.string().describe('One model or comma-separated list of up to 10 (e.g. "Claude Opus 4.7,GPT-5.5,Gemini 3"). Names or ids both work.'),
426
445
  input_tokens_per_day: z.number().min(0).describe('Expected daily input token volume'),
427
446
  output_tokens_per_day: z.number().min(0).describe('Expected daily output token volume'),
@@ -456,7 +475,7 @@ server.tool('cost_projection', 'Project the cost of a token-usage workload acros
456
475
  };
457
476
  });
458
477
  // ── Tool: news_search (1 credit) ────────────────────────────────────
459
- server.tool('news_search', 'Full-text search over the TensorFeed news article corpus with optional date range, provider, and category filters. Relevance scoring with recency boost. Costs 1 credit.', {
478
+ registerTool('news_search', 'Full-text search over the TensorFeed news article corpus with optional date range, provider, and category filters. Relevance scoring with recency boost. Costs 1 credit.', {
460
479
  q: z.string().optional().describe('Free-text query, e.g. "claude opus pricing". Omit to browse latest filtered articles.'),
461
480
  from: z.string().optional().describe('Start date YYYY-MM-DD UTC (inclusive)'),
462
481
  to: z.string().optional().describe('End date YYYY-MM-DD UTC (inclusive end-of-day)'),
@@ -494,7 +513,7 @@ server.tool('news_search', 'Full-text search over the TensorFeed news article co
494
513
  };
495
514
  });
496
515
  // ── Tool: list_watches ──────────────────────────────────────────────
497
- server.tool('list_watches', 'List the active webhook watches owned by the configured TensorFeed token. Free, requires TENSORFEED_TOKEN.', {}, async () => {
516
+ registerTool('list_watches', 'List the active webhook watches owned by the configured TensorFeed token. Free, requires TENSORFEED_TOKEN.', {}, async () => {
498
517
  const data = (await fetchJSON('/premium/watches', { auth: true }));
499
518
  if (data.count === 0) {
500
519
  return { content: [{ type: 'text', text: 'No active watches.' }] };
@@ -510,7 +529,7 @@ server.tool('list_watches', 'List the active webhook watches owned by the config
510
529
  return { content: [{ type: 'text', text: `${data.count} active watches:\n\n${rows}` }] };
511
530
  });
512
531
  // ── Tool: create_price_watch (1 credit) ─────────────────────────────
513
- server.tool('create_price_watch', 'Register a webhook watch on a model price change. Costs 1 credit. Watch lives 90 days. Each fire is an HMAC-signed POST to callback_url.', {
532
+ registerTool('create_price_watch', 'Register a webhook watch on a model price change. Costs 1 credit. Watch lives 90 days. Each fire is an HMAC-signed POST to callback_url.', {
514
533
  model: z.string().describe('Model name (e.g. "Claude Opus 4.7")'),
515
534
  field: z.enum(['inputPrice', 'outputPrice', 'blended']).describe('Which price field to watch'),
516
535
  op: z.enum(['lt', 'gt', 'changes']).describe('Trigger: lt = below threshold, gt = above, changes = any change'),
@@ -535,7 +554,7 @@ server.tool('create_price_watch', 'Register a webhook watch on a model price cha
535
554
  };
536
555
  });
537
556
  // ── Tool: create_status_watch (1 credit) ────────────────────────────
538
- server.tool('create_status_watch', 'Register a webhook watch on a service status transition (e.g. anthropic becomes down). Costs 1 credit. Watch lives 90 days.', {
557
+ registerTool('create_status_watch', 'Register a webhook watch on a service status transition (e.g. anthropic becomes down). Costs 1 credit. Watch lives 90 days.', {
539
558
  provider: z.string().describe('Provider name (e.g. anthropic, openai)'),
540
559
  op: z.enum(['becomes', 'changes']).describe('becomes = transitions to a specific value; changes = any transition'),
541
560
  value: z.enum(['operational', 'degraded', 'down']).optional().describe('Required when op is becomes'),
@@ -559,7 +578,7 @@ server.tool('create_status_watch', 'Register a webhook watch on a service status
559
578
  };
560
579
  });
561
580
  // ── Tool: create_digest_watch (1 credit) ────────────────────────────
562
- server.tool('create_digest_watch', 'Register a scheduled digest webhook that fires daily or weekly with a curated summary of pricing changes (regardless of whether anything dramatic happened). Costs 1 credit. Watch lives 90 days. Set-and-forget for agents that want a periodic snapshot without subscribing to realtime transitions.', {
581
+ registerTool('create_digest_watch', 'Register a scheduled digest webhook that fires daily or weekly with a curated summary of pricing changes (regardless of whether anything dramatic happened). Costs 1 credit. Watch lives 90 days. Set-and-forget for agents that want a periodic snapshot without subscribing to realtime transitions.', {
563
582
  cadence: z.enum(['daily', 'weekly']).describe('How often the digest fires'),
564
583
  callback_url: z.string().describe('HTTPS URL to POST to when the digest fires'),
565
584
  secret: z.string().optional().describe('Optional HMAC shared secret'),
@@ -581,14 +600,14 @@ server.tool('create_digest_watch', 'Register a scheduled digest webhook that fir
581
600
  };
582
601
  });
583
602
  // ── Tool: delete_watch ──────────────────────────────────────────────
584
- server.tool('delete_watch', 'Delete one of your active webhook watches by id. Free, requires TENSORFEED_TOKEN.', {
603
+ registerTool('delete_watch', 'Delete one of your active webhook watches by id. Free, requires TENSORFEED_TOKEN.', {
585
604
  watch_id: z.string().describe('The wat_... id from create_price_watch / create_status_watch / list_watches'),
586
605
  }, async ({ watch_id }) => {
587
606
  await fetchJSON(`/premium/watches/${watch_id}`, { method: 'DELETE', auth: true });
588
607
  return { content: [{ type: 'text', text: `Deleted watch ${watch_id}.` }] };
589
608
  });
590
609
  // ── Tool: probe_latest (free) ───────────────────────────────────────
591
- server.tool('probe_latest', 'Last 24 hours of measured LLM endpoint latency and availability per provider (Anthropic, OpenAI, Google, Mistral, Cohere). TensorFeed pings each provider\'s chat completion endpoint every 15 min and records time-to-first-byte, total response time, and HTTP status. Returns per-provider success rate and ttfb/total p50/p95/p99 latency. The data is unique because we measure it ourselves, not self-reported by the providers. Useful when an agent needs to pick a provider whose SLA you can verify, or to detect ongoing incidents before they hit a status page. Free, no auth.', {}, async () => {
610
+ registerTool('probe_latest', 'Last 24 hours of measured LLM endpoint latency and availability per provider (Anthropic, OpenAI, Google, Mistral, Cohere). TensorFeed pings each provider\'s chat completion endpoint every 15 min and records time-to-first-byte, total response time, and HTTP status. Returns per-provider success rate and ttfb/total p50/p95/p99 latency. The data is unique because we measure it ourselves, not self-reported by the providers. Useful when an agent needs to pick a provider whose SLA you can verify, or to detect ongoing incidents before they hit a status page. Free, no auth.', {}, async () => {
592
611
  const data = (await fetchJSON('/probe/latest'));
593
612
  const s = data.summary;
594
613
  if (s.providers.length === 0) {
@@ -614,7 +633,7 @@ server.tool('probe_latest', 'Last 24 hours of measured LLM endpoint latency and
614
633
  };
615
634
  });
616
635
  // ── Tool: probe_series (1 credit) ───────────────────────────────────
617
- server.tool('probe_series', 'Daily SLA series for one LLM provider, measured by TensorFeed. Returns per-day count, success rate, ttfb p50/p95/p99, total p50/p95/p99, and incident-hour count across the requested window. Provider status pages are politically managed; this is the measured truth. Pairs naturally with premium_routing for picking a model whose SLA you can verify. Costs 1 credit.', {
636
+ registerTool('probe_series', 'Daily SLA series for one LLM provider, measured by TensorFeed. Returns per-day count, success rate, ttfb p50/p95/p99, total p50/p95/p99, and incident-hour count across the requested window. Provider status pages are politically managed; this is the measured truth. Pairs naturally with premium_routing for picking a model whose SLA you can verify. Costs 1 credit.', {
618
637
  provider: z.enum(['anthropic', 'openai', 'google', 'mistral', 'cohere']).describe('LLM provider key'),
619
638
  from: z.string().optional().describe('Inclusive start YYYY-MM-DD (default: 30 days before to)'),
620
639
  to: z.string().optional().describe('Inclusive end YYYY-MM-DD (default: today UTC)'),
@@ -645,7 +664,7 @@ server.tool('probe_series', 'Daily SLA series for one LLM provider, measured by
645
664
  };
646
665
  });
647
666
  // ── Tool: mcp_registry_snapshot (free) ──────────────────────────────
648
- server.tool('mcp_registry_snapshot', 'Today\'s summary of the official Model Context Protocol server registry. Returns total servers, by-status breakdown, top namespaces, and 1-day deltas (newly added, reactivated, deprecated). Captured daily at 9:30 AM UTC from registry.modelcontextprotocol.io. Useful when an agent wants to see how the MCP ecosystem is growing or detect freshly-deprecated servers it may be using. Free, no auth.', {}, async () => {
667
+ registerTool('mcp_registry_snapshot', 'Today\'s summary of the official Model Context Protocol server registry. Returns total servers, by-status breakdown, top namespaces, and 1-day deltas (newly added, reactivated, deprecated). Captured daily at 9:30 AM UTC from registry.modelcontextprotocol.io. Useful when an agent wants to see how the MCP ecosystem is growing or detect freshly-deprecated servers it may be using. Free, no auth.', {}, async () => {
649
668
  const data = (await fetchJSON('/mcp/registry/snapshot'));
650
669
  const s = data.summary;
651
670
  const statusLine = Object.entries(s.by_status)
@@ -676,7 +695,7 @@ server.tool('mcp_registry_snapshot', 'Today\'s summary of the official Model Con
676
695
  };
677
696
  });
678
697
  // ── Tool: mcp_registry_series (1 credit) ────────────────────────────
679
- server.tool('mcp_registry_series', 'Multi-day series of MCP server registry growth and churn. Returns per-day total servers, active count, and daily added/removed counts across the requested window. The registry itself is open data, but a 30/90-day trend requires daily capture started weeks ago, which TensorFeed has been running. Costs 1 credit.', {
698
+ registerTool('mcp_registry_series', 'Multi-day series of MCP server registry growth and churn. Returns per-day total servers, active count, and daily added/removed counts across the requested window. The registry itself is open data, but a 30/90-day trend requires daily capture started weeks ago, which TensorFeed has been running. Costs 1 credit.', {
680
699
  from: z.string().optional().describe('Inclusive start date YYYY-MM-DD (default: 30 days before to)'),
681
700
  to: z.string().optional().describe('Inclusive end date YYYY-MM-DD (default: today UTC)'),
682
701
  }, async ({ from, to }) => {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Defense-in-depth output scrub for tool responses.
3
+ *
4
+ * The MCP server returns text from the TensorFeed worker into a host LLM
5
+ * (Claude Desktop, Cursor, Cline, Roo, etc). Any text we forward becomes
6
+ * untrusted input to that downstream model. The worker already runs
7
+ * sanitize.ts on its agent-facing endpoints, but this layer protects
8
+ * against:
9
+ *
10
+ * 1. New worker endpoints that forget the worker-side scrub
11
+ * 2. Future tools that aggregate or reformat upstream text
12
+ * 3. Drift between the worker's sanitization rules and what the MCP
13
+ * server should consider safe
14
+ *
15
+ * The rule set mirrors worker/src/sanitize.ts:
16
+ * - Strip ASCII control chars (preserves \n \r \t)
17
+ * - Strip bidi controls and zero-width chars used in spoofing
18
+ * - Neutralize role-confusion markers (ChatML / Llama / Mistral chat
19
+ * tokens, jailbreak preamble lines, "ignore previous instructions")
20
+ * - Collapse runs of whitespace
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.
24
+ */
25
+ /**
26
+ * Scrub a single string for return to a host LLM. Idempotent and pure.
27
+ */
28
+ export declare function sanitizeForLLM(input: string): string;
29
+ /**
30
+ * Recursively scrub the `text` field of every text-content item in an
31
+ * MCP tool response. Non-text items (resources, images) pass through
32
+ * untouched. Returns a new content array; the input is not mutated.
33
+ */
34
+ export declare function sanitizeToolResponse<T extends {
35
+ content: Array<{
36
+ type: string;
37
+ text?: string;
38
+ [k: string]: unknown;
39
+ }>;
40
+ }>(response: T): T;
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tensorfeed/mcp-server",
3
- "version": "1.11.0",
3
+ "version": "1.12.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",
@@ -19,13 +19,29 @@
19
19
  "build": "tsc",
20
20
  "start": "node dist/index.js"
21
21
  },
22
- "keywords": ["mcp", "ai", "news", "tensorfeed", "model-context-protocol", "claude", "agents", "x402", "usdc", "base", "model-routing", "agent-payments"],
22
+ "keywords": [
23
+ "mcp",
24
+ "ai",
25
+ "news",
26
+ "tensorfeed",
27
+ "model-context-protocol",
28
+ "claude",
29
+ "agents",
30
+ "x402",
31
+ "usdc",
32
+ "base",
33
+ "model-routing",
34
+ "agent-payments"
35
+ ],
23
36
  "author": "TensorFeed.ai",
24
37
  "license": "MIT",
25
38
  "homepage": "https://tensorfeed.ai/developers",
26
39
  "repository": {
27
40
  "type": "git",
28
- "url": "https://github.com/RipperMercs/tensorfeed"
41
+ "url": "https://github.com/RipperMercs/tensorfeed-mcp"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/RipperMercs/tensorfeed-mcp/issues"
29
45
  },
30
46
  "dependencies": {
31
47
  "@modelcontextprotocol/sdk": "^1.28.0",
package/server.json CHANGED
@@ -3,19 +3,18 @@
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.11.0",
6
+ "version": "1.12.0",
7
7
  "websiteUrl": "https://tensorfeed.ai/developers/agent-payments",
8
8
  "repository": {
9
- "url": "https://github.com/RipperMercs/tensorfeed",
10
- "source": "github",
11
- "subfolder": "mcp-server"
9
+ "url": "https://github.com/RipperMercs/tensorfeed-mcp",
10
+ "source": "github"
12
11
  },
13
12
  "packages": [
14
13
  {
15
14
  "registryType": "npm",
16
15
  "registryBaseUrl": "https://registry.npmjs.org",
17
16
  "identifier": "@tensorfeed/mcp-server",
18
- "version": "1.11.0",
17
+ "version": "1.12.0",
19
18
  "runtimeHint": "npx",
20
19
  "transport": {
21
20
  "type": "stdio"