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.
- chartlibrary_mcp-1.0.0/LICENSE.bak +21 -0
- chartlibrary_mcp-1.0.0/PKG-INFO +73 -0
- chartlibrary_mcp-1.0.0/README.md +53 -0
- chartlibrary_mcp-1.0.0/chartlibrary_mcp.egg-info/PKG-INFO +73 -0
- chartlibrary_mcp-1.0.0/chartlibrary_mcp.egg-info/SOURCES.txt +10 -0
- chartlibrary_mcp-1.0.0/chartlibrary_mcp.egg-info/dependency_links.txt +1 -0
- chartlibrary_mcp-1.0.0/chartlibrary_mcp.egg-info/entry_points.txt +2 -0
- chartlibrary_mcp-1.0.0/chartlibrary_mcp.egg-info/requires.txt +2 -0
- chartlibrary_mcp-1.0.0/chartlibrary_mcp.egg-info/top_level.txt +1 -0
- chartlibrary_mcp-1.0.0/mcp_server.py +422 -0
- chartlibrary_mcp-1.0.0/pyproject.toml +35 -0
- chartlibrary_mcp-1.0.0/setup.cfg +4 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcp_server
|
|
@@ -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"]
|