stock-scanner-mcp 1.16.3 → 1.17.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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  A modular MCP server for Claude Code and Claude Desktop that provides real-time access to stock and crypto market data. Scan markets, check technicals, monitor insider trades, track earnings, analyze options flow, and optionally save your own watchlists and thesis notes from one server.
8
8
 
9
- **64 tools** across **13 modules** — 9 modules work with zero API keys, including an optional stateful Market Workspace.
9
+ **65 tools** across **13 modules** — 9 modules work with zero API keys, including an optional stateful Market Workspace.
10
10
 
11
11
  ## Quick Start
12
12
 
@@ -97,7 +97,7 @@ Once answered, it saves your profile and creates a `core` watchlist:
97
97
 
98
98
  You can also skip the skill and ask Claude directly: *"Set up my workspace — I'm a swing trader, create a core watchlist with MARA, HOOD, BTC, daily reviews."*
99
99
 
100
- **That's it.** You now have 64 tools, 19 skills, and a personalized workspace. Try `/workspace-morning-brief` for your first tailored market scan.
100
+ **That's it.** You now have 65 tools, 19 skills, and a personalized workspace. Try `/workspace-morning-brief` for your first tailored market scan.
101
101
 
102
102
  ## What You Can Do
103
103
 
@@ -208,6 +208,14 @@ Data is stored locally in `workspace.json` — no cloud sync, no external calls.
208
208
 
209
209
  For the full list of workspace tools, see the [tool reference](#workspace--personalized-context-optional-no-api-key) below.
210
210
 
211
+ ## Documentation
212
+
213
+ - **[Installation & Setup](https://github.com/yyordanov-tradu/stock-scanner-mcp/wiki/Installation-&-Setup)** — Getting the MCP server running in Claude Code or Cursor
214
+ - **[Trading Skills](https://github.com/yyordanov-tradu/stock-scanner-mcp/wiki/Trading-Skills)** — Ready-made workflows for professional analysis
215
+ - **[Sidecar HTTP API](https://github.com/yyordanov-tradu/stock-scanner-mcp/wiki/Sidecar-HTTP-API)** — REST endpoints for non-MCP clients
216
+ - **[Docker Deployment](docs/DOCKER.md)** — Running in containers with workspace persistence
217
+ - **[FAQ & Troubleshooting](https://github.com/yyordanov-tradu/stock-scanner-mcp/wiki/FAQ-&-Troubleshooting)** — Common issues and solutions
218
+
211
219
  ## Modules
212
220
 
213
221
  | Module | Tools | API Key | Description |
@@ -220,7 +228,7 @@ For the full list of workspace tools, see the [tool reference](#workspace--perso
220
228
  | options-cboe | 1 | None | CBOE put/call ratio sentiment indicator |
221
229
  | sentiment | 2 | None | CNN Fear & Greed Index, Crypto Fear & Greed Index |
222
230
  | frankfurter | 5 | None | Forex exchange rates — 31 currencies from ECB (daily reference rates) |
223
- | reddit | 3 | None | Reddit trending tickers, mention tracking, and sentiment from r/wallstreetbets, r/stocks, r/investing, r/options |
231
+ | reddit | 4 | None | Reddit trending tickers, mention tracking, sentiment, and batch watchlist scan from r/wallstreetbets, r/stocks, r/investing, r/options |
224
232
  | workspace | 7 | None | Optional stateful profile, watchlists, and thesis tracking for personalized workflows (`--enable-workspace`) |
225
233
  | finnhub | 9 | `FINNHUB_API_KEY` | Quotes, news, earnings, analyst ratings, short interest |
226
234
  | alpha-vantage | 5 | `ALPHA_VANTAGE_API_KEY` | Quotes, daily prices, fundamentals, earnings, dividends |
@@ -230,7 +238,7 @@ Modules auto-enable when their API key is set. No-key modules are always enabled
230
238
 
231
239
  For a complete list of every tool with descriptions, see the [Full Tool Reference](#full-tool-reference-64-tools) below.
232
240
 
233
- ## Full Tool Reference (64 tools)
241
+ ## Full Tool Reference (65 tools)
234
242
 
235
243
  ### TradingView — Stock Scanning (no API key)
236
244
 
@@ -315,6 +323,7 @@ For a complete list of every tool with descriptions, see the [Full Tool Referenc
315
323
  | `reddit_trending` | Trending stock tickers from Reddit by mention frequency across r/wallstreetbets, r/stocks, r/investing, r/options |
316
324
  | `reddit_mentions` | Mention count and top posts for a specific ticker across Reddit investing subreddits |
317
325
  | `reddit_sentiment` | Keyword-based sentiment analysis (bullish/bearish/neutral) for a ticker from Reddit discussions |
326
+ | `reddit_watchlist_scan` | Batch Reddit scan for a list of tickers in one pass (combined OR query, ceil(N/20)×4 requests) — per-ticker mentions, sentiment, top post, and a hot flag (≥5 mentions) |
318
327
 
319
328
  ### Workspace — Personalized Context (optional, no API key)
320
329
 
@@ -399,14 +408,16 @@ An optional HTTP server exposing all tools as REST endpoints for non-MCP integra
399
408
  npx stock-scanner-sidecar # Start on port 3200
400
409
  npx stock-scanner-sidecar --port 8080 # Custom port
401
410
 
411
+ # Optional: Enable the stateful Market Workspace via HTTP
412
+ npx stock-scanner-sidecar --enable-workspace --data-dir ./my-data
413
+
402
414
  # Access the OpenAPI spec
403
415
  curl http://localhost:3200/openapi.json
404
416
  ```
405
417
 
406
- See [Sidecar HTTP API](wiki-repo/Sidecar-HTTP-API.md) for endpoint details.
407
- ```
418
+ See [Sidecar HTTP API](https://github.com/yyordanov-tradu/stock-scanner-mcp/wiki/Sidecar-HTTP-API) for endpoint details.
408
419
 
409
- 55 endpoints including `/tradingview/quote`, `/options/chain`, `/frankfurter/convert`, and more. See [wiki](https://github.com/yyordanov-tradu/stock-scanner-mcp/wiki/Sidecar-HTTP-API) for the full route table.
420
+ **65 tools** exposed as REST routes, including `/tradingview/quote`, `/options/chain`, `/workspace/profile`, and more.
410
421
 
411
422
  ## Rate Limits
412
423
 
@@ -459,10 +470,11 @@ src/
459
470
  │ ├── fred/ # 4 tools — economic calendar, indicators, historical data
460
471
  │ ├── sentiment/ # 2 tools — Fear & Greed indexes (market + crypto)
461
472
  │ ├── frankfurter/ # 5 tools — forex exchange rates (ECB, 31 currencies)
462
- │ └── reddit/ # 3 tools — trending tickers, mentions, sentiment from Reddit
473
+ │ └── reddit/ # 4 tools — trending tickers, mentions, sentiment, watchlist scan from Reddit
463
474
  ├── sidecar/
464
475
  │ ├── index.ts # HTTP sidecar entry point (port 3200)
465
- └── server.ts # HTTP request handler (55 endpoints)
476
+ ├── routes.ts # Declarative URL routing table
477
+ │ └── server.ts # Dynamic HTTP request handler
466
478
  └── shared/
467
479
  ├── http.ts # HTTP client with timeouts and key sanitization
468
480
  ├── cache.ts # In-memory TTL cache
@@ -2978,6 +2978,7 @@ var DEFAULT_SUBREDDITS = [
2978
2978
  ];
2979
2979
  var trendingCache = new TtlCache(5 * 60 * 1e3);
2980
2980
  var mentionCache = new TtlCache(2 * 60 * 1e3);
2981
+ var watchlistCache = new TtlCache(5 * 60 * 1e3);
2981
2982
  var STOP_WORDS = /* @__PURE__ */ new Set([
2982
2983
  // 1-2 letter
2983
2984
  "I",
@@ -3466,6 +3467,75 @@ async function getTickerMentions(symbol, period = "week") {
3466
3467
  mentionCache.set(cacheKey, result);
3467
3468
  return result;
3468
3469
  }
3470
+ var WATCHLIST_BATCH_SIZE = 20;
3471
+ var HOT_THRESHOLD = 5;
3472
+ async function scanWatchlist(symbols, period = "day") {
3473
+ const upperSymbols = symbols.map((s) => s.toUpperCase());
3474
+ const cacheKey = `watchlist:${[...upperSymbols].sort().join(",")}:${period}`;
3475
+ const cached = watchlistCache.get(cacheKey);
3476
+ if (cached) return cached;
3477
+ const tickerData = /* @__PURE__ */ new Map();
3478
+ for (const sym of upperSymbols) {
3479
+ tickerData.set(sym, { mentions: 0, bullish: 0, bearish: 0, neutral: 0, scoreSum: 0, topPost: null });
3480
+ }
3481
+ const batches = [];
3482
+ for (let i = 0; i < upperSymbols.length; i += WATCHLIST_BATCH_SIZE) {
3483
+ batches.push(upperSymbols.slice(i, i + WATCHLIST_BATCH_SIZE));
3484
+ }
3485
+ let requestsMade = 0;
3486
+ for (const batch of batches) {
3487
+ const orQuery = batch.join(" OR ");
3488
+ const settled = await Promise.allSettled(
3489
+ DEFAULT_SUBREDDITS.map((sub) => fetchSubredditPosts(sub, orQuery, period, 100))
3490
+ );
3491
+ for (const outcome of settled) {
3492
+ if (outcome.status !== "fulfilled") continue;
3493
+ requestsMade += 1;
3494
+ const posts = outcome.value;
3495
+ for (const post of posts) {
3496
+ const text = `${post.title} ${post.selftext}`;
3497
+ const matched = extractTickers(text).filter((t) => tickerData.has(t));
3498
+ if (matched.length === 0) continue;
3499
+ const sentScore = scoreSentiment(text);
3500
+ for (const sym of matched) {
3501
+ const acc = tickerData.get(sym);
3502
+ acc.mentions += 1;
3503
+ if (sentScore > 0) acc.bullish += 1;
3504
+ else if (sentScore < 0) acc.bearish += 1;
3505
+ else acc.neutral += 1;
3506
+ acc.scoreSum += sentScore;
3507
+ if (!acc.topPost || post.score > acc.topPost.score) {
3508
+ acc.topPost = { title: post.title, score: post.score, subreddit: post.subreddit };
3509
+ }
3510
+ }
3511
+ }
3512
+ }
3513
+ }
3514
+ const tickers = upperSymbols.map((sym) => {
3515
+ const acc = tickerData.get(sym);
3516
+ const total = acc.bullish + acc.bearish + acc.neutral;
3517
+ return {
3518
+ symbol: sym,
3519
+ mentions: acc.mentions,
3520
+ sentiment: {
3521
+ bullish: acc.bullish,
3522
+ bearish: acc.bearish,
3523
+ neutral: acc.neutral,
3524
+ score: total > 0 ? Math.round(acc.scoreSum / total * 100) / 100 : 0
3525
+ },
3526
+ topPost: acc.topPost,
3527
+ hot: acc.mentions >= HOT_THRESHOLD
3528
+ };
3529
+ }).sort((a, b) => b.mentions - a.mentions);
3530
+ const result = {
3531
+ scanned: upperSymbols.length,
3532
+ subredditsChecked: [...DEFAULT_SUBREDDITS],
3533
+ requestsMade,
3534
+ tickers
3535
+ };
3536
+ watchlistCache.set(cacheKey, result);
3537
+ return result;
3538
+ }
3469
3539
  async function getTickerSentiment(symbol, limit = 50) {
3470
3540
  const cacheKey = `sentiment:${symbol}:${limit}`;
3471
3541
  const cached = mentionCache.get(cacheKey);
@@ -3546,12 +3616,25 @@ var sentimentTool = {
3546
3616
  return successResult(JSON.stringify(result, null, 2));
3547
3617
  }, { source: "reddit", dataDelay: "real-time" })
3548
3618
  };
3619
+ var watchlistScanTool = {
3620
+ name: "reddit_watchlist_scan",
3621
+ description: "Batch Reddit sentiment scan for a list of stock tickers in a single pass. Combines the tickers into one OR query per subreddit (r/wallstreetbets, r/stocks, r/investing, r/options), costing ceil(symbols/20)\xD74 requests instead of one call per ticker \u2014 prefer this over calling reddit_mentions or reddit_sentiment for each symbol in a watchlist. Returns, per ticker: mention count, bullish/bearish/neutral sentiment breakdown with an average score, the top post by upvotes, and a 'hot' flag (true when a ticker has 5 or more mentions in the period). Limitations: keyword-based scoring, not NLP (sarcasm/context may be missed); sentiment is scored per-post, not per-ticker, so a multi-ticker post applies the same score to each matched symbol; symbols that collide with common English words/finance acronyms (e.g. REAL, OPEN, HOLD, SELL) or fall outside 2\u20135 uppercase characters may report zero mentions even when discussed; and because each subreddit's combined query shares one 100-post cap, heavily-discussed tickers in a large batch can crowd out quieter ones, so counts may run lower than a single-ticker reddit_mentions query. Results cached for 5 minutes.",
3622
+ inputSchema: z12.object({
3623
+ symbols: z12.array(z12.string().max(10)).min(1).max(50).describe("Watchlist stock tickers to scan, e.g. ['AAPL', 'NVDA', 'TSLA'] (1\u201350 symbols)"),
3624
+ period: z12.enum(["hour", "day", "week"]).default("day").describe("Time window to search Reddit posts (default: day)")
3625
+ }),
3626
+ readOnly: true,
3627
+ handler: withMetadata(async (args) => {
3628
+ const result = await scanWatchlist(args.symbols, args.period ?? "day");
3629
+ return successResult(JSON.stringify(result, null, 2));
3630
+ }, { source: "reddit", dataDelay: "real-time" })
3631
+ };
3549
3632
  function createRedditModule() {
3550
3633
  return {
3551
3634
  name: "reddit",
3552
3635
  description: "Reddit sentiment \u2014 trending tickers, mention tracking, and sentiment analysis from popular investing subreddits",
3553
3636
  requiredEnvVars: [],
3554
- tools: [trendingTool2, mentionsTool, sentimentTool]
3637
+ tools: [trendingTool2, mentionsTool, sentimentTool, watchlistScanTool]
3555
3638
  };
3556
3639
  }
3557
3640
 
@@ -3975,7 +4058,7 @@ var MODULE_CATALOG = [
3975
4058
  { name: "fred", envVar: "FRED_API_KEY", toolCount: 4, factory: (config) => config.env.FRED_API_KEY ? createFredModule(config.env.FRED_API_KEY) : null },
3976
4059
  { name: "sentiment", envVar: null, toolCount: 2, factory: () => createSentimentModule() },
3977
4060
  { name: "frankfurter", envVar: null, toolCount: 5, factory: () => createFrankfurterModule() },
3978
- { name: "reddit", envVar: null, toolCount: 3, factory: () => createRedditModule() },
4061
+ { name: "reddit", envVar: null, toolCount: 4, factory: () => createRedditModule() },
3979
4062
  { name: "workspace", envVar: null, toolCount: 7, factory: (config) => config.enableWorkspace ? createWorkspaceModule(config.dataDir || path2.join(os.homedir(), ".stock-scanner-mcp"), config.defaultExchange) : null }
3980
4063
  ];
3981
4064
  function resolveEnabledModules(allModules, env, filter) {
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  MODULE_CATALOG,
7
7
  resolveEnabledModules
8
- } from "./chunk-5IWY6HOQ.js";
8
+ } from "./chunk-WRGE3BZV.js";
9
9
 
10
10
  // src/index.ts
11
11
  import { readFileSync } from "fs";
@@ -79,7 +79,7 @@ OPTIONS
79
79
  --enable-workspace Enable stateful workspace (watchlists, theses, profile)
80
80
  --data-dir <path> Directory for workspace storage (default: ~/.stock-scanner-mcp)
81
81
 
82
- MODULES (64 tools total)
82
+ MODULES (65 tools total)
83
83
  tradingview 10 tools Stock scanning, quotes, technicals (no key)
84
84
  tradingview-crypto 4 tools Crypto pair scanning and technicals (no key)
85
85
  sec-edgar 6 tools SEC filings, insider trades, holdings (no key)
@@ -88,7 +88,7 @@ MODULES (64 tools total)
88
88
  options-cboe 1 tool CBOE put/call ratio sentiment (no key)
89
89
  sentiment 2 tools Market & crypto Fear & Greed sentiment (no key)
90
90
  frankfurter 5 tools Forex exchange rates and conversion (no key)
91
- reddit 3 tools Reddit trending tickers and sentiment (no key)
91
+ reddit 4 tools Reddit trending, sentiment, watchlist scan (no key)
92
92
  workspace 7 tools Stateful watchlists, theses, and profile (no key)
93
93
  finnhub 9 tools Quotes, profiles, peers, news, earnings (FINNHUB_API_KEY)
94
94
  alpha-vantage 5 tools Stock quotes, fundamentals, dividends (ALPHA_VANTAGE_API_KEY)
@@ -132,6 +132,11 @@ async function main() {
132
132
  await runInstallSkills(args.slice(1));
133
133
  process.exit(0);
134
134
  }
135
+ if (args.includes("--version") || args.includes("-v")) {
136
+ process.stderr.write(`stock-scanner-mcp v${pkg.version}
137
+ `);
138
+ process.exit(0);
139
+ }
135
140
  if (args.includes("--help") || args.includes("-h")) {
136
141
  printHelp();
137
142
  process.exit(0);
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  MODULE_CATALOG,
4
4
  resolveEnabledModules
5
- } from "../chunk-5IWY6HOQ.js";
5
+ } from "../chunk-WRGE3BZV.js";
6
6
 
7
7
  // src/sidecar/server.ts
8
8
  import * as http from "http";
@@ -144,6 +144,17 @@ var SIDECAR_ROUTES = [
144
144
  { method: "GET", path: "/reddit/trending", tool: "reddit_trending" },
145
145
  { method: "GET", path: "/reddit/mentions", tool: "reddit_mentions" },
146
146
  { method: "GET", path: "/reddit/sentiment", tool: "reddit_sentiment" },
147
+ {
148
+ method: "GET",
149
+ path: "/reddit/watchlist-scan",
150
+ tool: "reddit_watchlist_scan",
151
+ transformParams: (p) => ({
152
+ // Default to [] (not undefined) so a missing param fails Zod's .min(1)
153
+ // with a clear "at least 1" message rather than a generic type error.
154
+ symbols: p.get("symbols")?.split(",").map((s) => s.trim()).filter(Boolean) ?? [],
155
+ period: p.get("period") || void 0
156
+ })
157
+ },
147
158
  // Workspace
148
159
  { method: "GET", path: "/workspace/profile", tool: "workspace_get_profile" },
149
160
  { method: "POST", path: "/workspace/profile", tool: "workspace_update_profile" },
@@ -210,9 +221,7 @@ function convertZodToOpenApi(zod) {
210
221
  const inner = convertZodToOpenApi(def.innerType || def.schema);
211
222
  return { ...inner, optional: true, description: description || inner.description };
212
223
  }
213
- if (typeName === "ZodNumber" || defType === "number") {
214
- return { type: zod.isInt ? "integer" : "number", description };
215
- }
224
+ if (typeName === "ZodNumber" || defType === "number") return { type: zod.isInt ? "integer" : "number", description };
216
225
  if (typeName === "ZodBoolean" || defType === "boolean") return { type: "boolean", description };
217
226
  if (typeName === "ZodEnum" || defType === "enum") return { type: "string", enum: def.values, description };
218
227
  if (typeName === "ZodString" || defType === "string") return { type: "string", description };
@@ -245,6 +254,51 @@ function convertZodToOpenApi(zod) {
245
254
  }
246
255
  return { type: "string", description };
247
256
  }
257
+ function getResponseSchema(toolName) {
258
+ if (toolName.startsWith("tradingview_") || toolName.startsWith("crypto_")) {
259
+ return {
260
+ type: "array",
261
+ items: {
262
+ type: "object",
263
+ properties: {
264
+ symbol: { type: "string" },
265
+ data: { type: "object", additionalProperties: true }
266
+ }
267
+ }
268
+ };
269
+ }
270
+ if (toolName.startsWith("edgar_")) {
271
+ return { type: "array", items: { type: "object", additionalProperties: true } };
272
+ }
273
+ if (toolName === "finnhub_quote") {
274
+ return {
275
+ type: "object",
276
+ properties: {
277
+ c: { type: "number", description: "Current price" },
278
+ d: { type: "number", description: "Change" },
279
+ dp: { type: "number", description: "Percent change" },
280
+ h: { type: "number", description: "High price of the day" },
281
+ l: { type: "number", description: "Low price of the day" },
282
+ o: { type: "number", description: "Open price of the day" },
283
+ pc: { type: "number", description: "Previous close price" },
284
+ t: { type: "integer", description: "Timestamp" }
285
+ }
286
+ };
287
+ }
288
+ if (toolName.startsWith("finnhub_")) {
289
+ return { type: "object", additionalProperties: true };
290
+ }
291
+ if (toolName.startsWith("options_")) {
292
+ return { type: "object", additionalProperties: true };
293
+ }
294
+ if (toolName.startsWith("fred_")) {
295
+ return { type: "array", items: { type: "object", additionalProperties: true } };
296
+ }
297
+ if (toolName.startsWith("workspace_")) {
298
+ return { type: "object", additionalProperties: true };
299
+ }
300
+ return { type: "object", description: "Varies by tool" };
301
+ }
248
302
  function generateOpenApiSpec() {
249
303
  const mockConfig = {
250
304
  env: {
@@ -315,7 +369,11 @@ function generateOpenApiSpec() {
315
369
  responses: {
316
370
  "200": {
317
371
  description: "Successful response",
318
- content: { "application/json": { schema: { type: "object", description: "Varies by tool" } } }
372
+ content: {
373
+ "application/json": {
374
+ schema: getResponseSchema(route.tool)
375
+ }
376
+ }
319
377
  },
320
378
  "400": {
321
379
  description: "Invalid parameters",
@@ -361,7 +419,9 @@ function generateOpenApiSpec() {
361
419
  description: prop.description || ""
362
420
  };
363
421
  if (prop.type === "array") {
364
- param.schema = { type: "string", description: prop.description + " (comma-separated list)" };
422
+ const desc = prop.description + " (comma-separated list)";
423
+ param.description = desc;
424
+ param.schema = { type: "string", description: desc };
365
425
  } else {
366
426
  param.schema = prop;
367
427
  }
@@ -455,8 +515,9 @@ function buildTools(config) {
455
515
  FRED_API_KEY: config.fredApiKey,
456
516
  ALPHA_VANTAGE_API_KEY: config.alphaVantageApiKey
457
517
  },
458
- enableWorkspace: false,
459
- defaultExchange: "NASDAQ"
518
+ enableWorkspace: config.enableWorkspace ?? false,
519
+ dataDir: config.dataDir,
520
+ defaultExchange: config.defaultExchange ?? "NASDAQ"
460
521
  };
461
522
  const allModules = MODULE_CATALOG.map((entry) => entry.factory(mockConfig)).filter((m) => m !== null);
462
523
  const enabled = resolveEnabledModules(allModules, mockConfig.env);
@@ -609,13 +670,29 @@ function parsePort(args) {
609
670
  }
610
671
  return 3200;
611
672
  }
673
+ function parseStringFlag(args, flag) {
674
+ const idx = args.indexOf(flag);
675
+ if (idx !== -1 && args[idx + 1]) return args[idx + 1];
676
+ return void 0;
677
+ }
612
678
  function main() {
613
679
  const args = process.argv.slice(2);
614
680
  const port = parsePort(args);
681
+ const enableWorkspace = args.includes("--enable-workspace");
682
+ const dataDir = parseStringFlag(args, "--data-dir") ?? process.env.STOCK_SCANNER_DATA_DIR;
683
+ const defaultExchange = parseStringFlag(args, "--default-exchange") ?? "NASDAQ";
615
684
  const finnhubApiKey = process.env.FINNHUB_API_KEY;
616
685
  const fredApiKey = process.env.FRED_API_KEY;
617
686
  const alphaVantageApiKey = process.env.ALPHA_VANTAGE_API_KEY;
618
- const server = createServer2({ port, finnhubApiKey, fredApiKey, alphaVantageApiKey });
687
+ const server = createServer2({
688
+ port,
689
+ finnhubApiKey,
690
+ fredApiKey,
691
+ alphaVantageApiKey,
692
+ enableWorkspace,
693
+ dataDir,
694
+ defaultExchange
695
+ });
619
696
  const enabled = [];
620
697
  const disabled = [];
621
698
  if (finnhubApiKey) enabled.push("finnhub");
@@ -624,8 +701,10 @@ function main() {
624
701
  else disabled.push("fred (FRED_API_KEY not set)");
625
702
  if (alphaVantageApiKey) enabled.push("alpha-vantage");
626
703
  else disabled.push("alpha-vantage (ALPHA_VANTAGE_API_KEY not set)");
704
+ if (enableWorkspace) enabled.push("workspace");
705
+ else disabled.push("workspace (--enable-workspace not set)");
627
706
  console.error(`[stock-scanner-sidecar] listening on port ${port}`);
628
- console.error(` Always-on: tradingview, tradingview-crypto, sec-edgar, options, options-cboe, sentiment, coingecko, frankfurter`);
707
+ console.error(` Always-on: tradingview, tradingview-crypto, sec-edgar, options, options-cboe, sentiment, coingecko, frankfurter, reddit`);
629
708
  if (enabled.length) console.error(` Enabled: ${enabled.join(", ")}`);
630
709
  if (disabled.length) console.error(` Disabled: ${disabled.join(", ")}`);
631
710
  const shutdown = () => {
@@ -3,7 +3,7 @@
3
3
  "info": {
4
4
  "title": "Stock Scanner Sidecar API",
5
5
  "description": "REST API for stock and crypto market data, SEC filings, and technical analysis. This is the HTTP sidecar for the stock-scanner-mcp server.",
6
- "version": "1.16.3",
6
+ "version": "1.17.0",
7
7
  "contact": {
8
8
  "name": "Yordan Yordanov",
9
9
  "url": "https://github.com/yyordanov-tradu/stock-scanner-mcp"
@@ -52,8 +52,19 @@
52
52
  "content": {
53
53
  "application/json": {
54
54
  "schema": {
55
- "type": "object",
56
- "description": "Varies by tool"
55
+ "type": "array",
56
+ "items": {
57
+ "type": "object",
58
+ "properties": {
59
+ "symbol": {
60
+ "type": "string"
61
+ },
62
+ "data": {
63
+ "type": "object",
64
+ "additionalProperties": true
65
+ }
66
+ }
67
+ }
57
68
  }
58
69
  }
59
70
  }
@@ -178,8 +189,19 @@
178
189
  "content": {
179
190
  "application/json": {
180
191
  "schema": {
181
- "type": "object",
182
- "description": "Varies by tool"
192
+ "type": "array",
193
+ "items": {
194
+ "type": "object",
195
+ "properties": {
196
+ "symbol": {
197
+ "type": "string"
198
+ },
199
+ "data": {
200
+ "type": "object",
201
+ "additionalProperties": true
202
+ }
203
+ }
204
+ }
183
205
  }
184
206
  }
185
207
  }
@@ -220,7 +242,7 @@
220
242
  "name": "tickers",
221
243
  "in": "query",
222
244
  "required": true,
223
- "description": "Stock tickers, e.g. ['AAPL', 'MSFT']",
245
+ "description": "Stock tickers, e.g. ['AAPL', 'MSFT'] (comma-separated list)",
224
246
  "schema": {
225
247
  "type": "string",
226
248
  "description": "Stock tickers, e.g. ['AAPL', 'MSFT'] (comma-separated list)"
@@ -239,8 +261,19 @@
239
261
  "content": {
240
262
  "application/json": {
241
263
  "schema": {
242
- "type": "object",
243
- "description": "Varies by tool"
264
+ "type": "array",
265
+ "items": {
266
+ "type": "object",
267
+ "properties": {
268
+ "symbol": {
269
+ "type": "string"
270
+ },
271
+ "data": {
272
+ "type": "object",
273
+ "additionalProperties": true
274
+ }
275
+ }
276
+ }
244
277
  }
245
278
  }
246
279
  }
@@ -281,7 +314,7 @@
281
314
  "name": "tickers",
282
315
  "in": "query",
283
316
  "required": true,
284
- "description": "Stock tickers, e.g. ['AAPL', 'IBM']",
317
+ "description": "Stock tickers, e.g. ['AAPL', 'IBM'] (comma-separated list)",
285
318
  "schema": {
286
319
  "type": "string",
287
320
  "description": "Stock tickers, e.g. ['AAPL', 'IBM'] (comma-separated list)"
@@ -310,8 +343,19 @@
310
343
  "content": {
311
344
  "application/json": {
312
345
  "schema": {
313
- "type": "object",
314
- "description": "Varies by tool"
346
+ "type": "array",
347
+ "items": {
348
+ "type": "object",
349
+ "properties": {
350
+ "symbol": {
351
+ "type": "string"
352
+ },
353
+ "data": {
354
+ "type": "object",
355
+ "additionalProperties": true
356
+ }
357
+ }
358
+ }
315
359
  }
316
360
  }
317
361
  }
@@ -352,7 +396,7 @@
352
396
  "name": "tickers",
353
397
  "in": "query",
354
398
  "required": true,
355
- "description": "2-5 stock tickers to compare (e.g. AAPL, MSFT)",
399
+ "description": "2-5 stock tickers to compare (e.g. AAPL, MSFT) (comma-separated list)",
356
400
  "schema": {
357
401
  "type": "string",
358
402
  "description": "2-5 stock tickers to compare (e.g. AAPL, MSFT) (comma-separated list)"
@@ -371,8 +415,19 @@
371
415
  "content": {
372
416
  "application/json": {
373
417
  "schema": {
374
- "type": "object",
375
- "description": "Varies by tool"
418
+ "type": "array",
419
+ "items": {
420
+ "type": "object",
421
+ "properties": {
422
+ "symbol": {
423
+ "type": "string"
424
+ },
425
+ "data": {
426
+ "type": "object",
427
+ "additionalProperties": true
428
+ }
429
+ }
430
+ }
376
431
  }
377
432
  }
378
433
  }
@@ -452,8 +507,19 @@
452
507
  "content": {
453
508
  "application/json": {
454
509
  "schema": {
455
- "type": "object",
456
- "description": "Varies by tool"
510
+ "type": "array",
511
+ "items": {
512
+ "type": "object",
513
+ "properties": {
514
+ "symbol": {
515
+ "type": "string"
516
+ },
517
+ "data": {
518
+ "type": "object",
519
+ "additionalProperties": true
520
+ }
521
+ }
522
+ }
457
523
  }
458
524
  }
459
525
  }
@@ -533,8 +599,19 @@
533
599
  "content": {
534
600
  "application/json": {
535
601
  "schema": {
536
- "type": "object",
537
- "description": "Varies by tool"
602
+ "type": "array",
603
+ "items": {
604
+ "type": "object",
605
+ "properties": {
606
+ "symbol": {
607
+ "type": "string"
608
+ },
609
+ "data": {
610
+ "type": "object",
611
+ "additionalProperties": true
612
+ }
613
+ }
614
+ }
538
615
  }
539
616
  }
540
617
  }
@@ -614,8 +691,19 @@
614
691
  "content": {
615
692
  "application/json": {
616
693
  "schema": {
617
- "type": "object",
618
- "description": "Varies by tool"
694
+ "type": "array",
695
+ "items": {
696
+ "type": "object",
697
+ "properties": {
698
+ "symbol": {
699
+ "type": "string"
700
+ },
701
+ "data": {
702
+ "type": "object",
703
+ "additionalProperties": true
704
+ }
705
+ }
706
+ }
619
707
  }
620
708
  }
621
709
  }
@@ -664,8 +752,19 @@
664
752
  "content": {
665
753
  "application/json": {
666
754
  "schema": {
667
- "type": "object",
668
- "description": "Varies by tool"
755
+ "type": "array",
756
+ "items": {
757
+ "type": "object",
758
+ "properties": {
759
+ "symbol": {
760
+ "type": "string"
761
+ },
762
+ "data": {
763
+ "type": "object",
764
+ "additionalProperties": true
765
+ }
766
+ }
767
+ }
669
768
  }
670
769
  }
671
770
  }
@@ -714,8 +813,19 @@
714
813
  "content": {
715
814
  "application/json": {
716
815
  "schema": {
717
- "type": "object",
718
- "description": "Varies by tool"
816
+ "type": "array",
817
+ "items": {
818
+ "type": "object",
819
+ "properties": {
820
+ "symbol": {
821
+ "type": "string"
822
+ },
823
+ "data": {
824
+ "type": "object",
825
+ "additionalProperties": true
826
+ }
827
+ }
828
+ }
719
829
  }
720
830
  }
721
831
  }
@@ -785,8 +895,19 @@
785
895
  "content": {
786
896
  "application/json": {
787
897
  "schema": {
788
- "type": "object",
789
- "description": "Varies by tool"
898
+ "type": "array",
899
+ "items": {
900
+ "type": "object",
901
+ "properties": {
902
+ "symbol": {
903
+ "type": "string"
904
+ },
905
+ "data": {
906
+ "type": "object",
907
+ "additionalProperties": true
908
+ }
909
+ }
910
+ }
790
911
  }
791
912
  }
792
913
  }
@@ -919,8 +1040,19 @@
919
1040
  "content": {
920
1041
  "application/json": {
921
1042
  "schema": {
922
- "type": "object",
923
- "description": "Varies by tool"
1043
+ "type": "array",
1044
+ "items": {
1045
+ "type": "object",
1046
+ "properties": {
1047
+ "symbol": {
1048
+ "type": "string"
1049
+ },
1050
+ "data": {
1051
+ "type": "object",
1052
+ "additionalProperties": true
1053
+ }
1054
+ }
1055
+ }
924
1056
  }
925
1057
  }
926
1058
  }
@@ -961,7 +1093,7 @@
961
1093
  "name": "symbols",
962
1094
  "in": "query",
963
1095
  "required": true,
964
- "description": "Crypto pair symbols (e.g. ['BTCUSDT', 'ETHUSDT'])",
1096
+ "description": "Crypto pair symbols (e.g. ['BTCUSDT', 'ETHUSDT']) (comma-separated list)",
965
1097
  "schema": {
966
1098
  "type": "string",
967
1099
  "description": "Crypto pair symbols (e.g. ['BTCUSDT', 'ETHUSDT']) (comma-separated list)"
@@ -980,8 +1112,19 @@
980
1112
  "content": {
981
1113
  "application/json": {
982
1114
  "schema": {
983
- "type": "object",
984
- "description": "Varies by tool"
1115
+ "type": "array",
1116
+ "items": {
1117
+ "type": "object",
1118
+ "properties": {
1119
+ "symbol": {
1120
+ "type": "string"
1121
+ },
1122
+ "data": {
1123
+ "type": "object",
1124
+ "additionalProperties": true
1125
+ }
1126
+ }
1127
+ }
985
1128
  }
986
1129
  }
987
1130
  }
@@ -1022,7 +1165,7 @@
1022
1165
  "name": "symbols",
1023
1166
  "in": "query",
1024
1167
  "required": true,
1025
- "description": "Crypto symbols (e.g. ['BTCUSDT'])",
1168
+ "description": "Crypto symbols (e.g. ['BTCUSDT']) (comma-separated list)",
1026
1169
  "schema": {
1027
1170
  "type": "string",
1028
1171
  "description": "Crypto symbols (e.g. ['BTCUSDT']) (comma-separated list)"
@@ -1051,8 +1194,19 @@
1051
1194
  "content": {
1052
1195
  "application/json": {
1053
1196
  "schema": {
1054
- "type": "object",
1055
- "description": "Varies by tool"
1197
+ "type": "array",
1198
+ "items": {
1199
+ "type": "object",
1200
+ "properties": {
1201
+ "symbol": {
1202
+ "type": "string"
1203
+ },
1204
+ "data": {
1205
+ "type": "object",
1206
+ "additionalProperties": true
1207
+ }
1208
+ }
1209
+ }
1056
1210
  }
1057
1211
  }
1058
1212
  }
@@ -1123,7 +1277,7 @@
1123
1277
  "application/json": {
1124
1278
  "schema": {
1125
1279
  "type": "object",
1126
- "description": "Varies by tool"
1280
+ "additionalProperties": true
1127
1281
  }
1128
1282
  }
1129
1283
  }
@@ -1214,7 +1368,7 @@
1214
1368
  "application/json": {
1215
1369
  "schema": {
1216
1370
  "type": "object",
1217
- "description": "Varies by tool"
1371
+ "additionalProperties": true
1218
1372
  }
1219
1373
  }
1220
1374
  }
@@ -1305,7 +1459,7 @@
1305
1459
  "application/json": {
1306
1460
  "schema": {
1307
1461
  "type": "object",
1308
- "description": "Varies by tool"
1462
+ "additionalProperties": true
1309
1463
  }
1310
1464
  }
1311
1465
  }
@@ -1366,7 +1520,7 @@
1366
1520
  "application/json": {
1367
1521
  "schema": {
1368
1522
  "type": "object",
1369
- "description": "Varies by tool"
1523
+ "additionalProperties": true
1370
1524
  }
1371
1525
  }
1372
1526
  }
@@ -1427,7 +1581,7 @@
1427
1581
  "application/json": {
1428
1582
  "schema": {
1429
1583
  "type": "object",
1430
- "description": "Varies by tool"
1584
+ "additionalProperties": true
1431
1585
  }
1432
1586
  }
1433
1587
  }
@@ -1498,7 +1652,7 @@
1498
1652
  "application/json": {
1499
1653
  "schema": {
1500
1654
  "type": "object",
1501
- "description": "Varies by tool"
1655
+ "additionalProperties": true
1502
1656
  }
1503
1657
  }
1504
1658
  }
@@ -1559,7 +1713,7 @@
1559
1713
  "application/json": {
1560
1714
  "schema": {
1561
1715
  "type": "object",
1562
- "description": "Varies by tool"
1716
+ "additionalProperties": true
1563
1717
  }
1564
1718
  }
1565
1719
  }
@@ -1620,7 +1774,7 @@
1620
1774
  "application/json": {
1621
1775
  "schema": {
1622
1776
  "type": "object",
1623
- "description": "Varies by tool"
1777
+ "additionalProperties": true
1624
1778
  }
1625
1779
  }
1626
1780
  }
@@ -1681,7 +1835,40 @@
1681
1835
  "application/json": {
1682
1836
  "schema": {
1683
1837
  "type": "object",
1684
- "description": "Varies by tool"
1838
+ "properties": {
1839
+ "c": {
1840
+ "type": "number",
1841
+ "description": "Current price"
1842
+ },
1843
+ "d": {
1844
+ "type": "number",
1845
+ "description": "Change"
1846
+ },
1847
+ "dp": {
1848
+ "type": "number",
1849
+ "description": "Percent change"
1850
+ },
1851
+ "h": {
1852
+ "type": "number",
1853
+ "description": "High price of the day"
1854
+ },
1855
+ "l": {
1856
+ "type": "number",
1857
+ "description": "Low price of the day"
1858
+ },
1859
+ "o": {
1860
+ "type": "number",
1861
+ "description": "Open price of the day"
1862
+ },
1863
+ "pc": {
1864
+ "type": "number",
1865
+ "description": "Previous close price"
1866
+ },
1867
+ "t": {
1868
+ "type": "integer",
1869
+ "description": "Timestamp"
1870
+ }
1871
+ }
1685
1872
  }
1686
1873
  }
1687
1874
  }
@@ -1741,8 +1928,11 @@
1741
1928
  "content": {
1742
1929
  "application/json": {
1743
1930
  "schema": {
1744
- "type": "object",
1745
- "description": "Varies by tool"
1931
+ "type": "array",
1932
+ "items": {
1933
+ "type": "object",
1934
+ "additionalProperties": true
1935
+ }
1746
1936
  }
1747
1937
  }
1748
1938
  }
@@ -1803,7 +1993,7 @@
1803
1993
  "name": "forms",
1804
1994
  "in": "query",
1805
1995
  "required": false,
1806
- "description": "Form types to filter (e.g. ['10-K', '10-Q', '8-K'])",
1996
+ "description": "Form types to filter (e.g. ['10-K', '10-Q', '8-K']) (comma-separated list)",
1807
1997
  "schema": {
1808
1998
  "type": "string",
1809
1999
  "description": "Form types to filter (e.g. ['10-K', '10-Q', '8-K']) (comma-separated list)"
@@ -1813,7 +2003,7 @@
1813
2003
  "name": "tickers",
1814
2004
  "in": "query",
1815
2005
  "required": false,
1816
- "description": "Company tickers to filter (e.g. ['AAPL', 'MSFT'])",
2006
+ "description": "Company tickers to filter (e.g. ['AAPL', 'MSFT']) (comma-separated list)",
1817
2007
  "schema": {
1818
2008
  "type": "string",
1819
2009
  "description": "Company tickers to filter (e.g. ['AAPL', 'MSFT']) (comma-separated list)"
@@ -1842,8 +2032,11 @@
1842
2032
  "content": {
1843
2033
  "application/json": {
1844
2034
  "schema": {
1845
- "type": "object",
1846
- "description": "Varies by tool"
2035
+ "type": "array",
2036
+ "items": {
2037
+ "type": "object",
2038
+ "additionalProperties": true
2039
+ }
1847
2040
  }
1848
2041
  }
1849
2042
  }
@@ -1904,7 +2097,7 @@
1904
2097
  "name": "forms",
1905
2098
  "in": "query",
1906
2099
  "required": false,
1907
- "description": "Form types to filter (e.g. ['10-K', '10-Q', '8-K'])",
2100
+ "description": "Form types to filter (e.g. ['10-K', '10-Q', '8-K']) (comma-separated list)",
1908
2101
  "schema": {
1909
2102
  "type": "string",
1910
2103
  "description": "Form types to filter (e.g. ['10-K', '10-Q', '8-K']) (comma-separated list)"
@@ -1914,7 +2107,7 @@
1914
2107
  "name": "tickers",
1915
2108
  "in": "query",
1916
2109
  "required": false,
1917
- "description": "Company tickers to filter (e.g. ['AAPL', 'MSFT'])",
2110
+ "description": "Company tickers to filter (e.g. ['AAPL', 'MSFT']) (comma-separated list)",
1918
2111
  "schema": {
1919
2112
  "type": "string",
1920
2113
  "description": "Company tickers to filter (e.g. ['AAPL', 'MSFT']) (comma-separated list)"
@@ -1943,8 +2136,11 @@
1943
2136
  "content": {
1944
2137
  "application/json": {
1945
2138
  "schema": {
1946
- "type": "object",
1947
- "description": "Varies by tool"
2139
+ "type": "array",
2140
+ "items": {
2141
+ "type": "object",
2142
+ "additionalProperties": true
2143
+ }
1948
2144
  }
1949
2145
  }
1950
2146
  }
@@ -1995,7 +2191,7 @@
1995
2191
  "name": "forms",
1996
2192
  "in": "query",
1997
2193
  "required": false,
1998
- "description": "Form types (e.g. ['10-K', '10-Q']). Default: all.",
2194
+ "description": "Form types (e.g. ['10-K', '10-Q']). Default: all. (comma-separated list)",
1999
2195
  "schema": {
2000
2196
  "type": "string",
2001
2197
  "description": "Form types (e.g. ['10-K', '10-Q']). Default: all. (comma-separated list)"
@@ -2024,8 +2220,11 @@
2024
2220
  "content": {
2025
2221
  "application/json": {
2026
2222
  "schema": {
2027
- "type": "object",
2028
- "description": "Varies by tool"
2223
+ "type": "array",
2224
+ "items": {
2225
+ "type": "object",
2226
+ "additionalProperties": true
2227
+ }
2029
2228
  }
2030
2229
  }
2031
2230
  }
@@ -2085,8 +2284,11 @@
2085
2284
  "content": {
2086
2285
  "application/json": {
2087
2286
  "schema": {
2088
- "type": "object",
2089
- "description": "Varies by tool"
2287
+ "type": "array",
2288
+ "items": {
2289
+ "type": "object",
2290
+ "additionalProperties": true
2291
+ }
2090
2292
  }
2091
2293
  }
2092
2294
  }
@@ -2156,8 +2358,11 @@
2156
2358
  "content": {
2157
2359
  "application/json": {
2158
2360
  "schema": {
2159
- "type": "object",
2160
- "description": "Varies by tool"
2361
+ "type": "array",
2362
+ "items": {
2363
+ "type": "object",
2364
+ "additionalProperties": true
2365
+ }
2161
2366
  }
2162
2367
  }
2163
2368
  }
@@ -2227,8 +2432,11 @@
2227
2432
  "content": {
2228
2433
  "application/json": {
2229
2434
  "schema": {
2230
- "type": "object",
2231
- "description": "Varies by tool"
2435
+ "type": "array",
2436
+ "items": {
2437
+ "type": "object",
2438
+ "additionalProperties": true
2439
+ }
2232
2440
  }
2233
2441
  }
2234
2442
  }
@@ -2299,7 +2507,7 @@
2299
2507
  "application/json": {
2300
2508
  "schema": {
2301
2509
  "type": "object",
2302
- "description": "Varies by tool"
2510
+ "additionalProperties": true
2303
2511
  }
2304
2512
  }
2305
2513
  }
@@ -2420,7 +2628,7 @@
2420
2628
  "application/json": {
2421
2629
  "schema": {
2422
2630
  "type": "object",
2423
- "description": "Varies by tool"
2631
+ "additionalProperties": true
2424
2632
  }
2425
2633
  }
2426
2634
  }
@@ -2481,7 +2689,7 @@
2481
2689
  "application/json": {
2482
2690
  "schema": {
2483
2691
  "type": "object",
2484
- "description": "Varies by tool"
2692
+ "additionalProperties": true
2485
2693
  }
2486
2694
  }
2487
2695
  }
@@ -2572,7 +2780,7 @@
2572
2780
  "application/json": {
2573
2781
  "schema": {
2574
2782
  "type": "object",
2575
- "description": "Varies by tool"
2783
+ "additionalProperties": true
2576
2784
  }
2577
2785
  }
2578
2786
  }
@@ -2643,7 +2851,7 @@
2643
2851
  "application/json": {
2644
2852
  "schema": {
2645
2853
  "type": "object",
2646
- "description": "Varies by tool"
2854
+ "additionalProperties": true
2647
2855
  }
2648
2856
  }
2649
2857
  }
@@ -2714,7 +2922,7 @@
2714
2922
  "application/json": {
2715
2923
  "schema": {
2716
2924
  "type": "object",
2717
- "description": "Varies by tool"
2925
+ "additionalProperties": true
2718
2926
  }
2719
2927
  }
2720
2928
  }
@@ -2884,8 +3092,11 @@
2884
3092
  "content": {
2885
3093
  "application/json": {
2886
3094
  "schema": {
2887
- "type": "object",
2888
- "description": "Varies by tool"
3095
+ "type": "array",
3096
+ "items": {
3097
+ "type": "object",
3098
+ "additionalProperties": true
3099
+ }
2889
3100
  }
2890
3101
  }
2891
3102
  }
@@ -2955,8 +3166,11 @@
2955
3166
  "content": {
2956
3167
  "application/json": {
2957
3168
  "schema": {
2958
- "type": "object",
2959
- "description": "Varies by tool"
3169
+ "type": "array",
3170
+ "items": {
3171
+ "type": "object",
3172
+ "additionalProperties": true
3173
+ }
2960
3174
  }
2961
3175
  }
2962
3176
  }
@@ -3016,8 +3230,11 @@
3016
3230
  "content": {
3017
3231
  "application/json": {
3018
3232
  "schema": {
3019
- "type": "object",
3020
- "description": "Varies by tool"
3233
+ "type": "array",
3234
+ "items": {
3235
+ "type": "object",
3236
+ "additionalProperties": true
3237
+ }
3021
3238
  }
3022
3239
  }
3023
3240
  }
@@ -3137,8 +3354,11 @@
3137
3354
  "content": {
3138
3355
  "application/json": {
3139
3356
  "schema": {
3140
- "type": "object",
3141
- "description": "Varies by tool"
3357
+ "type": "array",
3358
+ "items": {
3359
+ "type": "object",
3360
+ "additionalProperties": true
3361
+ }
3142
3362
  }
3143
3363
  }
3144
3364
  }
@@ -3759,6 +3979,77 @@
3759
3979
  ]
3760
3980
  }
3761
3981
  },
3982
+ "/reddit/watchlist-scan": {
3983
+ "get": {
3984
+ "summary": "Batch Reddit sentiment scan for a list of stock tickers in a single pass",
3985
+ "description": "Batch Reddit sentiment scan for a list of stock tickers in a single pass. Combines the tickers into one OR query per subreddit (r/wallstreetbets, r/stocks, r/investing, r/options), costing ceil(symbols/20)×4 requests instead of one call per ticker — prefer this over calling reddit_mentions or reddit_sentiment for each symbol in a watchlist. Returns, per ticker: mention count, bullish/bearish/neutral sentiment breakdown with an average score, the top post by upvotes, and a 'hot' flag (true when a ticker has 5 or more mentions in the period). Limitations: keyword-based scoring, not NLP (sarcasm/context may be missed); sentiment is scored per-post, not per-ticker, so a multi-ticker post applies the same score to each matched symbol; symbols that collide with common English words/finance acronyms (e.g. REAL, OPEN, HOLD, SELL) or fall outside 2–5 uppercase characters may report zero mentions even when discussed; and because each subreddit's combined query shares one 100-post cap, heavily-discussed tickers in a large batch can crowd out quieter ones, so counts may run lower than a single-ticker reddit_mentions query. Results cached for 5 minutes.",
3986
+ "responses": {
3987
+ "200": {
3988
+ "description": "Successful response",
3989
+ "content": {
3990
+ "application/json": {
3991
+ "schema": {
3992
+ "type": "object",
3993
+ "description": "Varies by tool"
3994
+ }
3995
+ }
3996
+ }
3997
+ },
3998
+ "400": {
3999
+ "description": "Invalid parameters",
4000
+ "content": {
4001
+ "application/json": {
4002
+ "schema": {
4003
+ "$ref": "#/components/schemas/Error"
4004
+ }
4005
+ }
4006
+ }
4007
+ },
4008
+ "404": {
4009
+ "description": "Tool not found or API key missing",
4010
+ "content": {
4011
+ "application/json": {
4012
+ "schema": {
4013
+ "$ref": "#/components/schemas/Error"
4014
+ }
4015
+ }
4016
+ }
4017
+ },
4018
+ "500": {
4019
+ "description": "Internal server error",
4020
+ "content": {
4021
+ "application/json": {
4022
+ "schema": {
4023
+ "$ref": "#/components/schemas/Error"
4024
+ }
4025
+ }
4026
+ }
4027
+ }
4028
+ },
4029
+ "parameters": [
4030
+ {
4031
+ "name": "symbols",
4032
+ "in": "query",
4033
+ "required": true,
4034
+ "description": "Watchlist stock tickers to scan, e.g. ['AAPL', 'NVDA', 'TSLA'] (1–50 symbols) (comma-separated list)",
4035
+ "schema": {
4036
+ "type": "string",
4037
+ "description": "Watchlist stock tickers to scan, e.g. ['AAPL', 'NVDA', 'TSLA'] (1–50 symbols) (comma-separated list)"
4038
+ }
4039
+ },
4040
+ {
4041
+ "name": "period",
4042
+ "in": "query",
4043
+ "required": false,
4044
+ "description": "Time window to search Reddit posts (default: day)",
4045
+ "schema": {
4046
+ "type": "string",
4047
+ "description": "Time window to search Reddit posts (default: day)"
4048
+ }
4049
+ }
4050
+ ]
4051
+ }
4052
+ },
3762
4053
  "/workspace/profile": {
3763
4054
  "get": {
3764
4055
  "summary": "Get the current user's trading profile and workspace settings",
@@ -3770,7 +4061,7 @@
3770
4061
  "application/json": {
3771
4062
  "schema": {
3772
4063
  "type": "object",
3773
- "description": "Varies by tool"
4064
+ "additionalProperties": true
3774
4065
  }
3775
4066
  }
3776
4067
  }
@@ -3818,7 +4109,7 @@
3818
4109
  "application/json": {
3819
4110
  "schema": {
3820
4111
  "type": "object",
3821
- "description": "Varies by tool"
4112
+ "additionalProperties": true
3822
4113
  }
3823
4114
  }
3824
4115
  }
@@ -3896,7 +4187,7 @@
3896
4187
  "application/json": {
3897
4188
  "schema": {
3898
4189
  "type": "object",
3899
- "description": "Varies by tool"
4190
+ "additionalProperties": true
3900
4191
  }
3901
4192
  }
3902
4193
  }
@@ -3944,7 +4235,7 @@
3944
4235
  "application/json": {
3945
4236
  "schema": {
3946
4237
  "type": "object",
3947
- "description": "Varies by tool"
4238
+ "additionalProperties": true
3948
4239
  }
3949
4240
  }
3950
4241
  }
@@ -4013,7 +4304,7 @@
4013
4304
  "application/json": {
4014
4305
  "schema": {
4015
4306
  "type": "object",
4016
- "description": "Varies by tool"
4307
+ "additionalProperties": true
4017
4308
  }
4018
4309
  }
4019
4310
  }
@@ -4091,7 +4382,7 @@
4091
4382
  "application/json": {
4092
4383
  "schema": {
4093
4384
  "type": "object",
4094
- "description": "Varies by tool"
4385
+ "additionalProperties": true
4095
4386
  }
4096
4387
  }
4097
4388
  }
@@ -4150,7 +4441,7 @@
4150
4441
  "application/json": {
4151
4442
  "schema": {
4152
4443
  "type": "object",
4153
- "description": "Varies by tool"
4444
+ "additionalProperties": true
4154
4445
  }
4155
4446
  }
4156
4447
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stock-scanner-mcp",
3
3
  "mcpName": "io.github.yyordanov-tradu/stock-scanner-mcp",
4
- "version": "1.16.3",
4
+ "version": "1.17.0",
5
5
  "description": "MCP server providing Claude Code with real-time stock and crypto market data, SEC filings, insider trades, and technical analysis",
6
6
  "type": "module",
7
7
  "bin": {
@@ -80,18 +80,18 @@
80
80
  "dependencies": {
81
81
  "@modelcontextprotocol/sdk": "^1.27.1",
82
82
  "proper-lockfile": "^4.1.2",
83
- "zod": "^4.3.6"
83
+ "zod": "^4.3.6",
84
+ "zod-to-json-schema": "^3.25.2"
84
85
  },
85
86
  "devDependencies": {
86
87
  "@emnapi/core": "^1.9.2",
87
88
  "@emnapi/runtime": "^1.9.2",
88
- "@emnapi/wasi-threads": "^1.2.1",
89
+ "@emnapi/wasi-threads": "^2.0.0",
89
90
  "@types/node": "^25.5.0",
90
91
  "@types/proper-lockfile": "^4.1.4",
91
92
  "tsup": "^8.0.0",
92
93
  "tsx": "^4.21.0",
93
94
  "typescript": "^6.0.2",
94
- "vitest": "^4.1.1",
95
- "zod-to-json-schema": "^3.25.2"
95
+ "vitest": "^4.1.1"
96
96
  }
97
97
  }