stock-scanner-mcp 1.16.0 → 1.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/dist/{chunk-HAYAISNP.js → chunk-T5IF6777.js} +2 -0
- package/dist/index.js +666 -69
- package/dist/sidecar/index.js +16 -1
- package/package.json +4 -3
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
|
-
**
|
|
9
|
+
**64 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
|
|
|
@@ -35,7 +35,7 @@ Copy this complete config block into your config file:
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
This gives you **
|
|
38
|
+
This gives you **46 tools** immediately with no API keys. API keys are optional and free — they unlock 18 additional tools for real-time quotes, news, earnings, and economic data. See [API Keys](#api-keys-optional) below for where to get them.
|
|
39
39
|
|
|
40
40
|
> **Minimal config** — if you don't want workspace or API keys, use this instead:
|
|
41
41
|
> ```json
|
|
@@ -48,7 +48,7 @@ This gives you **43 tools** immediately with no API keys. API keys are optional
|
|
|
48
48
|
> }
|
|
49
49
|
> }
|
|
50
50
|
> ```
|
|
51
|
-
> This gives you **
|
|
51
|
+
> This gives you **39 stateless tools** with no local data storage.
|
|
52
52
|
|
|
53
53
|
Restart Claude Desktop after saving. Claude Code picks up the config automatically.
|
|
54
54
|
|
|
@@ -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
|
|
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.
|
|
101
101
|
|
|
102
102
|
## What You Can Do
|
|
103
103
|
|
|
@@ -220,6 +220,7 @@ For the full list of workspace tools, see the [tool reference](#workspace--perso
|
|
|
220
220
|
| options-cboe | 1 | None | CBOE put/call ratio sentiment indicator |
|
|
221
221
|
| sentiment | 2 | None | CNN Fear & Greed Index, Crypto Fear & Greed Index |
|
|
222
222
|
| 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 |
|
|
223
224
|
| workspace | 7 | None | Optional stateful profile, watchlists, and thesis tracking for personalized workflows (`--enable-workspace`) |
|
|
224
225
|
| finnhub | 9 | `FINNHUB_API_KEY` | Quotes, news, earnings, analyst ratings, short interest |
|
|
225
226
|
| alpha-vantage | 5 | `ALPHA_VANTAGE_API_KEY` | Quotes, daily prices, fundamentals, earnings, dividends |
|
|
@@ -227,9 +228,9 @@ For the full list of workspace tools, see the [tool reference](#workspace--perso
|
|
|
227
228
|
|
|
228
229
|
Modules auto-enable when their API key is set. No-key modules are always enabled, except `workspace`, which requires `--enable-workspace`.
|
|
229
230
|
|
|
230
|
-
For a complete list of every tool with descriptions, see the [Full Tool Reference](#full-tool-reference-
|
|
231
|
+
For a complete list of every tool with descriptions, see the [Full Tool Reference](#full-tool-reference-64-tools) below.
|
|
231
232
|
|
|
232
|
-
## Full Tool Reference (
|
|
233
|
+
## Full Tool Reference (64 tools)
|
|
233
234
|
|
|
234
235
|
### TradingView — Stock Scanning (no API key)
|
|
235
236
|
|
|
@@ -307,6 +308,14 @@ For a complete list of every tool with descriptions, see the [Full Tool Referenc
|
|
|
307
308
|
| `frankfurter_convert` | Convert an amount between two currencies |
|
|
308
309
|
| `frankfurter_currencies` | List all supported currency codes |
|
|
309
310
|
|
|
311
|
+
### Reddit — Trending Tickers & Sentiment (no API key)
|
|
312
|
+
|
|
313
|
+
| Tool | Description |
|
|
314
|
+
|------|-------------|
|
|
315
|
+
| `reddit_trending` | Trending stock tickers from Reddit by mention frequency across r/wallstreetbets, r/stocks, r/investing, r/options |
|
|
316
|
+
| `reddit_mentions` | Mention count and top posts for a specific ticker across Reddit investing subreddits |
|
|
317
|
+
| `reddit_sentiment` | Keyword-based sentiment analysis (bullish/bearish/neutral) for a ticker from Reddit discussions |
|
|
318
|
+
|
|
310
319
|
### Workspace — Personalized Context (optional, no API key)
|
|
311
320
|
|
|
312
321
|
Enabled only when you start the server with `--enable-workspace`.
|
|
@@ -384,11 +393,17 @@ Any MCP client that supports stdio servers can run this package. Use the same JS
|
|
|
384
393
|
|
|
385
394
|
## HTTP Sidecar
|
|
386
395
|
|
|
387
|
-
An optional HTTP server exposing all tools as REST endpoints for non-MCP integrations (
|
|
396
|
+
An optional HTTP server exposing all tools as REST endpoints for non-MCP integrations (GPT Actions, chat UIs, LLM pipelines). It includes an **OpenAPI 3.1** specification for automated tool discovery.
|
|
388
397
|
|
|
389
398
|
```bash
|
|
390
399
|
npx stock-scanner-sidecar # Start on port 3200
|
|
391
400
|
npx stock-scanner-sidecar --port 8080 # Custom port
|
|
401
|
+
|
|
402
|
+
# Access the OpenAPI spec
|
|
403
|
+
curl http://localhost:3200/openapi.json
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
See [Sidecar HTTP API](wiki-repo/Sidecar-HTTP-API.md) for endpoint details.
|
|
392
407
|
```
|
|
393
408
|
|
|
394
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.
|
|
@@ -443,7 +458,8 @@ src/
|
|
|
443
458
|
│ ├── alpha-vantage/ # 5 tools — quotes, fundamentals, dividends
|
|
444
459
|
│ ├── fred/ # 4 tools — economic calendar, indicators, historical data
|
|
445
460
|
│ ├── sentiment/ # 2 tools — Fear & Greed indexes (market + crypto)
|
|
446
|
-
│
|
|
461
|
+
│ ├── frankfurter/ # 5 tools — forex exchange rates (ECB, 31 currencies)
|
|
462
|
+
│ └── reddit/ # 3 tools — trending tickers, mentions, sentiment from Reddit
|
|
447
463
|
├── sidecar/
|
|
448
464
|
│ ├── index.ts # HTTP sidecar entry point (port 3200)
|
|
449
465
|
│ └── server.ts # HTTP request handler (55 endpoints)
|
|
@@ -1602,9 +1602,11 @@ async function getCurrencies() {
|
|
|
1602
1602
|
export {
|
|
1603
1603
|
errorResult,
|
|
1604
1604
|
successResult,
|
|
1605
|
+
httpGet,
|
|
1605
1606
|
scanStocks,
|
|
1606
1607
|
resolveTicker,
|
|
1607
1608
|
scanCrypto,
|
|
1609
|
+
TtlCache,
|
|
1608
1610
|
searchFilings,
|
|
1609
1611
|
getCompanyFilings,
|
|
1610
1612
|
getCompanyFacts,
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
runInstallSkills
|
|
4
4
|
} from "./chunk-YEZEE6FS.js";
|
|
5
5
|
import {
|
|
6
|
+
TtlCache,
|
|
6
7
|
convertCurrency,
|
|
7
8
|
errorResult,
|
|
8
9
|
fetchOptionChain,
|
|
@@ -38,13 +39,14 @@ import {
|
|
|
38
39
|
getShortInterest,
|
|
39
40
|
getTimeSeries,
|
|
40
41
|
getTrending,
|
|
42
|
+
httpGet,
|
|
41
43
|
resolveTicker,
|
|
42
44
|
scanCrypto,
|
|
43
45
|
scanStocks,
|
|
44
46
|
searchFilings,
|
|
45
47
|
searchSeries,
|
|
46
48
|
successResult
|
|
47
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-T5IF6777.js";
|
|
48
50
|
|
|
49
51
|
// src/index.ts
|
|
50
52
|
import { readFileSync } from "fs";
|
|
@@ -52,7 +54,7 @@ import { fileURLToPath } from "url";
|
|
|
52
54
|
import { dirname as dirname2, join as join2 } from "path";
|
|
53
55
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
54
56
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
55
|
-
import { z as
|
|
57
|
+
import { z as z15 } from "zod";
|
|
56
58
|
|
|
57
59
|
// src/config.ts
|
|
58
60
|
import * as path from "path";
|
|
@@ -1454,8 +1456,601 @@ function createFrankfurterModule() {
|
|
|
1454
1456
|
};
|
|
1455
1457
|
}
|
|
1456
1458
|
|
|
1459
|
+
// src/modules/reddit/index.ts
|
|
1460
|
+
import { z as z12 } from "zod";
|
|
1461
|
+
|
|
1462
|
+
// src/modules/reddit/client.ts
|
|
1463
|
+
var REDDIT_BASE = "https://www.reddit.com";
|
|
1464
|
+
var REDDIT_HEADERS = {
|
|
1465
|
+
"User-Agent": "stock-scanner-mcp/1.0 (by /u/stock-scanner-bot)"
|
|
1466
|
+
};
|
|
1467
|
+
var DEFAULT_SUBREDDITS = [
|
|
1468
|
+
"wallstreetbets",
|
|
1469
|
+
"stocks",
|
|
1470
|
+
"investing",
|
|
1471
|
+
"options"
|
|
1472
|
+
];
|
|
1473
|
+
var trendingCache = new TtlCache(5 * 60 * 1e3);
|
|
1474
|
+
var mentionCache = new TtlCache(2 * 60 * 1e3);
|
|
1475
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1476
|
+
// 1-2 letter
|
|
1477
|
+
"I",
|
|
1478
|
+
"A",
|
|
1479
|
+
"AM",
|
|
1480
|
+
"AN",
|
|
1481
|
+
"AS",
|
|
1482
|
+
"AT",
|
|
1483
|
+
"BE",
|
|
1484
|
+
"BY",
|
|
1485
|
+
"DO",
|
|
1486
|
+
"GO",
|
|
1487
|
+
"HE",
|
|
1488
|
+
"IF",
|
|
1489
|
+
"IN",
|
|
1490
|
+
"IS",
|
|
1491
|
+
"IT",
|
|
1492
|
+
"ME",
|
|
1493
|
+
"MY",
|
|
1494
|
+
"NO",
|
|
1495
|
+
"OF",
|
|
1496
|
+
"OK",
|
|
1497
|
+
"ON",
|
|
1498
|
+
"OR",
|
|
1499
|
+
"SO",
|
|
1500
|
+
"TO",
|
|
1501
|
+
"UP",
|
|
1502
|
+
"US",
|
|
1503
|
+
"WE",
|
|
1504
|
+
// 3 letter common English
|
|
1505
|
+
"ALL",
|
|
1506
|
+
"AND",
|
|
1507
|
+
"ANY",
|
|
1508
|
+
"ARE",
|
|
1509
|
+
"BAD",
|
|
1510
|
+
"BIG",
|
|
1511
|
+
"BUT",
|
|
1512
|
+
"CAN",
|
|
1513
|
+
"DAY",
|
|
1514
|
+
"DID",
|
|
1515
|
+
"END",
|
|
1516
|
+
"FAR",
|
|
1517
|
+
"FEW",
|
|
1518
|
+
"FOR",
|
|
1519
|
+
"GET",
|
|
1520
|
+
"GOD",
|
|
1521
|
+
"GOT",
|
|
1522
|
+
"GUY",
|
|
1523
|
+
"HAS",
|
|
1524
|
+
"HAD",
|
|
1525
|
+
"HER",
|
|
1526
|
+
"HIM",
|
|
1527
|
+
"HIS",
|
|
1528
|
+
"HOW",
|
|
1529
|
+
"ITS",
|
|
1530
|
+
"JOB",
|
|
1531
|
+
"LET",
|
|
1532
|
+
"LOT",
|
|
1533
|
+
"MAN",
|
|
1534
|
+
"MAY",
|
|
1535
|
+
"MEN",
|
|
1536
|
+
"MOM",
|
|
1537
|
+
"NEW",
|
|
1538
|
+
"NOT",
|
|
1539
|
+
"NOW",
|
|
1540
|
+
"OLD",
|
|
1541
|
+
"ONE",
|
|
1542
|
+
"OUR",
|
|
1543
|
+
"OUT",
|
|
1544
|
+
"OWN",
|
|
1545
|
+
"PUT",
|
|
1546
|
+
"RAN",
|
|
1547
|
+
"RUN",
|
|
1548
|
+
"SAT",
|
|
1549
|
+
"SAY",
|
|
1550
|
+
"SET",
|
|
1551
|
+
"SHE",
|
|
1552
|
+
"SIT",
|
|
1553
|
+
"THE",
|
|
1554
|
+
"TOO",
|
|
1555
|
+
"TOP",
|
|
1556
|
+
"TRY",
|
|
1557
|
+
"TWO",
|
|
1558
|
+
"USE",
|
|
1559
|
+
"WAR",
|
|
1560
|
+
"WAS",
|
|
1561
|
+
"WAY",
|
|
1562
|
+
"WHO",
|
|
1563
|
+
"WHY",
|
|
1564
|
+
"WIN",
|
|
1565
|
+
"WON",
|
|
1566
|
+
"YES",
|
|
1567
|
+
"YET",
|
|
1568
|
+
"YOU",
|
|
1569
|
+
// 4 letter common English
|
|
1570
|
+
"ALSO",
|
|
1571
|
+
"BACK",
|
|
1572
|
+
"BEEN",
|
|
1573
|
+
"BEST",
|
|
1574
|
+
"BODY",
|
|
1575
|
+
"BOTH",
|
|
1576
|
+
"CALL",
|
|
1577
|
+
"CAME",
|
|
1578
|
+
"COME",
|
|
1579
|
+
"DARK",
|
|
1580
|
+
"DAYS",
|
|
1581
|
+
"DEAD",
|
|
1582
|
+
"DOES",
|
|
1583
|
+
"DONE",
|
|
1584
|
+
"DOWN",
|
|
1585
|
+
"EACH",
|
|
1586
|
+
"EVEN",
|
|
1587
|
+
"EVER",
|
|
1588
|
+
"EYES",
|
|
1589
|
+
"FACE",
|
|
1590
|
+
"FACT",
|
|
1591
|
+
"FEEL",
|
|
1592
|
+
"FIND",
|
|
1593
|
+
"FOUR",
|
|
1594
|
+
"FROM",
|
|
1595
|
+
"FULL",
|
|
1596
|
+
"GAVE",
|
|
1597
|
+
"GIVE",
|
|
1598
|
+
"GOOD",
|
|
1599
|
+
"HALF",
|
|
1600
|
+
"HAND",
|
|
1601
|
+
"HARD",
|
|
1602
|
+
"HAVE",
|
|
1603
|
+
"HEAD",
|
|
1604
|
+
"HEAR",
|
|
1605
|
+
"HELP",
|
|
1606
|
+
"HERE",
|
|
1607
|
+
"HIGH",
|
|
1608
|
+
"HOME",
|
|
1609
|
+
"HOPE",
|
|
1610
|
+
"IDEA",
|
|
1611
|
+
"INTO",
|
|
1612
|
+
"JUST",
|
|
1613
|
+
"KEEP",
|
|
1614
|
+
"KIND",
|
|
1615
|
+
"KNEW",
|
|
1616
|
+
"KNOW",
|
|
1617
|
+
"LAND",
|
|
1618
|
+
"LAST",
|
|
1619
|
+
"LEFT",
|
|
1620
|
+
"LESS",
|
|
1621
|
+
"LIFE",
|
|
1622
|
+
"LIKE",
|
|
1623
|
+
"LINE",
|
|
1624
|
+
"LIST",
|
|
1625
|
+
"LIVE",
|
|
1626
|
+
"LONG",
|
|
1627
|
+
"LOOK",
|
|
1628
|
+
"LOST",
|
|
1629
|
+
"LOVE",
|
|
1630
|
+
"MADE",
|
|
1631
|
+
"MAKE",
|
|
1632
|
+
"MANY",
|
|
1633
|
+
"MIND",
|
|
1634
|
+
"MORE",
|
|
1635
|
+
"MOST",
|
|
1636
|
+
"MUCH",
|
|
1637
|
+
"MUST",
|
|
1638
|
+
"NAME",
|
|
1639
|
+
"NEED",
|
|
1640
|
+
"NEWS",
|
|
1641
|
+
"NEXT",
|
|
1642
|
+
"NICE",
|
|
1643
|
+
"NONE",
|
|
1644
|
+
"ONCE",
|
|
1645
|
+
"ONLY",
|
|
1646
|
+
"OPEN",
|
|
1647
|
+
"OVER",
|
|
1648
|
+
"PAID",
|
|
1649
|
+
"PART",
|
|
1650
|
+
"PAST",
|
|
1651
|
+
"PLAN",
|
|
1652
|
+
"PLAY",
|
|
1653
|
+
"POST",
|
|
1654
|
+
"PULL",
|
|
1655
|
+
"PUSH",
|
|
1656
|
+
"RATE",
|
|
1657
|
+
"READ",
|
|
1658
|
+
"REAL",
|
|
1659
|
+
"REST",
|
|
1660
|
+
"RISK",
|
|
1661
|
+
"ROOM",
|
|
1662
|
+
"SAFE",
|
|
1663
|
+
"SAID",
|
|
1664
|
+
"SAME",
|
|
1665
|
+
"SEEN",
|
|
1666
|
+
"SHOW",
|
|
1667
|
+
"SIDE",
|
|
1668
|
+
"SOME",
|
|
1669
|
+
"SOON",
|
|
1670
|
+
"STOP",
|
|
1671
|
+
"SUCH",
|
|
1672
|
+
"SURE",
|
|
1673
|
+
"TAKE",
|
|
1674
|
+
"TALK",
|
|
1675
|
+
"TELL",
|
|
1676
|
+
"THAN",
|
|
1677
|
+
"THAT",
|
|
1678
|
+
"THEM",
|
|
1679
|
+
"THEN",
|
|
1680
|
+
"THEY",
|
|
1681
|
+
"THIS",
|
|
1682
|
+
"TIME",
|
|
1683
|
+
"TOOK",
|
|
1684
|
+
"TRUE",
|
|
1685
|
+
"TURN",
|
|
1686
|
+
"UPON",
|
|
1687
|
+
"VERY",
|
|
1688
|
+
"WANT",
|
|
1689
|
+
"WEEK",
|
|
1690
|
+
"WELL",
|
|
1691
|
+
"WENT",
|
|
1692
|
+
"WERE",
|
|
1693
|
+
"WHAT",
|
|
1694
|
+
"WHEN",
|
|
1695
|
+
"WILL",
|
|
1696
|
+
"WITH",
|
|
1697
|
+
"WORD",
|
|
1698
|
+
"WORK",
|
|
1699
|
+
"YEAR",
|
|
1700
|
+
"YOUR",
|
|
1701
|
+
// 5 letter common English
|
|
1702
|
+
"ABOUT",
|
|
1703
|
+
"AFTER",
|
|
1704
|
+
"AGAIN",
|
|
1705
|
+
"BEING",
|
|
1706
|
+
"BELOW",
|
|
1707
|
+
"BLACK",
|
|
1708
|
+
"BRING",
|
|
1709
|
+
"COULD",
|
|
1710
|
+
"EARLY",
|
|
1711
|
+
"EVERY",
|
|
1712
|
+
"FIRST",
|
|
1713
|
+
"GREAT",
|
|
1714
|
+
"GREEN",
|
|
1715
|
+
"HEARD",
|
|
1716
|
+
"HOUSE",
|
|
1717
|
+
"LARGE",
|
|
1718
|
+
"LATER",
|
|
1719
|
+
"LEARN",
|
|
1720
|
+
"LEVEL",
|
|
1721
|
+
"LIGHT",
|
|
1722
|
+
"MIGHT",
|
|
1723
|
+
"MONEY",
|
|
1724
|
+
"MONTH",
|
|
1725
|
+
"NEVER",
|
|
1726
|
+
"NIGHT",
|
|
1727
|
+
"OTHER",
|
|
1728
|
+
"PLACE",
|
|
1729
|
+
"POINT",
|
|
1730
|
+
"POWER",
|
|
1731
|
+
"PRICE",
|
|
1732
|
+
"RIGHT",
|
|
1733
|
+
"SHALL",
|
|
1734
|
+
"SINCE",
|
|
1735
|
+
"SMALL",
|
|
1736
|
+
"SORRY",
|
|
1737
|
+
"STAND",
|
|
1738
|
+
"START",
|
|
1739
|
+
"STATE",
|
|
1740
|
+
"STILL",
|
|
1741
|
+
"STUDY",
|
|
1742
|
+
"THEIR",
|
|
1743
|
+
"THERE",
|
|
1744
|
+
"THESE",
|
|
1745
|
+
"THING",
|
|
1746
|
+
"THINK",
|
|
1747
|
+
"THREE",
|
|
1748
|
+
"TODAY",
|
|
1749
|
+
"UNDER",
|
|
1750
|
+
"UNTIL",
|
|
1751
|
+
"WATER",
|
|
1752
|
+
"WHERE",
|
|
1753
|
+
"WHICH",
|
|
1754
|
+
"WHITE",
|
|
1755
|
+
"WHILE",
|
|
1756
|
+
"WHOLE",
|
|
1757
|
+
"WORLD",
|
|
1758
|
+
"WOULD",
|
|
1759
|
+
"WRITE",
|
|
1760
|
+
"YOUNG",
|
|
1761
|
+
// Finance acronyms that aren't tickers
|
|
1762
|
+
"ETF",
|
|
1763
|
+
"IPO",
|
|
1764
|
+
"SEC",
|
|
1765
|
+
"GDP",
|
|
1766
|
+
"FDA",
|
|
1767
|
+
"FED",
|
|
1768
|
+
"CEO",
|
|
1769
|
+
"CFO",
|
|
1770
|
+
"COO",
|
|
1771
|
+
"CTO",
|
|
1772
|
+
"ATH",
|
|
1773
|
+
"ATL",
|
|
1774
|
+
"EOD",
|
|
1775
|
+
"WSB",
|
|
1776
|
+
"OTC",
|
|
1777
|
+
"ITM",
|
|
1778
|
+
"OTM",
|
|
1779
|
+
"IMO",
|
|
1780
|
+
"LOL",
|
|
1781
|
+
"OMG",
|
|
1782
|
+
"USA",
|
|
1783
|
+
"NYSE",
|
|
1784
|
+
"YOLO",
|
|
1785
|
+
"FOMO",
|
|
1786
|
+
"HODL",
|
|
1787
|
+
"MOASS",
|
|
1788
|
+
"TLDR",
|
|
1789
|
+
"IMHO",
|
|
1790
|
+
"LMAO",
|
|
1791
|
+
"EDIT",
|
|
1792
|
+
"TLDR",
|
|
1793
|
+
"INFO",
|
|
1794
|
+
"HUGE",
|
|
1795
|
+
"GAIN",
|
|
1796
|
+
"HOLD",
|
|
1797
|
+
"SELL",
|
|
1798
|
+
"MOON",
|
|
1799
|
+
"CASH",
|
|
1800
|
+
"DEBT",
|
|
1801
|
+
"FUND",
|
|
1802
|
+
"BEAR",
|
|
1803
|
+
"BULL",
|
|
1804
|
+
"BOND",
|
|
1805
|
+
"LOAN",
|
|
1806
|
+
"LOSS",
|
|
1807
|
+
"PUMP",
|
|
1808
|
+
"DUMP",
|
|
1809
|
+
"LONG",
|
|
1810
|
+
"MAMA",
|
|
1811
|
+
"PAPA",
|
|
1812
|
+
"FREE",
|
|
1813
|
+
"ZERO",
|
|
1814
|
+
"PLUS"
|
|
1815
|
+
]);
|
|
1816
|
+
var CASHTAG_RE = /\$([A-Z]{2,5})\b/g;
|
|
1817
|
+
var BARE_TICKER_RE = /\b([A-Z]{2,5})\b/g;
|
|
1818
|
+
function extractTickers(text) {
|
|
1819
|
+
const tickers = /* @__PURE__ */ new Set();
|
|
1820
|
+
let match;
|
|
1821
|
+
CASHTAG_RE.lastIndex = 0;
|
|
1822
|
+
while ((match = CASHTAG_RE.exec(text)) !== null) {
|
|
1823
|
+
const ticker = match[1];
|
|
1824
|
+
if (!STOP_WORDS.has(ticker)) {
|
|
1825
|
+
tickers.add(ticker);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
BARE_TICKER_RE.lastIndex = 0;
|
|
1829
|
+
while ((match = BARE_TICKER_RE.exec(text)) !== null) {
|
|
1830
|
+
const word = match[1];
|
|
1831
|
+
if (!STOP_WORDS.has(word)) {
|
|
1832
|
+
tickers.add(word);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
return [...tickers];
|
|
1836
|
+
}
|
|
1837
|
+
var BULLISH_TERMS = [
|
|
1838
|
+
"bull",
|
|
1839
|
+
"calls",
|
|
1840
|
+
"long",
|
|
1841
|
+
"moon",
|
|
1842
|
+
"rocket",
|
|
1843
|
+
"tendies",
|
|
1844
|
+
"diamond hands",
|
|
1845
|
+
"buy",
|
|
1846
|
+
"buying",
|
|
1847
|
+
"bought",
|
|
1848
|
+
"dip",
|
|
1849
|
+
"undervalued",
|
|
1850
|
+
"breakout",
|
|
1851
|
+
"squeeze",
|
|
1852
|
+
"gamma",
|
|
1853
|
+
"rip",
|
|
1854
|
+
"printing",
|
|
1855
|
+
"lambo",
|
|
1856
|
+
"gains",
|
|
1857
|
+
"green",
|
|
1858
|
+
"upside"
|
|
1859
|
+
];
|
|
1860
|
+
var BEARISH_TERMS = [
|
|
1861
|
+
"bear",
|
|
1862
|
+
"puts",
|
|
1863
|
+
"short",
|
|
1864
|
+
"dump",
|
|
1865
|
+
"crash",
|
|
1866
|
+
"sell",
|
|
1867
|
+
"selling",
|
|
1868
|
+
"sold",
|
|
1869
|
+
"overvalued",
|
|
1870
|
+
"bubble",
|
|
1871
|
+
"bag",
|
|
1872
|
+
"bagholder",
|
|
1873
|
+
"red",
|
|
1874
|
+
"loss",
|
|
1875
|
+
"losses",
|
|
1876
|
+
"downside",
|
|
1877
|
+
"drill",
|
|
1878
|
+
"tank",
|
|
1879
|
+
"fade",
|
|
1880
|
+
"rug",
|
|
1881
|
+
"rekt",
|
|
1882
|
+
"worthless"
|
|
1883
|
+
];
|
|
1884
|
+
function scoreSentiment(text) {
|
|
1885
|
+
const lower = text.toLowerCase();
|
|
1886
|
+
let score = 0;
|
|
1887
|
+
for (const term of BULLISH_TERMS) {
|
|
1888
|
+
if (lower.includes(term)) score += 1;
|
|
1889
|
+
}
|
|
1890
|
+
for (const term of BEARISH_TERMS) {
|
|
1891
|
+
if (lower.includes(term)) score -= 1;
|
|
1892
|
+
}
|
|
1893
|
+
return score;
|
|
1894
|
+
}
|
|
1895
|
+
async function fetchSubredditPosts(subreddit, query, timeFilter, limit = 25) {
|
|
1896
|
+
const url = `${REDDIT_BASE}/r/${encodeURIComponent(subreddit)}/search.json?q=${encodeURIComponent(query)}&sort=new&restrict_sr=on&t=${encodeURIComponent(timeFilter)}&limit=${encodeURIComponent(String(limit))}`;
|
|
1897
|
+
const listing = await httpGet(url, {
|
|
1898
|
+
headers: REDDIT_HEADERS
|
|
1899
|
+
});
|
|
1900
|
+
return listing.data.children.map((child) => ({
|
|
1901
|
+
title: child.data.title,
|
|
1902
|
+
selftext: child.data.selftext,
|
|
1903
|
+
score: child.data.score,
|
|
1904
|
+
numComments: child.data.num_comments,
|
|
1905
|
+
createdUtc: child.data.created_utc,
|
|
1906
|
+
permalink: child.data.permalink,
|
|
1907
|
+
subreddit: child.data.subreddit
|
|
1908
|
+
}));
|
|
1909
|
+
}
|
|
1910
|
+
async function getTrendingTickers(subreddits = DEFAULT_SUBREDDITS, limit = 20) {
|
|
1911
|
+
const cacheKey = `trending:${[...subreddits].sort().join(",")}:${limit}`;
|
|
1912
|
+
const cached = trendingCache.get(cacheKey);
|
|
1913
|
+
if (cached) return cached;
|
|
1914
|
+
const tickerMap = /* @__PURE__ */ new Map();
|
|
1915
|
+
for (const sub of subreddits) {
|
|
1916
|
+
let posts;
|
|
1917
|
+
try {
|
|
1918
|
+
posts = await fetchSubredditPosts(sub, "stocks OR market OR trading", "week");
|
|
1919
|
+
} catch {
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
for (const post of posts) {
|
|
1923
|
+
const text = `${post.title} ${post.selftext}`;
|
|
1924
|
+
const tickers = extractTickers(text);
|
|
1925
|
+
for (const ticker of tickers) {
|
|
1926
|
+
const existing = tickerMap.get(ticker) ?? { mentions: 0, subreddits: {} };
|
|
1927
|
+
existing.mentions += 1;
|
|
1928
|
+
existing.subreddits[sub] = (existing.subreddits[sub] ?? 0) + 1;
|
|
1929
|
+
tickerMap.set(ticker, existing);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
const result = [...tickerMap.entries()].map(([symbol, data]) => ({ symbol, mentions: data.mentions, subreddits: data.subreddits })).sort((a, b) => b.mentions - a.mentions).slice(0, limit);
|
|
1934
|
+
trendingCache.set(cacheKey, result);
|
|
1935
|
+
return result;
|
|
1936
|
+
}
|
|
1937
|
+
async function getTickerMentions(symbol, period = "week") {
|
|
1938
|
+
const cacheKey = `mentions:${symbol}:${period}`;
|
|
1939
|
+
const cached = mentionCache.get(cacheKey);
|
|
1940
|
+
if (cached) return cached;
|
|
1941
|
+
const subredditCounts = {};
|
|
1942
|
+
const allPosts = [];
|
|
1943
|
+
for (const sub of DEFAULT_SUBREDDITS) {
|
|
1944
|
+
let posts;
|
|
1945
|
+
try {
|
|
1946
|
+
posts = await fetchSubredditPosts(sub, symbol, period);
|
|
1947
|
+
} catch {
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
subredditCounts[sub] = posts.length;
|
|
1951
|
+
allPosts.push(...posts);
|
|
1952
|
+
}
|
|
1953
|
+
const topPosts = allPosts.sort((a, b) => b.score - a.score).slice(0, 10);
|
|
1954
|
+
const result = {
|
|
1955
|
+
symbol,
|
|
1956
|
+
totalMentions: allPosts.length,
|
|
1957
|
+
subreddits: subredditCounts,
|
|
1958
|
+
topPosts
|
|
1959
|
+
};
|
|
1960
|
+
mentionCache.set(cacheKey, result);
|
|
1961
|
+
return result;
|
|
1962
|
+
}
|
|
1963
|
+
async function getTickerSentiment(symbol, limit = 50) {
|
|
1964
|
+
const cacheKey = `sentiment:${symbol}:${limit}`;
|
|
1965
|
+
const cached = mentionCache.get(cacheKey);
|
|
1966
|
+
if (cached) return cached;
|
|
1967
|
+
const allPosts = [];
|
|
1968
|
+
for (const sub of DEFAULT_SUBREDDITS) {
|
|
1969
|
+
let posts;
|
|
1970
|
+
try {
|
|
1971
|
+
posts = await fetchSubredditPosts(sub, symbol, "week", limit);
|
|
1972
|
+
} catch {
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
allPosts.push(...posts);
|
|
1976
|
+
}
|
|
1977
|
+
let bullishCount = 0;
|
|
1978
|
+
let bearishCount = 0;
|
|
1979
|
+
let neutralCount = 0;
|
|
1980
|
+
let totalScore = 0;
|
|
1981
|
+
const scored = allPosts.map((post) => {
|
|
1982
|
+
const text = `${post.title} ${post.selftext}`;
|
|
1983
|
+
const sentimentScore = scoreSentiment(text);
|
|
1984
|
+
totalScore += sentimentScore;
|
|
1985
|
+
if (sentimentScore > 0) bullishCount += 1;
|
|
1986
|
+
else if (sentimentScore < 0) bearishCount += 1;
|
|
1987
|
+
else neutralCount += 1;
|
|
1988
|
+
return { ...post, sentimentScore };
|
|
1989
|
+
});
|
|
1990
|
+
const samplePosts = scored.sort((a, b) => Math.abs(b.sentimentScore) - Math.abs(a.sentimentScore)).slice(0, 10);
|
|
1991
|
+
const result = {
|
|
1992
|
+
symbol,
|
|
1993
|
+
bullishCount,
|
|
1994
|
+
bearishCount,
|
|
1995
|
+
neutralCount,
|
|
1996
|
+
averageScore: allPosts.length > 0 ? totalScore / allPosts.length : 0,
|
|
1997
|
+
samplePosts
|
|
1998
|
+
};
|
|
1999
|
+
mentionCache.set(cacheKey, result);
|
|
2000
|
+
return result;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// src/modules/reddit/index.ts
|
|
2004
|
+
var trendingTool2 = {
|
|
2005
|
+
name: "reddit_trending",
|
|
2006
|
+
description: "Get trending stock tickers from Reddit based on mention frequency. Scans r/wallstreetbets, r/stocks, r/investing, and r/options for posts mentioning tickers. Returns tickers sorted by mention count with per-subreddit breakdown. Limitation: uses keyword extraction (cashtags + uppercase words), not NLP \u2014 some false positives possible. Best for gauging retail buzz, not precise sentiment.",
|
|
2007
|
+
inputSchema: z12.object({
|
|
2008
|
+
subreddits: z12.array(z12.string()).max(10).optional().describe("Subreddits to scan (default: wallstreetbets, stocks, investing, options)"),
|
|
2009
|
+
limit: z12.number().min(1).max(50).default(20).describe("Maximum number of trending tickers to return (default: 20)")
|
|
2010
|
+
}),
|
|
2011
|
+
readOnly: true,
|
|
2012
|
+
handler: withMetadata(async (args) => {
|
|
2013
|
+
const result = await getTrendingTickers(args.subreddits ?? [...DEFAULT_SUBREDDITS], args.limit ?? 20);
|
|
2014
|
+
return successResult(JSON.stringify(result, null, 2));
|
|
2015
|
+
}, { source: "reddit", dataDelay: "real-time" })
|
|
2016
|
+
};
|
|
2017
|
+
var mentionsTool = {
|
|
2018
|
+
name: "reddit_mentions",
|
|
2019
|
+
description: "Get mention count and top posts for a specific stock ticker across Reddit. Searches r/wallstreetbets, r/stocks, r/investing, and r/options. Returns total mentions, per-subreddit breakdown, and top 10 posts by score. Use this to check how much retail attention a ticker is getting.",
|
|
2020
|
+
inputSchema: z12.object({
|
|
2021
|
+
symbol: z12.string().max(10).describe("Stock ticker symbol (e.g. AAPL, TSLA, GME)"),
|
|
2022
|
+
period: z12.enum(["hour", "day", "week"]).default("day").describe("Time period to search (default: day)")
|
|
2023
|
+
}),
|
|
2024
|
+
readOnly: true,
|
|
2025
|
+
handler: withMetadata(async (args) => {
|
|
2026
|
+
const result = await getTickerMentions(args.symbol.toUpperCase(), args.period ?? "day");
|
|
2027
|
+
return successResult(JSON.stringify(result, null, 2));
|
|
2028
|
+
}, { source: "reddit", dataDelay: "real-time" })
|
|
2029
|
+
};
|
|
2030
|
+
var sentimentTool = {
|
|
2031
|
+
name: "reddit_sentiment",
|
|
2032
|
+
description: "Get sentiment analysis for a stock ticker from Reddit discussions. Searches r/wallstreetbets, r/stocks, r/investing, and r/options, then scores each post using keyword matching (bullish terms like 'moon', 'calls', 'breakout' vs bearish terms like 'crash', 'puts', 'dump'). Returns bullish/bearish/neutral counts, average sentiment score, and sample posts. Limitation: keyword-based scoring, not NLP \u2014 sarcasm and context may be missed.",
|
|
2033
|
+
inputSchema: z12.object({
|
|
2034
|
+
symbol: z12.string().max(10).describe("Stock ticker symbol (e.g. AAPL, TSLA, GME)"),
|
|
2035
|
+
limit: z12.number().min(1).max(100).default(50).describe("Maximum posts to analyze per subreddit (default: 50)")
|
|
2036
|
+
}),
|
|
2037
|
+
readOnly: true,
|
|
2038
|
+
handler: withMetadata(async (args) => {
|
|
2039
|
+
const result = await getTickerSentiment(args.symbol.toUpperCase(), args.limit ?? 50);
|
|
2040
|
+
return successResult(JSON.stringify(result, null, 2));
|
|
2041
|
+
}, { source: "reddit", dataDelay: "real-time" })
|
|
2042
|
+
};
|
|
2043
|
+
function createRedditModule() {
|
|
2044
|
+
return {
|
|
2045
|
+
name: "reddit",
|
|
2046
|
+
description: "Reddit sentiment \u2014 trending tickers, mention tracking, and sentiment analysis from popular investing subreddits",
|
|
2047
|
+
requiredEnvVars: [],
|
|
2048
|
+
tools: [trendingTool2, mentionsTool, sentimentTool]
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
|
|
1457
2052
|
// src/modules/workspace/index.ts
|
|
1458
|
-
import { z as
|
|
2053
|
+
import { z as z14 } from "zod";
|
|
1459
2054
|
|
|
1460
2055
|
// src/modules/workspace/storage.ts
|
|
1461
2056
|
import * as fs from "fs/promises";
|
|
@@ -1463,57 +2058,57 @@ import * as path2 from "path";
|
|
|
1463
2058
|
import { lock } from "proper-lockfile";
|
|
1464
2059
|
|
|
1465
2060
|
// src/modules/workspace/types.ts
|
|
1466
|
-
import { z as
|
|
2061
|
+
import { z as z13 } from "zod";
|
|
1467
2062
|
var RESERVED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype", "toString", "valueOf", "hasOwnProperty"]);
|
|
1468
|
-
var ProfileSchema =
|
|
1469
|
-
defaultExchange:
|
|
1470
|
-
tradingStyle:
|
|
1471
|
-
assetFocus:
|
|
1472
|
-
preferredTimeframe:
|
|
1473
|
-
workflowCadence:
|
|
1474
|
-
updatedAt:
|
|
2063
|
+
var ProfileSchema = z13.object({
|
|
2064
|
+
defaultExchange: z13.string().max(20).default("NASDAQ"),
|
|
2065
|
+
tradingStyle: z13.string().max(100).optional(),
|
|
2066
|
+
assetFocus: z13.array(z13.string().max(50)).max(20).default([]),
|
|
2067
|
+
preferredTimeframe: z13.string().max(50).optional(),
|
|
2068
|
+
workflowCadence: z13.enum(["daily", "weekly"]).default("daily"),
|
|
2069
|
+
updatedAt: z13.string().default(() => (/* @__PURE__ */ new Date(0)).toISOString())
|
|
1475
2070
|
});
|
|
1476
|
-
var InstrumentSchema =
|
|
1477
|
-
full:
|
|
1478
|
-
ticker:
|
|
1479
|
-
exchange:
|
|
1480
|
-
isCrypto:
|
|
1481
|
-
input:
|
|
1482
|
-
note:
|
|
1483
|
-
addedAt:
|
|
2071
|
+
var InstrumentSchema = z13.object({
|
|
2072
|
+
full: z13.string().max(80),
|
|
2073
|
+
ticker: z13.string().max(50),
|
|
2074
|
+
exchange: z13.string().optional(),
|
|
2075
|
+
isCrypto: z13.boolean(),
|
|
2076
|
+
input: z13.string().max(50),
|
|
2077
|
+
note: z13.string().max(500).optional(),
|
|
2078
|
+
addedAt: z13.string()
|
|
1484
2079
|
});
|
|
1485
|
-
var WatchlistSchema =
|
|
1486
|
-
id:
|
|
1487
|
-
name:
|
|
1488
|
-
instruments:
|
|
1489
|
-
createdAt:
|
|
1490
|
-
updatedAt:
|
|
2080
|
+
var WatchlistSchema = z13.object({
|
|
2081
|
+
id: z13.string().max(100),
|
|
2082
|
+
name: z13.string().max(100),
|
|
2083
|
+
instruments: z13.array(InstrumentSchema).max(200).default([]),
|
|
2084
|
+
createdAt: z13.string(),
|
|
2085
|
+
updatedAt: z13.string()
|
|
1491
2086
|
});
|
|
1492
|
-
var ThesisSchema =
|
|
1493
|
-
full:
|
|
1494
|
-
ticker:
|
|
1495
|
-
exchange:
|
|
1496
|
-
isCrypto:
|
|
1497
|
-
input:
|
|
1498
|
-
summary:
|
|
1499
|
-
bullCase:
|
|
1500
|
-
bearCase:
|
|
1501
|
-
catalyst:
|
|
1502
|
-
invalidation:
|
|
1503
|
-
timeframe:
|
|
1504
|
-
nextReviewDate:
|
|
1505
|
-
confidence:
|
|
1506
|
-
updatedAt:
|
|
1507
|
-
archivedAt:
|
|
2087
|
+
var ThesisSchema = z13.object({
|
|
2088
|
+
full: z13.string().max(80),
|
|
2089
|
+
ticker: z13.string().max(50),
|
|
2090
|
+
exchange: z13.string().optional(),
|
|
2091
|
+
isCrypto: z13.boolean(),
|
|
2092
|
+
input: z13.string().max(50),
|
|
2093
|
+
summary: z13.string().max(5e3),
|
|
2094
|
+
bullCase: z13.string().max(2e3).optional(),
|
|
2095
|
+
bearCase: z13.string().max(2e3).optional(),
|
|
2096
|
+
catalyst: z13.string().max(2e3).optional(),
|
|
2097
|
+
invalidation: z13.string().max(2e3).optional(),
|
|
2098
|
+
timeframe: z13.string().max(100).optional(),
|
|
2099
|
+
nextReviewDate: z13.string().optional(),
|
|
2100
|
+
confidence: z13.number().min(0).max(5).optional(),
|
|
2101
|
+
updatedAt: z13.string(),
|
|
2102
|
+
archivedAt: z13.string().optional()
|
|
1508
2103
|
});
|
|
1509
|
-
var WorkspaceSchema =
|
|
1510
|
-
schemaVersion:
|
|
2104
|
+
var WorkspaceSchema = z13.object({
|
|
2105
|
+
schemaVersion: z13.number().default(1),
|
|
1511
2106
|
profile: ProfileSchema.default(() => ProfileSchema.parse({})),
|
|
1512
|
-
watchlists:
|
|
2107
|
+
watchlists: z13.record(z13.string(), WatchlistSchema).refine(
|
|
1513
2108
|
(rec) => Object.keys(rec).every((k) => !RESERVED_KEYS.has(k)),
|
|
1514
2109
|
{ message: "Workspace contains a reserved key in watchlists" }
|
|
1515
2110
|
).default({}),
|
|
1516
|
-
theses:
|
|
2111
|
+
theses: z13.record(z13.string(), ThesisSchema).refine(
|
|
1517
2112
|
(rec) => Object.keys(rec).every((k) => !RESERVED_KEYS.has(k)),
|
|
1518
2113
|
{ message: "Workspace contains a reserved key in theses" }
|
|
1519
2114
|
).default({})
|
|
@@ -1645,7 +2240,7 @@ function createWorkspaceModule(dataDir, defaultExchange = "NASDAQ") {
|
|
|
1645
2240
|
{
|
|
1646
2241
|
name: "workspace_get_profile",
|
|
1647
2242
|
description: "Get the current user's trading profile and workspace settings.",
|
|
1648
|
-
inputSchema:
|
|
2243
|
+
inputSchema: z14.object({}),
|
|
1649
2244
|
readOnly: true,
|
|
1650
2245
|
openWorld: false,
|
|
1651
2246
|
handler: withMetadata(async () => {
|
|
@@ -1656,10 +2251,10 @@ function createWorkspaceModule(dataDir, defaultExchange = "NASDAQ") {
|
|
|
1656
2251
|
{
|
|
1657
2252
|
name: "workspace_update_profile",
|
|
1658
2253
|
description: "Update the user's trading profile (style, asset focus, review cadence).",
|
|
1659
|
-
inputSchema:
|
|
1660
|
-
tradingStyle:
|
|
1661
|
-
assetFocus:
|
|
1662
|
-
workflowCadence:
|
|
2254
|
+
inputSchema: z14.object({
|
|
2255
|
+
tradingStyle: z14.string().max(100).optional().describe("E.g., 'options', 'swing', 'day'"),
|
|
2256
|
+
assetFocus: z14.array(z14.string().max(50)).max(20).optional().describe("Asset classes like 'equities', 'crypto'"),
|
|
2257
|
+
workflowCadence: z14.enum(["daily", "weekly"]).optional()
|
|
1663
2258
|
}),
|
|
1664
2259
|
readOnly: false,
|
|
1665
2260
|
openWorld: false,
|
|
@@ -1687,7 +2282,7 @@ function createWorkspaceModule(dataDir, defaultExchange = "NASDAQ") {
|
|
|
1687
2282
|
{
|
|
1688
2283
|
name: "workspace_list_watchlists",
|
|
1689
2284
|
description: "List all watchlists and their instruments.",
|
|
1690
|
-
inputSchema:
|
|
2285
|
+
inputSchema: z14.object({}),
|
|
1691
2286
|
readOnly: true,
|
|
1692
2287
|
openWorld: false,
|
|
1693
2288
|
handler: withMetadata(async () => {
|
|
@@ -1698,8 +2293,8 @@ function createWorkspaceModule(dataDir, defaultExchange = "NASDAQ") {
|
|
|
1698
2293
|
{
|
|
1699
2294
|
name: "workspace_create_watchlist",
|
|
1700
2295
|
description: "Create a new empty watchlist (max 50 per workspace).",
|
|
1701
|
-
inputSchema:
|
|
1702
|
-
name:
|
|
2296
|
+
inputSchema: z14.object({
|
|
2297
|
+
name: z14.string().max(100).describe("The ID/name of the watchlist (e.g., 'core', 'swing')")
|
|
1703
2298
|
}),
|
|
1704
2299
|
readOnly: false,
|
|
1705
2300
|
openWorld: false,
|
|
@@ -1738,9 +2333,9 @@ function createWorkspaceModule(dataDir, defaultExchange = "NASDAQ") {
|
|
|
1738
2333
|
{
|
|
1739
2334
|
name: "workspace_update_watchlist",
|
|
1740
2335
|
description: "Replace the instruments in a specific watchlist.",
|
|
1741
|
-
inputSchema:
|
|
1742
|
-
name:
|
|
1743
|
-
symbols:
|
|
2336
|
+
inputSchema: z14.object({
|
|
2337
|
+
name: z14.string().max(100).describe("The ID of the watchlist to update"),
|
|
2338
|
+
symbols: z14.array(z14.string().max(50)).max(200).describe("Raw symbols to track (e.g., ['AAPL', 'BTC'])")
|
|
1744
2339
|
}),
|
|
1745
2340
|
readOnly: false,
|
|
1746
2341
|
openWorld: false,
|
|
@@ -1787,8 +2382,8 @@ function createWorkspaceModule(dataDir, defaultExchange = "NASDAQ") {
|
|
|
1787
2382
|
{
|
|
1788
2383
|
name: "workspace_get_thesis",
|
|
1789
2384
|
description: "Get the saved global investment thesis for a specific symbol. Returns a stable JSON shape with a 'found' flag.",
|
|
1790
|
-
inputSchema:
|
|
1791
|
-
symbol:
|
|
2385
|
+
inputSchema: z14.object({
|
|
2386
|
+
symbol: z14.string().max(50).describe("The raw symbol (e.g., 'AAPL')")
|
|
1792
2387
|
}),
|
|
1793
2388
|
readOnly: true,
|
|
1794
2389
|
openWorld: false,
|
|
@@ -1806,13 +2401,13 @@ function createWorkspaceModule(dataDir, defaultExchange = "NASDAQ") {
|
|
|
1806
2401
|
{
|
|
1807
2402
|
name: "workspace_save_thesis",
|
|
1808
2403
|
description: "Save or update the global investment thesis for a specific symbol (max 200 per workspace).",
|
|
1809
|
-
inputSchema:
|
|
1810
|
-
symbol:
|
|
1811
|
-
summary:
|
|
1812
|
-
bullCase:
|
|
1813
|
-
bearCase:
|
|
1814
|
-
catalyst:
|
|
1815
|
-
timeframe:
|
|
2404
|
+
inputSchema: z14.object({
|
|
2405
|
+
symbol: z14.string().max(50),
|
|
2406
|
+
summary: z14.string().max(5e3),
|
|
2407
|
+
bullCase: z14.string().max(2e3).optional(),
|
|
2408
|
+
bearCase: z14.string().max(2e3).optional(),
|
|
2409
|
+
catalyst: z14.string().max(2e3).optional(),
|
|
2410
|
+
timeframe: z14.string().max(100).optional()
|
|
1816
2411
|
}),
|
|
1817
2412
|
readOnly: false,
|
|
1818
2413
|
openWorld: false,
|
|
@@ -1877,6 +2472,7 @@ var MODULE_CATALOG = [
|
|
|
1877
2472
|
{ name: "fred", envVar: "FRED_API_KEY", toolCount: 4, factory: (config) => config.env.FRED_API_KEY ? createFredModule(config.env.FRED_API_KEY) : null },
|
|
1878
2473
|
{ name: "sentiment", envVar: null, toolCount: 2, factory: () => createSentimentModule() },
|
|
1879
2474
|
{ name: "frankfurter", envVar: null, toolCount: 5, factory: () => createFrankfurterModule() },
|
|
2475
|
+
{ name: "reddit", envVar: null, toolCount: 3, factory: () => createRedditModule() },
|
|
1880
2476
|
{ name: "workspace", envVar: null, toolCount: 7, factory: (config) => config.enableWorkspace ? createWorkspaceModule(config.dataDir || path3.join(os2.homedir(), ".stock-scanner-mcp"), config.defaultExchange) : null }
|
|
1881
2477
|
];
|
|
1882
2478
|
var TOTAL_TOOLS = MODULE_CATALOG.reduce((n, m) => n + m.toolCount, 0);
|
|
@@ -1896,7 +2492,7 @@ OPTIONS
|
|
|
1896
2492
|
--enable-workspace Enable stateful workspace (watchlists, theses, profile)
|
|
1897
2493
|
--data-dir <path> Directory for workspace storage (default: ~/.stock-scanner-mcp)
|
|
1898
2494
|
|
|
1899
|
-
MODULES (
|
|
2495
|
+
MODULES (64 tools total)
|
|
1900
2496
|
tradingview 10 tools Stock scanning, quotes, technicals (no key)
|
|
1901
2497
|
tradingview-crypto 4 tools Crypto pair scanning and technicals (no key)
|
|
1902
2498
|
sec-edgar 6 tools SEC filings, insider trades, holdings (no key)
|
|
@@ -1905,6 +2501,7 @@ MODULES (61 tools total)
|
|
|
1905
2501
|
options-cboe 1 tool CBOE put/call ratio sentiment (no key)
|
|
1906
2502
|
sentiment 2 tools Market & crypto Fear & Greed sentiment (no key)
|
|
1907
2503
|
frankfurter 5 tools Forex exchange rates and conversion (no key)
|
|
2504
|
+
reddit 3 tools Reddit trending tickers and sentiment (no key)
|
|
1908
2505
|
workspace 7 tools Stateful watchlists, theses, and profile (no key)
|
|
1909
2506
|
finnhub 9 tools Quotes, profiles, peers, news, earnings (FINNHUB_API_KEY)
|
|
1910
2507
|
alpha-vantage 5 tools Stock quotes, fundamentals, dividends (ALPHA_VANTAGE_API_KEY)
|
|
@@ -2004,7 +2601,7 @@ Tools registered: ${registeredTools}/${TOTAL_TOOLS}`);
|
|
|
2004
2601
|
server.registerPrompt("analyze_stock", {
|
|
2005
2602
|
description: "Analyze a stock ticker \u2014 includes crypto correlation check for crypto-related companies",
|
|
2006
2603
|
argsSchema: {
|
|
2007
|
-
ticker:
|
|
2604
|
+
ticker: z15.string().describe("Stock ticker symbol (e.g. AAPL, CIFR, MARA)")
|
|
2008
2605
|
}
|
|
2009
2606
|
}, async ({ ticker }) => {
|
|
2010
2607
|
return {
|
|
@@ -2038,8 +2635,8 @@ Tools registered: ${registeredTools}/${TOTAL_TOOLS}`);
|
|
|
2038
2635
|
server.registerPrompt("intraday_candidates", {
|
|
2039
2636
|
description: "Find intraday trading candidates with custom price range and filters",
|
|
2040
2637
|
argsSchema: {
|
|
2041
|
-
min_price:
|
|
2042
|
-
max_price:
|
|
2638
|
+
min_price: z15.string().optional().describe("Minimum stock price (default: 10)"),
|
|
2639
|
+
max_price: z15.string().optional().describe("Maximum stock price (default: 50)")
|
|
2043
2640
|
}
|
|
2044
2641
|
}, async ({ min_price, max_price }) => {
|
|
2045
2642
|
const min = min_price || "10";
|
package/dist/sidecar/index.js
CHANGED
|
@@ -39,10 +39,15 @@ import {
|
|
|
39
39
|
scanStocks,
|
|
40
40
|
searchFilings,
|
|
41
41
|
searchSeries
|
|
42
|
-
} from "../chunk-
|
|
42
|
+
} from "../chunk-T5IF6777.js";
|
|
43
43
|
|
|
44
44
|
// src/sidecar/server.ts
|
|
45
45
|
import * as http from "http";
|
|
46
|
+
import { readFileSync } from "fs";
|
|
47
|
+
import { join, dirname } from "path";
|
|
48
|
+
import { fileURLToPath } from "url";
|
|
49
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
50
|
+
var __dirname = dirname(__filename);
|
|
46
51
|
var SYMBOL_RE = /^[A-Z][A-Z0-9._-]{0,19}$/i;
|
|
47
52
|
var MAX_BODY_BYTES = 1024 * 1024;
|
|
48
53
|
var LOCALHOST_ORIGINS = /* @__PURE__ */ new Set(["http://localhost", "http://127.0.0.1"]);
|
|
@@ -128,6 +133,16 @@ function createServer2(config) {
|
|
|
128
133
|
json(res, req, 200, { status: "ok" });
|
|
129
134
|
return;
|
|
130
135
|
}
|
|
136
|
+
if (path === "/openapi.json" && req.method === "GET") {
|
|
137
|
+
try {
|
|
138
|
+
const specPath = join(__dirname, "../../docs/sidecar-openapi.json");
|
|
139
|
+
const spec = JSON.parse(readFileSync(specPath, "utf-8"));
|
|
140
|
+
json(res, req, 200, spec);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
json(res, req, 404, { error: "OpenAPI specification not found" });
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
131
146
|
if (path === "/tradingview/scan" && req.method === "POST") {
|
|
132
147
|
const body = await parseBody(req);
|
|
133
148
|
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
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
|
+
"version": "1.16.2",
|
|
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": {
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
"stock-scanner-sidecar": "./dist/sidecar/index.js"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "tsup src/index.ts src/install-skills.ts src/sidecar/index.ts --format esm --dts --clean",
|
|
13
|
+
"build": "npm run generate-openapi && tsup src/index.ts src/install-skills.ts src/sidecar/index.ts --format esm --dts --clean",
|
|
14
14
|
"dev": "tsup src/index.ts --format esm --watch",
|
|
15
15
|
"test": "vitest run",
|
|
16
16
|
"test:watch": "vitest",
|
|
17
17
|
"lint": "tsc --noEmit",
|
|
18
|
+
"generate-openapi": "tsx src/scripts/generate-sidecar-openapi.ts",
|
|
18
19
|
"validate-tools": "tsx src/scripts/validate-tools.ts",
|
|
19
20
|
"validate-structure": "tsx src/scripts/validate-structure.ts",
|
|
20
21
|
"prepublishOnly": "npm run build && npm test"
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
"@types/proper-lockfile": "^4.1.4",
|
|
89
90
|
"tsup": "^8.0.0",
|
|
90
91
|
"tsx": "^4.21.0",
|
|
91
|
-
"typescript": "^
|
|
92
|
+
"typescript": "^6.0.2",
|
|
92
93
|
"vitest": "^4.1.1"
|
|
93
94
|
}
|
|
94
95
|
}
|