chartlibrary-mcp 1.0.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chart Library (chartlibrary.io)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: chartlibrary-mcp
3
+ Version: 1.0.0
4
+ Summary: Chart Library MCP Server — pattern intelligence for AI agents
5
+ Author-email: Chart Library <graham@chartlibrary.io>
6
+ Project-URL: Homepage, https://chartlibrary.io
7
+ Project-URL: Documentation, https://chartlibrary.io/developers
8
+ Project-URL: Repository, https://github.com/grahammccain/chart-library-mcp
9
+ Keywords: mcp,chart patterns,stock analysis,AI agent,trading
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Office/Business :: Financial :: Investment
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE.bak
17
+ Requires-Dist: mcp>=1.0.0
18
+ Requires-Dist: requests>=2.28.0
19
+ Dynamic: license-file
20
+
21
+ # Chart Library MCP Server
22
+
23
+ Give your AI agent 10 years of chart pattern intelligence. Search 24 million historical patterns and see what happened next.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install chartlibrary-mcp
29
+ ```
30
+
31
+ ## Usage with Claude
32
+
33
+ ```bash
34
+ # Claude Code
35
+ claude mcp add chart-library -- chartlibrary-mcp
36
+
37
+ # Or run directly
38
+ CHART_LIBRARY_API_KEY=cl_your_key chartlibrary-mcp
39
+ ```
40
+
41
+ ## Tools
42
+
43
+ | Tool | Description |
44
+ |------|-------------|
45
+ | `analyze_pattern` | Search + forward returns + AI summary (recommended) |
46
+ | `search_charts` | Find 10 most similar historical patterns |
47
+ | `get_follow_through` | Forward returns (1/3/5/10-day) for matches |
48
+ | `get_pattern_summary` | AI-generated plain-English summary |
49
+ | `get_discover_picks` | Daily top patterns ranked by interest score |
50
+ | `search_batch` | Multi-symbol parallel search (up to 20) |
51
+ | `get_status` | Database stats and coverage |
52
+
53
+ ## Example Conversation
54
+
55
+ > **User:** What does NVDA's chart look like right now?
56
+ >
57
+ > **Claude (using Chart Library):** NVDA's current pattern matches 10 historical setups. The closest is AAPL from May 2016 (93% similarity). Of the 10 matches, 8 went up over 5 days with an average gain of +3.0%. The current market regime resembles the post-SVB period of early 2023, which historically resolved bullishly.
58
+
59
+ ## API Key
60
+
61
+ Get a free API key (500 calls/day) at [chartlibrary.io/developers](https://chartlibrary.io/developers).
62
+
63
+ Set it as an environment variable:
64
+ ```bash
65
+ export CHART_LIBRARY_API_KEY=cl_your_key
66
+ ```
67
+
68
+ ## Links
69
+
70
+ - Website: [chartlibrary.io](https://chartlibrary.io)
71
+ - API Docs: [chartlibrary.io/api/docs](https://chartlibrary.io/api/docs)
72
+ - Developer Portal: [chartlibrary.io/developers](https://chartlibrary.io/developers)
73
+ - Regime Tracker: [chartlibrary.io/regime](https://chartlibrary.io/regime)
@@ -0,0 +1,53 @@
1
+ # Chart Library MCP Server
2
+
3
+ Give your AI agent 10 years of chart pattern intelligence. Search 24 million historical patterns and see what happened next.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install chartlibrary-mcp
9
+ ```
10
+
11
+ ## Usage with Claude
12
+
13
+ ```bash
14
+ # Claude Code
15
+ claude mcp add chart-library -- chartlibrary-mcp
16
+
17
+ # Or run directly
18
+ CHART_LIBRARY_API_KEY=cl_your_key chartlibrary-mcp
19
+ ```
20
+
21
+ ## Tools
22
+
23
+ | Tool | Description |
24
+ |------|-------------|
25
+ | `analyze_pattern` | Search + forward returns + AI summary (recommended) |
26
+ | `search_charts` | Find 10 most similar historical patterns |
27
+ | `get_follow_through` | Forward returns (1/3/5/10-day) for matches |
28
+ | `get_pattern_summary` | AI-generated plain-English summary |
29
+ | `get_discover_picks` | Daily top patterns ranked by interest score |
30
+ | `search_batch` | Multi-symbol parallel search (up to 20) |
31
+ | `get_status` | Database stats and coverage |
32
+
33
+ ## Example Conversation
34
+
35
+ > **User:** What does NVDA's chart look like right now?
36
+ >
37
+ > **Claude (using Chart Library):** NVDA's current pattern matches 10 historical setups. The closest is AAPL from May 2016 (93% similarity). Of the 10 matches, 8 went up over 5 days with an average gain of +3.0%. The current market regime resembles the post-SVB period of early 2023, which historically resolved bullishly.
38
+
39
+ ## API Key
40
+
41
+ Get a free API key (500 calls/day) at [chartlibrary.io/developers](https://chartlibrary.io/developers).
42
+
43
+ Set it as an environment variable:
44
+ ```bash
45
+ export CHART_LIBRARY_API_KEY=cl_your_key
46
+ ```
47
+
48
+ ## Links
49
+
50
+ - Website: [chartlibrary.io](https://chartlibrary.io)
51
+ - API Docs: [chartlibrary.io/api/docs](https://chartlibrary.io/api/docs)
52
+ - Developer Portal: [chartlibrary.io/developers](https://chartlibrary.io/developers)
53
+ - Regime Tracker: [chartlibrary.io/regime](https://chartlibrary.io/regime)
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: chartlibrary-mcp
3
+ Version: 1.0.0
4
+ Summary: Chart Library MCP Server — pattern intelligence for AI agents
5
+ Author-email: Chart Library <graham@chartlibrary.io>
6
+ Project-URL: Homepage, https://chartlibrary.io
7
+ Project-URL: Documentation, https://chartlibrary.io/developers
8
+ Project-URL: Repository, https://github.com/grahammccain/chart-library-mcp
9
+ Keywords: mcp,chart patterns,stock analysis,AI agent,trading
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Office/Business :: Financial :: Investment
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE.bak
17
+ Requires-Dist: mcp>=1.0.0
18
+ Requires-Dist: requests>=2.28.0
19
+ Dynamic: license-file
20
+
21
+ # Chart Library MCP Server
22
+
23
+ Give your AI agent 10 years of chart pattern intelligence. Search 24 million historical patterns and see what happened next.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install chartlibrary-mcp
29
+ ```
30
+
31
+ ## Usage with Claude
32
+
33
+ ```bash
34
+ # Claude Code
35
+ claude mcp add chart-library -- chartlibrary-mcp
36
+
37
+ # Or run directly
38
+ CHART_LIBRARY_API_KEY=cl_your_key chartlibrary-mcp
39
+ ```
40
+
41
+ ## Tools
42
+
43
+ | Tool | Description |
44
+ |------|-------------|
45
+ | `analyze_pattern` | Search + forward returns + AI summary (recommended) |
46
+ | `search_charts` | Find 10 most similar historical patterns |
47
+ | `get_follow_through` | Forward returns (1/3/5/10-day) for matches |
48
+ | `get_pattern_summary` | AI-generated plain-English summary |
49
+ | `get_discover_picks` | Daily top patterns ranked by interest score |
50
+ | `search_batch` | Multi-symbol parallel search (up to 20) |
51
+ | `get_status` | Database stats and coverage |
52
+
53
+ ## Example Conversation
54
+
55
+ > **User:** What does NVDA's chart look like right now?
56
+ >
57
+ > **Claude (using Chart Library):** NVDA's current pattern matches 10 historical setups. The closest is AAPL from May 2016 (93% similarity). Of the 10 matches, 8 went up over 5 days with an average gain of +3.0%. The current market regime resembles the post-SVB period of early 2023, which historically resolved bullishly.
58
+
59
+ ## API Key
60
+
61
+ Get a free API key (500 calls/day) at [chartlibrary.io/developers](https://chartlibrary.io/developers).
62
+
63
+ Set it as an environment variable:
64
+ ```bash
65
+ export CHART_LIBRARY_API_KEY=cl_your_key
66
+ ```
67
+
68
+ ## Links
69
+
70
+ - Website: [chartlibrary.io](https://chartlibrary.io)
71
+ - API Docs: [chartlibrary.io/api/docs](https://chartlibrary.io/api/docs)
72
+ - Developer Portal: [chartlibrary.io/developers](https://chartlibrary.io/developers)
73
+ - Regime Tracker: [chartlibrary.io/regime](https://chartlibrary.io/regime)
@@ -0,0 +1,10 @@
1
+ LICENSE.bak
2
+ README.md
3
+ mcp_server.py
4
+ pyproject.toml
5
+ chartlibrary_mcp.egg-info/PKG-INFO
6
+ chartlibrary_mcp.egg-info/SOURCES.txt
7
+ chartlibrary_mcp.egg-info/dependency_links.txt
8
+ chartlibrary_mcp.egg-info/entry_points.txt
9
+ chartlibrary_mcp.egg-info/requires.txt
10
+ chartlibrary_mcp.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ chartlibrary-mcp = mcp_server:main
@@ -0,0 +1,2 @@
1
+ mcp>=1.0.0
2
+ requests>=2.28.0
@@ -0,0 +1,422 @@
1
+ """
2
+ Chart Library MCP Server — expose chart pattern search tools for Claude Desktop / Claude Code.
3
+
4
+ 7 tools:
5
+ 1. search_charts — text query → similar patterns
6
+ 2. get_follow_through — results → forward returns
7
+ 3. get_pattern_summary — results → English summary
8
+ 4. get_status — DB stats
9
+ 5. analyze_pattern — combined search + follow-through + summary
10
+ 6. get_discover_picks — top daily picks by interest score
11
+ 7. search_batch — batch multi-symbol search
12
+
13
+ Dual mode:
14
+ - If CHART_LIBRARY_API_KEY is set → HTTP API calls (cloud users)
15
+ - Otherwise → direct Python imports (self-hosted / local dev)
16
+
17
+ Install:
18
+ claude mcp add chart-library -- python mcp_server.py
19
+ """
20
+
21
+ import json
22
+ import logging
23
+ import os
24
+ import sys
25
+
26
+ # Ensure project root is on path for direct imports
27
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
28
+
29
+ from mcp.server.fastmcp import FastMCP
30
+
31
+ log = logging.getLogger("mcp_server")
32
+
33
+ _API_KEY = os.getenv("CHART_LIBRARY_API_KEY")
34
+ _API_BASE = os.getenv("CHART_LIBRARY_API_URL", "https://chartlibrary.io")
35
+
36
+ mcp = FastMCP("chart-library")
37
+
38
+
39
+ # ── Transport layer ──────────────────────────────────────────
40
+
41
+ def _use_http() -> bool:
42
+ """Whether to use HTTP API calls (vs direct Python imports)."""
43
+ return bool(_API_KEY)
44
+
45
+
46
+ def _http_post(path: str, body: dict) -> dict:
47
+ """Make an authenticated POST to the Chart Library API."""
48
+ import requests
49
+ url = f"{_API_BASE}{path}"
50
+ headers = {"Authorization": f"Bearer {_API_KEY}", "Content-Type": "application/json"}
51
+ resp = requests.post(url, json=body, headers=headers, timeout=30)
52
+ resp.raise_for_status()
53
+ return resp.json()
54
+
55
+
56
+ def _http_get(path: str) -> dict:
57
+ """Make an authenticated GET to the Chart Library API."""
58
+ import requests
59
+ url = f"{_API_BASE}{path}"
60
+ headers = {"Authorization": f"Bearer {_API_KEY}"}
61
+ resp = requests.get(url, headers=headers, timeout=30)
62
+ resp.raise_for_status()
63
+ return resp.json()
64
+
65
+
66
+ # ── Direct Python imports (local mode) ──────────────────────
67
+
68
+ def _direct_search(query: str, timeframe: str = "auto", top_n: int = 10) -> dict:
69
+ """Run search directly via Python imports."""
70
+ from dotenv import load_dotenv
71
+ load_dotenv()
72
+
73
+ from services.query_parser import parse_text_query, validate_text_query
74
+ from db.embeddings import search_similar_to_day, search_similar_to_window, MULTI_DAY_SCALES
75
+
76
+ parsed = parse_text_query(query)
77
+ if parsed is None:
78
+ return {"error": "Could not parse query. Use format: AAPL 2024-06-15"}
79
+
80
+ if len(parsed) == 3:
81
+ symbol, date_str, scale = parsed
82
+ timeframe = scale
83
+ else:
84
+ symbol, date_str = parsed
85
+ if timeframe == "auto":
86
+ timeframe = "rth"
87
+
88
+ error = validate_text_query(symbol, date_str, timeframe)
89
+ if error:
90
+ return {"error": error}
91
+
92
+ if timeframe in MULTI_DAY_SCALES:
93
+ results = search_similar_to_window(symbol, date_str, top_n=top_n, scale=timeframe)
94
+ else:
95
+ results = search_similar_to_day(symbol, date_str, top_n=top_n, timeframe=timeframe)
96
+
97
+ return {
98
+ "query": {"symbol": symbol, "date": date_str},
99
+ "results": results[:top_n],
100
+ "count": len(results[:top_n]),
101
+ "timeframe": timeframe,
102
+ }
103
+
104
+
105
+ def _direct_follow_through(results: list[dict]) -> dict:
106
+ """Compute follow-through directly."""
107
+ from dotenv import load_dotenv
108
+ load_dotenv()
109
+
110
+ from services.follow_through import compute_follow_through
111
+ return compute_follow_through(results)
112
+
113
+
114
+ def _direct_summary(query_label: str, n_matches: int, horizon_returns: dict) -> dict:
115
+ """Generate summary directly."""
116
+ from dotenv import load_dotenv
117
+ load_dotenv()
118
+
119
+ from services.summary_service import generate_pattern_summary
120
+ text = generate_pattern_summary(query_label, n_matches, horizon_returns)
121
+ return {"summary": text}
122
+
123
+
124
+ def _direct_status() -> dict:
125
+ """Get embedding status directly."""
126
+ from dotenv import load_dotenv
127
+ load_dotenv()
128
+
129
+ from db.embeddings import embedding_status
130
+ return embedding_status()
131
+
132
+
133
+ def _direct_analyze(query: str, timeframe: str = "auto", top_n: int = 10, include_summary: bool = True) -> dict:
134
+ """Run combined analysis directly via Python imports."""
135
+ from dotenv import load_dotenv
136
+ load_dotenv()
137
+
138
+ # Search
139
+ search_result = _direct_search(query, timeframe, top_n)
140
+ if "error" in search_result:
141
+ return search_result
142
+
143
+ results = search_result.get("results", [])
144
+ if not results:
145
+ return {**search_result, "follow_through": None, "outcome_distribution": None, "summary": None}
146
+
147
+ # Follow-through
148
+ ft = _direct_follow_through(results)
149
+
150
+ # Outcome distribution
151
+ rets_5d = ft.get("horizon_returns", {}).get(5, [])
152
+ outcome_dist = None
153
+ if rets_5d:
154
+ clean = [r for r in rets_5d if r is not None]
155
+ if clean:
156
+ up = sum(1 for r in clean if r > 0)
157
+ outcome_dist = {
158
+ "up_count": up,
159
+ "down_count": len(clean) - up,
160
+ "total": len(clean),
161
+ "median_return": round(sorted(clean)[len(clean) // 2], 2),
162
+ }
163
+
164
+ # Summary
165
+ summary_text = None
166
+ if include_summary:
167
+ try:
168
+ q = search_result["query"]
169
+ label = f"{q['symbol']} {q['date']}"
170
+ summary_result = _direct_summary(label, len(results), ft.get("horizon_returns", {}))
171
+ summary_text = summary_result.get("summary")
172
+ except Exception:
173
+ pass
174
+
175
+ return {
176
+ **search_result,
177
+ "follow_through": ft,
178
+ "outcome_distribution": outcome_dist,
179
+ "summary": summary_text,
180
+ }
181
+
182
+
183
+ # ── Tool implementations ─────────────────────────────────────
184
+
185
+ @mcp.tool()
186
+ async def search_charts(query: str, timeframe: str = "auto", top_n: int = 10) -> str:
187
+ """Search for historically similar chart patterns.
188
+
189
+ Input a symbol and date (e.g. 'AAPL 2024-06-15') to find the top 10
190
+ most similar historical charts from 800M+ minute bars.
191
+ Results include match scores and similarity distances.
192
+
193
+ Args:
194
+ query: Symbol + date, e.g. 'AAPL 2024-06-15' or 'TSLA 6/15/24 3d'
195
+ timeframe: Session: rth (regular hours), premarket, rth_3d, rth_5d, or auto
196
+ top_n: Number of results (1-50)
197
+ """
198
+ try:
199
+ if _use_http():
200
+ result = _http_post("/api/v1/search/text", {
201
+ "query": query, "timeframe": timeframe, "top_n": top_n,
202
+ })
203
+ else:
204
+ result = _direct_search(query, timeframe, top_n)
205
+ return json.dumps(result, default=str, indent=2)
206
+ except Exception as e:
207
+ return json.dumps({"error": str(e)})
208
+
209
+
210
+ @mcp.tool()
211
+ async def get_follow_through(results: list[dict]) -> str:
212
+ """Get forward return analysis for search results.
213
+
214
+ Pass the results from search_charts to see what happened 1, 3, 5, and 10
215
+ days later in each historical match. Returns % returns and cumulative paths.
216
+
217
+ Args:
218
+ results: Search results from search_charts (list of {symbol, date, timeframe, metadata})
219
+ """
220
+ try:
221
+ if _use_http():
222
+ result = _http_post("/api/v1/follow-through", {"results": results})
223
+ else:
224
+ result = _direct_follow_through(results)
225
+ return json.dumps(result, default=str, indent=2)
226
+ except Exception as e:
227
+ return json.dumps({"error": str(e)})
228
+
229
+
230
+ @mcp.tool()
231
+ async def get_pattern_summary(query_label: str, n_matches: int, horizon_returns: dict) -> str:
232
+ """Generate an AI-written plain English summary of pattern search results.
233
+
234
+ Returns a concise 2-3 sentence summary suitable for retail traders.
235
+
236
+ Args:
237
+ query_label: Human-readable query label (e.g. 'AAPL 2024-06-15')
238
+ n_matches: Number of matches found
239
+ horizon_returns: Forward returns dict {1: [...], 3: [...], 5: [...], 10: [...]}
240
+ """
241
+ try:
242
+ if _use_http():
243
+ result = _http_post("/api/v1/summary", {
244
+ "query_label": query_label,
245
+ "n_matches": n_matches,
246
+ "horizon_returns": horizon_returns,
247
+ })
248
+ else:
249
+ result = _direct_summary(query_label, n_matches, horizon_returns)
250
+ return json.dumps(result, default=str, indent=2)
251
+ except Exception as e:
252
+ return json.dumps({"error": str(e)})
253
+
254
+
255
+ @mcp.tool()
256
+ async def get_status() -> str:
257
+ """Get Chart Library database statistics.
258
+
259
+ Returns total embeddings, coverage percentage, date range, and distinct symbols.
260
+ """
261
+ try:
262
+ if _use_http():
263
+ result = _http_get("/api/v1/status")
264
+ else:
265
+ result = _direct_status()
266
+ return json.dumps(result, default=str, indent=2)
267
+ except Exception as e:
268
+ return json.dumps({"error": str(e)})
269
+
270
+
271
+ @mcp.tool()
272
+ async def analyze_pattern(query: str, timeframe: str = "auto", top_n: int = 10, include_summary: bool = True) -> str:
273
+ """Complete pattern analysis in one call: search + follow-through + AI summary.
274
+
275
+ This is the recommended tool for most use cases. It combines search_charts,
276
+ get_follow_through, and get_pattern_summary into a single call.
277
+
278
+ Returns matching patterns, forward return statistics (1/3/5/10 day),
279
+ outcome distribution, and an AI-written summary.
280
+
281
+ Args:
282
+ query: Symbol + date, e.g. 'AAPL 2024-06-15' or 'TSLA 6/15/24 3d'
283
+ timeframe: Session: rth (regular hours), premarket, rth_3d, rth_5d, or auto
284
+ top_n: Number of results (1-50)
285
+ include_summary: Whether to include AI-generated summary (default True)
286
+ """
287
+ try:
288
+ if _use_http():
289
+ result = _http_post("/api/v1/analyze", {
290
+ "query": query, "timeframe": timeframe,
291
+ "top_n": top_n, "include_summary": include_summary,
292
+ })
293
+ else:
294
+ result = _direct_analyze(query, timeframe, top_n, include_summary)
295
+ return json.dumps(result, default=str, indent=2)
296
+ except Exception as e:
297
+ return json.dumps({"error": str(e)})
298
+
299
+
300
+ @mcp.tool()
301
+ async def get_discover_picks(date: str = "", limit: int = 20) -> str:
302
+ """Get top daily chart pattern picks ranked by interest score.
303
+
304
+ Returns the most interesting patterns from the automated nightly scan,
305
+ with AI summaries, predicted returns, and confidence scores.
306
+
307
+ Args:
308
+ date: Date in YYYY-MM-DD format (defaults to latest available)
309
+ limit: Max picks to return (1-50, default 20)
310
+ """
311
+ try:
312
+ if _use_http():
313
+ params = f"?limit={limit}"
314
+ if date:
315
+ params += f"&date={date}"
316
+ result = _http_get(f"/api/v1/discover/picks{params}")
317
+ else:
318
+ # Direct DB access for local mode
319
+ from dotenv import load_dotenv
320
+ load_dotenv()
321
+ from db.connection import get_conn, put_conn
322
+ conn = get_conn()
323
+ try:
324
+ with conn.cursor() as cur:
325
+ if date:
326
+ pick_date = date
327
+ else:
328
+ cur.execute("SELECT MAX(test_date)::text FROM forward_tests WHERE interest_score IS NOT NULL")
329
+ row = cur.fetchone()
330
+ pick_date = row[0] if row and row[0] else None
331
+
332
+ if not pick_date:
333
+ return json.dumps({"date": "", "picks": [], "count": 0})
334
+
335
+ cur.execute("""
336
+ SELECT symbol, test_date::text, direction, interest_score,
337
+ wpred_1d, wpred_5d, wpred_10d, n_matches, summary_text
338
+ FROM forward_tests
339
+ WHERE test_date = %s AND interest_score IS NOT NULL
340
+ ORDER BY interest_score DESC LIMIT %s
341
+ """, (pick_date, limit))
342
+ rows = cur.fetchall()
343
+ picks = [{
344
+ "symbol": r[0], "date": r[1], "direction": r[2],
345
+ "interest_score": r[3], "wpred_1d": r[4], "wpred_5d": r[5],
346
+ "wpred_10d": r[6], "n_matches": r[7], "summary_text": r[8],
347
+ } for r in rows]
348
+ finally:
349
+ put_conn(conn)
350
+ result = {"date": pick_date, "picks": picks, "count": len(picks)}
351
+ return json.dumps(result, default=str, indent=2)
352
+ except Exception as e:
353
+ return json.dumps({"error": str(e)})
354
+
355
+
356
+ @mcp.tool()
357
+ async def search_batch(symbols: list[str], date: str, timeframe: str = "rth", top_n: int = 10) -> str:
358
+ """Search for similar patterns across multiple symbols at once.
359
+
360
+ Batch version of search_charts — runs parallel searches for up to 20 symbols
361
+ on the same date and returns forward return statistics for each.
362
+
363
+ Args:
364
+ symbols: List of ticker symbols (max 20), e.g. ['AAPL', 'MSFT', 'NVDA']
365
+ date: Date in YYYY-MM-DD format
366
+ timeframe: Session: rth, premarket, rth_3d, rth_5d (default rth)
367
+ top_n: Number of results per symbol (1-50)
368
+ """
369
+ try:
370
+ if _use_http():
371
+ result = _http_post("/api/v1/search/batch", {
372
+ "symbols": symbols, "date": date,
373
+ "timeframe": timeframe, "top_n": top_n,
374
+ })
375
+ else:
376
+ # Direct mode: run searches sequentially
377
+ from dotenv import load_dotenv
378
+ load_dotenv()
379
+ from services.follow_through import compute_follow_through
380
+ from services.stats_service import compute_stats
381
+
382
+ batch_results = []
383
+ for sym in symbols[:20]:
384
+ try:
385
+ sr = _direct_search(f"{sym} {date}", timeframe, top_n)
386
+ results = sr.get("results", [])
387
+ if results:
388
+ ft = compute_follow_through(results, max_workers=1)
389
+ stats = compute_stats(ft["horizon_returns"])
390
+ batch_results.append({
391
+ "symbol": sym.upper(), "date": date,
392
+ "count": len(results),
393
+ "horizon_returns": ft["horizon_returns"],
394
+ "stats": stats,
395
+ })
396
+ else:
397
+ batch_results.append({
398
+ "symbol": sym.upper(), "date": date,
399
+ "count": 0, "horizon_returns": {}, "stats": {},
400
+ })
401
+ except Exception as e:
402
+ batch_results.append({
403
+ "symbol": sym.upper(), "date": date,
404
+ "count": 0, "horizon_returns": {}, "stats": {},
405
+ "error": str(e),
406
+ })
407
+ result = {"date": date, "timeframe": timeframe, "results": batch_results}
408
+ return json.dumps(result, default=str, indent=2)
409
+ except Exception as e:
410
+ return json.dumps({"error": str(e)})
411
+
412
+
413
+ # ── Entry point ──────────────────────────────────────────────
414
+
415
+ def main():
416
+ """Entry point for console script and direct execution."""
417
+ transport = os.getenv("MCP_TRANSPORT", "stdio")
418
+ mcp.run(transport=transport)
419
+
420
+
421
+ if __name__ == "__main__":
422
+ main()
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "chartlibrary-mcp"
7
+ version = "1.0.0"
8
+ description = "Chart Library MCP Server — pattern intelligence for AI agents"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [
12
+ {name = "Chart Library", email = "graham@chartlibrary.io"},
13
+ ]
14
+ keywords = ["mcp", "chart patterns", "stock analysis", "AI agent", "trading"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Topic :: Office/Business :: Financial :: Investment",
19
+ "Programming Language :: Python :: 3",
20
+ ]
21
+ dependencies = [
22
+ "mcp>=1.0.0",
23
+ "requests>=2.28.0",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://chartlibrary.io"
28
+ Documentation = "https://chartlibrary.io/developers"
29
+ Repository = "https://github.com/grahammccain/chart-library-mcp"
30
+
31
+ [project.scripts]
32
+ chartlibrary-mcp = "mcp_server:main"
33
+
34
+ [tool.setuptools]
35
+ py-modules = ["mcp_server"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+