stock-scanner-mcp 1.16.4 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
 
@@ -52,8 +52,19 @@ This gives you **46 tools** immediately with no API keys. API keys are optional
52
52
 
53
53
  Restart Claude Desktop after saving. Claude Code picks up the config automatically.
54
54
 
55
+ > **Claude Code shortcut — install as a plugin instead of Steps 1 and 2.** If you use Claude Code, skip the manual config above and install everything with two commands:
56
+ > ```
57
+ > /plugin marketplace add yyordanov-tradu/stock-scanner-mcp
58
+ > /plugin install stock-scanner@tradu-marketplace
59
+ > ```
60
+ > Run `/reload-plugins`, then `/mcp` — you should see the `stock-scanner` server listed as connected. The first launch runs `npx -y stock-scanner-mcp`, which downloads the package once (then it is cached). The plugin enables the workspace by default (data is stored in `~/.stock-scanner-mcp`) and passes the optional API keys (`FINNHUB_API_KEY`, `ALPHA_VANTAGE_API_KEY`, `FRED_API_KEY`) through from your environment; modules without a key are skipped automatically.
61
+ >
62
+ > **The plugin already bundles the skills — do NOT run Step 2.** All 19 skills ship with the plugin and load under the `stock-scanner:` namespace, e.g. `/stock-scanner:analyze-stock AAPL`, `/stock-scanner:morning-briefing`, `/stock-scanner:setup-market-workspace`. Running the Step 2 installer as well copies a second, unprefixed set into `~/.claude/skills/`, and the two collide — Step 2 is only for the manual (non-plugin) install. After installing, go straight to Step 3, using the namespaced command: `/stock-scanner:setup-market-workspace`.
63
+
55
64
  ### Step 2 — Install the trading skills
56
65
 
66
+ > Skip this step if you installed via the plugin above — the plugin already bundles these skills (as `stock-scanner:<name>`). This step is only for the manual config in Step 1.
67
+
57
68
  Run this command in your terminal:
58
69
 
59
70
  ```bash
@@ -97,7 +108,7 @@ Once answered, it saves your profile and creates a `core` watchlist:
97
108
 
98
109
  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
110
 
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.
111
+ **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
112
 
102
113
  ## What You Can Do
103
114
 
@@ -228,7 +239,7 @@ For the full list of workspace tools, see the [tool reference](#workspace--perso
228
239
  | options-cboe | 1 | None | CBOE put/call ratio sentiment indicator |
229
240
  | sentiment | 2 | None | CNN Fear & Greed Index, Crypto Fear & Greed Index |
230
241
  | frankfurter | 5 | None | Forex exchange rates — 31 currencies from ECB (daily reference rates) |
231
- | reddit | 3 | None | Reddit trending tickers, mention tracking, and sentiment from r/wallstreetbets, r/stocks, r/investing, r/options |
242
+ | reddit | 4 | None | Reddit trending tickers, mention tracking, sentiment, and batch watchlist scan from r/wallstreetbets, r/stocks, r/investing, r/options |
232
243
  | workspace | 7 | None | Optional stateful profile, watchlists, and thesis tracking for personalized workflows (`--enable-workspace`) |
233
244
  | finnhub | 9 | `FINNHUB_API_KEY` | Quotes, news, earnings, analyst ratings, short interest |
234
245
  | alpha-vantage | 5 | `ALPHA_VANTAGE_API_KEY` | Quotes, daily prices, fundamentals, earnings, dividends |
@@ -238,7 +249,7 @@ Modules auto-enable when their API key is set. No-key modules are always enabled
238
249
 
239
250
  For a complete list of every tool with descriptions, see the [Full Tool Reference](#full-tool-reference-64-tools) below.
240
251
 
241
- ## Full Tool Reference (64 tools)
252
+ ## Full Tool Reference (65 tools)
242
253
 
243
254
  ### TradingView — Stock Scanning (no API key)
244
255
 
@@ -323,6 +334,7 @@ For a complete list of every tool with descriptions, see the [Full Tool Referenc
323
334
  | `reddit_trending` | Trending stock tickers from Reddit by mention frequency across r/wallstreetbets, r/stocks, r/investing, r/options |
324
335
  | `reddit_mentions` | Mention count and top posts for a specific ticker across Reddit investing subreddits |
325
336
  | `reddit_sentiment` | Keyword-based sentiment analysis (bullish/bearish/neutral) for a ticker from Reddit discussions |
337
+ | `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) |
326
338
 
327
339
  ### Workspace — Personalized Context (optional, no API key)
328
340
 
@@ -416,7 +428,7 @@ curl http://localhost:3200/openapi.json
416
428
 
417
429
  See [Sidecar HTTP API](https://github.com/yyordanov-tradu/stock-scanner-mcp/wiki/Sidecar-HTTP-API) for endpoint details.
418
430
 
419
- **64 tools** exposed as REST routes, including `/tradingview/quote`, `/options/chain`, `/workspace/profile`, and more.
431
+ **65 tools** exposed as REST routes, including `/tradingview/quote`, `/options/chain`, `/workspace/profile`, and more.
420
432
 
421
433
  ## Rate Limits
422
434
 
@@ -469,7 +481,7 @@ src/
469
481
  │ ├── fred/ # 4 tools — economic calendar, indicators, historical data
470
482
  │ ├── sentiment/ # 2 tools — Fear & Greed indexes (market + crypto)
471
483
  │ ├── frankfurter/ # 5 tools — forex exchange rates (ECB, 31 currencies)
472
- │ └── reddit/ # 3 tools — trending tickers, mentions, sentiment from Reddit
484
+ │ └── reddit/ # 4 tools — trending tickers, mentions, sentiment, watchlist scan from Reddit
473
485
  ├── sidecar/
474
486
  │ ├── index.ts # HTTP sidecar entry point (port 3200)
475
487
  │ ├── routes.ts # Declarative URL routing table
@@ -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" },
@@ -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.4",
6
+ "version": "1.17.1",
7
7
  "contact": {
8
8
  "name": "Yordan Yordanov",
9
9
  "url": "https://github.com/yyordanov-tradu/stock-scanner-mcp"
@@ -3979,6 +3979,77 @@
3979
3979
  ]
3980
3980
  }
3981
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
+ },
3982
4053
  "/workspace/profile": {
3983
4054
  "get": {
3984
4055
  "summary": "Get the current user's trading profile and workspace settings",
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.4",
4
+ "version": "1.17.1",
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": {
@@ -86,7 +86,7 @@
86
86
  "devDependencies": {
87
87
  "@emnapi/core": "^1.9.2",
88
88
  "@emnapi/runtime": "^1.9.2",
89
- "@emnapi/wasi-threads": "^1.2.1",
89
+ "@emnapi/wasi-threads": "^2.0.0",
90
90
  "@types/node": "^25.5.0",
91
91
  "@types/proper-lockfile": "^4.1.4",
92
92
  "tsup": "^8.0.0",
package/skills/README.md CHANGED
@@ -4,6 +4,8 @@ Ready-made trading workflows that orchestrate [stock-scanner-mcp](https://www.np
4
4
 
5
5
  ## Quick Start
6
6
 
7
+ > **Installed the Claude Code plugin?** You already have these skills — the plugin bundles them and loads them under the `stock-scanner:` namespace (e.g. `/stock-scanner:morning-briefing`). Skip the installer below; it is only for the manual MCP-server setup.
8
+
7
9
  **Prerequisites:** stock-scanner-mcp configured as an MCP server in Claude Code.
8
10
 
9
11
  ```bash