vanoe-intelligence-mcp 0.1.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.
- vanoe_intelligence_mcp-0.1.0/LICENSE +21 -0
- vanoe_intelligence_mcp-0.1.0/PKG-INFO +143 -0
- vanoe_intelligence_mcp-0.1.0/README.md +113 -0
- vanoe_intelligence_mcp-0.1.0/__init__.py +0 -0
- vanoe_intelligence_mcp-0.1.0/__main__.py +4 -0
- vanoe_intelligence_mcp-0.1.0/client.py +72 -0
- vanoe_intelligence_mcp-0.1.0/pyproject.toml +44 -0
- vanoe_intelligence_mcp-0.1.0/server.py +129 -0
- vanoe_intelligence_mcp-0.1.0/setup.cfg +4 -0
- vanoe_intelligence_mcp-0.1.0/tests/test_client.py +97 -0
- vanoe_intelligence_mcp-0.1.0/tests/test_server.py +108 -0
- vanoe_intelligence_mcp-0.1.0/vanoe_intelligence_mcp.egg-info/PKG-INFO +143 -0
- vanoe_intelligence_mcp-0.1.0/vanoe_intelligence_mcp.egg-info/SOURCES.txt +19 -0
- vanoe_intelligence_mcp-0.1.0/vanoe_intelligence_mcp.egg-info/dependency_links.txt +1 -0
- vanoe_intelligence_mcp-0.1.0/vanoe_intelligence_mcp.egg-info/entry_points.txt +2 -0
- vanoe_intelligence_mcp-0.1.0/vanoe_intelligence_mcp.egg-info/requires.txt +6 -0
- vanoe_intelligence_mcp-0.1.0/vanoe_intelligence_mcp.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VanoeAI Ltd
|
|
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,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vanoe-intelligence-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for the Vanoe Market Intelligence API: composite verdicts, trend-stage analysis, Point & Figure, sector breadth, macro regime and SEC filings as tools for Claude and other MCP clients.
|
|
5
|
+
Author-email: VanoeAI Ltd <support@vanoe.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://api.vanoe.ai
|
|
8
|
+
Project-URL: Documentation, https://api.vanoe.ai/docs
|
|
9
|
+
Project-URL: Get an API key, https://api.vanoe.ai/signup
|
|
10
|
+
Keywords: mcp,model-context-protocol,finance,stocks,trading,signals,claude
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: mcp<3.0,>=2.0
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# Vanoe Market Intelligence MCP server
|
|
32
|
+
|
|
33
|
+
Exposes the [Vanoe Market Intelligence API](https://api.vanoe.ai) as tools any
|
|
34
|
+
MCP-capable AI assistant (Claude Desktop, Claude Code, Cursor, etc.) can call
|
|
35
|
+
directly: a composite 0–100 verdict per ticker, trend-stage analysis (current
|
|
36
|
+
and a backtest-safe weekly history), Point & Figure signals, sector breadth,
|
|
37
|
+
the macro risk regime, SEC insider and 8-K filings, the macro calendar, and
|
|
38
|
+
your usage.
|
|
39
|
+
|
|
40
|
+
This is a thin authenticated client, not a free tier: every tool call is a
|
|
41
|
+
real, metered call against your plan using your own API key, exactly like
|
|
42
|
+
calling the REST API yourself. Get a free key (1,000 credits a month, no card)
|
|
43
|
+
at https://api.vanoe.ai/signup.
|
|
44
|
+
|
|
45
|
+
> Informational data only. Not financial advice. See https://api.vanoe.ai/terms.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
No install step is needed with [uv](https://docs.astral.sh/uv/) — `uvx` fetches
|
|
50
|
+
and runs the package on demand:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
uvx vanoe-intelligence-mcp
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or install it permanently with `pipx install vanoe-intelligence-mcp` or
|
|
57
|
+
`pip install vanoe-intelligence-mcp`, which puts a `vanoe-intelligence-mcp`
|
|
58
|
+
command on your PATH.
|
|
59
|
+
|
|
60
|
+
## Configure your MCP client
|
|
61
|
+
|
|
62
|
+
Set one environment variable, `VANOE_API_KEY`. `VANOE_API_BASE_URL` is optional
|
|
63
|
+
and defaults to `https://api.vanoe.ai`.
|
|
64
|
+
|
|
65
|
+
**Claude Desktop** (`claude_desktop_config.json`) and most other clients:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"vanoe": {
|
|
71
|
+
"command": "uvx",
|
|
72
|
+
"args": ["vanoe-intelligence-mcp"],
|
|
73
|
+
"env": { "VANOE_API_KEY": "sk_live_your_key_here" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Claude Code**:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
claude mcp add vanoe -e VANOE_API_KEY=sk_live_your_key_here -- uvx vanoe-intelligence-mcp
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then ask your assistant things like *"What's the verdict on NVDA and why?"*,
|
|
86
|
+
*"Which stage are AAPL, MSFT and AMD in?"*, or *"Is the macro regime risk-on
|
|
87
|
+
right now?"* — it will call the tools below.
|
|
88
|
+
|
|
89
|
+
## Tools
|
|
90
|
+
|
|
91
|
+
| Tool | API call | Credits |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `get_verdict(ticker)` | `GET /v1/verdict/{ticker}` | 5 |
|
|
94
|
+
| `get_stage(tickers)` | `GET /v1/signals/stage` | 1 per ticker |
|
|
95
|
+
| `get_stage_history(ticker, weeks)` | `GET /v1/signals/stage/history` | 5 |
|
|
96
|
+
| `get_pnf(tickers)` | `GET /v1/signals/pnf` | 1 per ticker |
|
|
97
|
+
| `get_sector_breadth()` | `GET /v1/sector-breadth` | 1 |
|
|
98
|
+
| `get_macro()` | `GET /v1/macro` | 1 |
|
|
99
|
+
| `get_filings(ticker)` | `GET /v1/filings/{ticker}` | 1 |
|
|
100
|
+
| `get_calendar()` | `GET /v1/calendar` | 1 |
|
|
101
|
+
| `get_usage()` | `GET /v1/usage` | 0 |
|
|
102
|
+
|
|
103
|
+
Tools never raise. Any failure — missing key, network error, quota exceeded,
|
|
104
|
+
an unknown ticker — comes back as `{"error": {"code", "message",
|
|
105
|
+
"http_status"}}` carrying the API's own message, so the assistant can explain
|
|
106
|
+
what happened instead of crashing the call.
|
|
107
|
+
|
|
108
|
+
Webhook management is deliberately not exposed: it is account administration,
|
|
109
|
+
not something to do mid-conversation. Use the REST API for that.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
cd mcp_server
|
|
115
|
+
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
116
|
+
pip install -e '.[test]'
|
|
117
|
+
pytest tests
|
|
118
|
+
VANOE_API_BASE_URL=http://localhost:8000 VANOE_API_KEY=sk_live_… vanoe-intelligence-mcp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Install it in its own virtualenv, separate from the API server's own
|
|
122
|
+
`requirements.txt`: the `mcp` SDK pulls in a newer `starlette` than the API's
|
|
123
|
+
FastAPI pin tolerates, and sharing one environment breaks the API server with
|
|
124
|
+
`Router.__init__() got an unexpected keyword argument 'on_startup'`. End users
|
|
125
|
+
never hit this (the MCP server runs on their machine, next to their assistant).
|
|
126
|
+
|
|
127
|
+
`tests/manual_e2e_smoke.py` runs the real server as a subprocess over stdio
|
|
128
|
+
against a reachable API with a real key; it is not part of `pytest`.
|
|
129
|
+
|
|
130
|
+
The MCP Python SDK is on v2: the server class is
|
|
131
|
+
`mcp.server.mcpserver.MCPServer` (older tutorials show `FastMCP` at a
|
|
132
|
+
different import path).
|
|
133
|
+
|
|
134
|
+
## Releasing
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
cd mcp_server
|
|
138
|
+
python -m build
|
|
139
|
+
twine upload dist/*
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Bump `version` in `pyproject.toml` first. The older `INTEL_API_KEY` /
|
|
143
|
+
`INTEL_API_BASE_URL` variable names are still accepted for compatibility.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Vanoe Market Intelligence MCP server
|
|
2
|
+
|
|
3
|
+
Exposes the [Vanoe Market Intelligence API](https://api.vanoe.ai) as tools any
|
|
4
|
+
MCP-capable AI assistant (Claude Desktop, Claude Code, Cursor, etc.) can call
|
|
5
|
+
directly: a composite 0–100 verdict per ticker, trend-stage analysis (current
|
|
6
|
+
and a backtest-safe weekly history), Point & Figure signals, sector breadth,
|
|
7
|
+
the macro risk regime, SEC insider and 8-K filings, the macro calendar, and
|
|
8
|
+
your usage.
|
|
9
|
+
|
|
10
|
+
This is a thin authenticated client, not a free tier: every tool call is a
|
|
11
|
+
real, metered call against your plan using your own API key, exactly like
|
|
12
|
+
calling the REST API yourself. Get a free key (1,000 credits a month, no card)
|
|
13
|
+
at https://api.vanoe.ai/signup.
|
|
14
|
+
|
|
15
|
+
> Informational data only. Not financial advice. See https://api.vanoe.ai/terms.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
No install step is needed with [uv](https://docs.astral.sh/uv/) — `uvx` fetches
|
|
20
|
+
and runs the package on demand:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
uvx vanoe-intelligence-mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or install it permanently with `pipx install vanoe-intelligence-mcp` or
|
|
27
|
+
`pip install vanoe-intelligence-mcp`, which puts a `vanoe-intelligence-mcp`
|
|
28
|
+
command on your PATH.
|
|
29
|
+
|
|
30
|
+
## Configure your MCP client
|
|
31
|
+
|
|
32
|
+
Set one environment variable, `VANOE_API_KEY`. `VANOE_API_BASE_URL` is optional
|
|
33
|
+
and defaults to `https://api.vanoe.ai`.
|
|
34
|
+
|
|
35
|
+
**Claude Desktop** (`claude_desktop_config.json`) and most other clients:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"vanoe": {
|
|
41
|
+
"command": "uvx",
|
|
42
|
+
"args": ["vanoe-intelligence-mcp"],
|
|
43
|
+
"env": { "VANOE_API_KEY": "sk_live_your_key_here" }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Claude Code**:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
claude mcp add vanoe -e VANOE_API_KEY=sk_live_your_key_here -- uvx vanoe-intelligence-mcp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then ask your assistant things like *"What's the verdict on NVDA and why?"*,
|
|
56
|
+
*"Which stage are AAPL, MSFT and AMD in?"*, or *"Is the macro regime risk-on
|
|
57
|
+
right now?"* — it will call the tools below.
|
|
58
|
+
|
|
59
|
+
## Tools
|
|
60
|
+
|
|
61
|
+
| Tool | API call | Credits |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `get_verdict(ticker)` | `GET /v1/verdict/{ticker}` | 5 |
|
|
64
|
+
| `get_stage(tickers)` | `GET /v1/signals/stage` | 1 per ticker |
|
|
65
|
+
| `get_stage_history(ticker, weeks)` | `GET /v1/signals/stage/history` | 5 |
|
|
66
|
+
| `get_pnf(tickers)` | `GET /v1/signals/pnf` | 1 per ticker |
|
|
67
|
+
| `get_sector_breadth()` | `GET /v1/sector-breadth` | 1 |
|
|
68
|
+
| `get_macro()` | `GET /v1/macro` | 1 |
|
|
69
|
+
| `get_filings(ticker)` | `GET /v1/filings/{ticker}` | 1 |
|
|
70
|
+
| `get_calendar()` | `GET /v1/calendar` | 1 |
|
|
71
|
+
| `get_usage()` | `GET /v1/usage` | 0 |
|
|
72
|
+
|
|
73
|
+
Tools never raise. Any failure — missing key, network error, quota exceeded,
|
|
74
|
+
an unknown ticker — comes back as `{"error": {"code", "message",
|
|
75
|
+
"http_status"}}` carrying the API's own message, so the assistant can explain
|
|
76
|
+
what happened instead of crashing the call.
|
|
77
|
+
|
|
78
|
+
Webhook management is deliberately not exposed: it is account administration,
|
|
79
|
+
not something to do mid-conversation. Use the REST API for that.
|
|
80
|
+
|
|
81
|
+
## Development
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
cd mcp_server
|
|
85
|
+
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
86
|
+
pip install -e '.[test]'
|
|
87
|
+
pytest tests
|
|
88
|
+
VANOE_API_BASE_URL=http://localhost:8000 VANOE_API_KEY=sk_live_… vanoe-intelligence-mcp
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Install it in its own virtualenv, separate from the API server's own
|
|
92
|
+
`requirements.txt`: the `mcp` SDK pulls in a newer `starlette` than the API's
|
|
93
|
+
FastAPI pin tolerates, and sharing one environment breaks the API server with
|
|
94
|
+
`Router.__init__() got an unexpected keyword argument 'on_startup'`. End users
|
|
95
|
+
never hit this (the MCP server runs on their machine, next to their assistant).
|
|
96
|
+
|
|
97
|
+
`tests/manual_e2e_smoke.py` runs the real server as a subprocess over stdio
|
|
98
|
+
against a reachable API with a real key; it is not part of `pytest`.
|
|
99
|
+
|
|
100
|
+
The MCP Python SDK is on v2: the server class is
|
|
101
|
+
`mcp.server.mcpserver.MCPServer` (older tutorials show `FastMCP` at a
|
|
102
|
+
different import path).
|
|
103
|
+
|
|
104
|
+
## Releasing
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
cd mcp_server
|
|
108
|
+
python -m build
|
|
109
|
+
twine upload dist/*
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Bump `version` in `pyproject.toml` first. The older `INTEL_API_KEY` /
|
|
113
|
+
`INTEL_API_BASE_URL` variable names are still accepted for compatibility.
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Thin HTTP client for the Vanoe Market Intelligence API — one function per
|
|
2
|
+
/v1 GET endpoint.
|
|
3
|
+
|
|
4
|
+
Auth: Authorization: Bearer <VANOE_API_KEY>. Base URL: VANOE_API_BASE_URL
|
|
5
|
+
(defaults to https://api.vanoe.ai; point it at a local instance for dev).
|
|
6
|
+
The older INTEL_API_KEY / INTEL_API_BASE_URL names are still honoured.
|
|
7
|
+
|
|
8
|
+
On any non-2xx response, raises IntelAPIError carrying the API's own
|
|
9
|
+
structured {"error": {code, message, request_id}} body when present, so a
|
|
10
|
+
tool caller sees the SAME message a developer would get calling the API
|
|
11
|
+
directly (e.g. "Monthly quota exceeded..."), not a raw HTTP status.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import httpx
|
|
18
|
+
|
|
19
|
+
DEFAULT_BASE_URL = "https://api.vanoe.ai"
|
|
20
|
+
SIGNUP_URL = "https://api.vanoe.ai/signup"
|
|
21
|
+
_TIMEOUT_SECONDS = 20
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IntelAPIError(Exception):
|
|
25
|
+
def __init__(self, status_code: int, code: str, message: str):
|
|
26
|
+
self.status_code = status_code
|
|
27
|
+
self.code = code
|
|
28
|
+
self.message = message
|
|
29
|
+
super().__init__(f"{message} (code={code}, http_status={status_code})")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MissingApiKeyError(Exception):
|
|
33
|
+
def __init__(self):
|
|
34
|
+
super().__init__(
|
|
35
|
+
f"VANOE_API_KEY is not set. Get a free key at {SIGNUP_URL} and set the "
|
|
36
|
+
"VANOE_API_KEY environment variable before starting this MCP server."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _env(new: str, old: str) -> str | None:
|
|
41
|
+
return os.environ.get(new) or os.environ.get(old)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _base_url() -> str:
|
|
45
|
+
return (_env("VANOE_API_BASE_URL", "INTEL_API_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _headers() -> dict:
|
|
49
|
+
key = _env("VANOE_API_KEY", "INTEL_API_KEY")
|
|
50
|
+
if not key:
|
|
51
|
+
raise MissingApiKeyError()
|
|
52
|
+
return {"Authorization": f"Bearer {key}"}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def get(path: str, params: dict | None = None) -> dict:
|
|
56
|
+
url = f"{_base_url()}{path}"
|
|
57
|
+
async with httpx.AsyncClient(timeout=_TIMEOUT_SECONDS) as http:
|
|
58
|
+
try:
|
|
59
|
+
r = await http.get(url, params=params, headers=_headers())
|
|
60
|
+
except httpx.RequestError as e:
|
|
61
|
+
raise IntelAPIError(0, "connection_error",
|
|
62
|
+
f"Could not reach the API at {_base_url()}: {e}") from e
|
|
63
|
+
|
|
64
|
+
if r.status_code >= 400:
|
|
65
|
+
try:
|
|
66
|
+
body = r.json()
|
|
67
|
+
err = body.get("error", {})
|
|
68
|
+
raise IntelAPIError(r.status_code, err.get("code", "unknown_error"),
|
|
69
|
+
err.get("message", r.text))
|
|
70
|
+
except ValueError:
|
|
71
|
+
raise IntelAPIError(r.status_code, "unknown_error", r.text)
|
|
72
|
+
return r.json()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vanoe-intelligence-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for the Vanoe Market Intelligence API: composite verdicts, trend-stage analysis, Point & Figure, sector breadth, macro regime and SEC filings as tools for Claude and other MCP clients."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [{ name = "VanoeAI Ltd", email = "support@vanoe.ai" }]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
keywords = ["mcp", "model-context-protocol", "finance", "stocks", "trading", "signals", "claude"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
21
|
+
]
|
|
22
|
+
dependencies = ["mcp>=2.0,<3.0", "httpx>=0.27"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://api.vanoe.ai"
|
|
26
|
+
Documentation = "https://api.vanoe.ai/docs"
|
|
27
|
+
"Get an API key" = "https://api.vanoe.ai/signup"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
vanoe-intelligence-mcp = "mcp_server.server:main"
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["setuptools>=68"]
|
|
34
|
+
build-backend = "setuptools.build_meta"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools]
|
|
37
|
+
packages = ["mcp_server"]
|
|
38
|
+
license-files = ["LICENSE"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-dir]
|
|
41
|
+
mcp_server = "."
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
test = ["pytest>=8.0", "pytest-asyncio>=0.24"]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Vanoe Market Intelligence MCP server — exposes the API's signals as MCP
|
|
2
|
+
tools so any MCP-capable AI assistant (Claude Desktop, Claude Code, etc.) can
|
|
3
|
+
call them directly.
|
|
4
|
+
|
|
5
|
+
This is a thin authenticated client, not a free bypass: every tool call
|
|
6
|
+
makes a real, metered call against your plan using your own VANOE_API_KEY,
|
|
7
|
+
exactly like calling the REST API yourself.
|
|
8
|
+
|
|
9
|
+
Setup:
|
|
10
|
+
VANOE_API_KEY your sk_live_... key (required) — free at https://api.vanoe.ai/signup
|
|
11
|
+
VANOE_API_BASE_URL defaults to https://api.vanoe.ai; point at a local
|
|
12
|
+
instance for development
|
|
13
|
+
|
|
14
|
+
Run: vanoe-intelligence-mcp (or: python -m mcp_server) — stdio transport,
|
|
15
|
+
for Claude Desktop / Claude Code style MCP client configs.
|
|
16
|
+
|
|
17
|
+
Tools never raise — every failure (missing key, network error, an API
|
|
18
|
+
4xx/5xx) is caught and returned as a {"error": {...}} JSON payload in the
|
|
19
|
+
tool result, not an exception. This is deliberate: a raised exception from a
|
|
20
|
+
tool crashes with UnexpectedToolError rather than being cleanly surfaced to
|
|
21
|
+
the caller (confirmed against the installed SDK version), so tools return a
|
|
22
|
+
predictable JSON shape either way instead of depending on that behavior.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
|
|
28
|
+
from mcp.server.mcpserver import MCPServer
|
|
29
|
+
|
|
30
|
+
from mcp_server import client
|
|
31
|
+
|
|
32
|
+
mcp = MCPServer(
|
|
33
|
+
name="vanoe-market-intelligence",
|
|
34
|
+
version="0.1.0",
|
|
35
|
+
instructions=(
|
|
36
|
+
"Vanoe Market Intelligence signals: four-stage trend analysis, Point & "
|
|
37
|
+
"Figure patterns, sector breadth, a composite verdict score, macro "
|
|
38
|
+
"regime, insider filings, and the economic calendar. All output is "
|
|
39
|
+
"informational data only — not financial advice, and not a "
|
|
40
|
+
"recommendation, solicitation, or offer to buy or sell any security."
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def _call(path: str, params: dict | None = None) -> str:
|
|
46
|
+
"""GET the API and return the JSON result as a string, or a
|
|
47
|
+
{"error": {...}} JSON string on any failure — tools never raise."""
|
|
48
|
+
try:
|
|
49
|
+
data = await client.get(path, params)
|
|
50
|
+
return json.dumps(data, indent=2)
|
|
51
|
+
except client.MissingApiKeyError as e:
|
|
52
|
+
return json.dumps({"error": {"code": "missing_api_key", "message": str(e)}}, indent=2)
|
|
53
|
+
except client.IntelAPIError as e:
|
|
54
|
+
return json.dumps(
|
|
55
|
+
{"error": {"code": e.code, "message": e.message, "http_status": e.status_code}},
|
|
56
|
+
indent=2,
|
|
57
|
+
)
|
|
58
|
+
except Exception as e: # last-resort guard — tools must never raise
|
|
59
|
+
return json.dumps({"error": {"code": "mcp_server_error", "message": str(e)}}, indent=2)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@mcp.tool(description="Macro regime (risk_on/neutral/risk_off), rates, inflation, yield curve, positioning.")
|
|
63
|
+
async def get_macro() -> str:
|
|
64
|
+
return await _call("/v1/macro")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@mcp.tool(description=(
|
|
68
|
+
"Stage analysis (1=Basing, 2=Advancing, 3=Topping, 4=Declining) "
|
|
69
|
+
"for up to 50 tickers. Pass tickers as a comma-separated string, e.g. "
|
|
70
|
+
"'AAPL,MSFT,NVDA'."
|
|
71
|
+
))
|
|
72
|
+
async def get_stage(tickers: str) -> str:
|
|
73
|
+
return await _call("/v1/signals/stage", {"tickers": tickers})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@mcp.tool(description=(
|
|
77
|
+
"Historical weekly trend-stage series for one ticker, walk-forward "
|
|
78
|
+
"computed (no lookahead) — the only commercial source of trend "
|
|
79
|
+
"stages as a backtest-safe data series. weeks defaults to 104, max 520."
|
|
80
|
+
))
|
|
81
|
+
async def get_stage_history(ticker: str, weeks: int = 104) -> str:
|
|
82
|
+
return await _call("/v1/signals/stage/history", {"ticker": ticker, "weeks": weeks})
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@mcp.tool(description=(
|
|
86
|
+
"Point & Figure signal, breakout pattern, and relative strength vs SPY, "
|
|
87
|
+
"for up to 50 tickers. Pass tickers as a comma-separated string."
|
|
88
|
+
))
|
|
89
|
+
async def get_pnf(tickers: str) -> str:
|
|
90
|
+
return await _call("/v1/signals/pnf", {"tickers": tickers})
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@mcp.tool(description="Bullish-percent breadth by sector — the % of names in each sector on a Point & Figure buy signal.")
|
|
94
|
+
async def get_sector_breadth() -> str:
|
|
95
|
+
return await _call("/v1/sector-breadth")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@mcp.tool(description=(
|
|
99
|
+
"Composite verdict for one ticker: fuses trend stage, Point & "
|
|
100
|
+
"Figure, insider-transaction clusters, and macro regime into a 0-100 "
|
|
101
|
+
"score, an accumulate/hold/distribute/avoid enum, and a plain-English "
|
|
102
|
+
"rationale. One call replaces separately calling stage, pnf, filings, "
|
|
103
|
+
"and macro."
|
|
104
|
+
))
|
|
105
|
+
async def get_verdict(ticker: str) -> str:
|
|
106
|
+
return await _call(f"/v1/verdict/{ticker}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@mcp.tool(description="Recent SEC insider transactions (Form 4) and 8-K filings for one ticker.")
|
|
110
|
+
async def get_filings(ticker: str) -> str:
|
|
111
|
+
return await _call(f"/v1/filings/{ticker}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@mcp.tool(description="Upcoming macro events (FOMC, CPI, payrolls) within a look-ahead window in days (default 10, max 60).")
|
|
115
|
+
async def get_calendar(within_days: int = 10) -> str:
|
|
116
|
+
return await _call("/v1/calendar", {"within_days": within_days})
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@mcp.tool(description="Your current billing-period usage and remaining credit quota on your plan. Free — never counts against quota.")
|
|
120
|
+
async def get_usage() -> str:
|
|
121
|
+
return await _call("/v1/usage")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main() -> None:
|
|
125
|
+
mcp.run()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
main()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""client.get() tests — httpx is monkeypatched, no network touched."""
|
|
2
|
+
import httpx
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from mcp_server import client
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture(autouse=True)
|
|
9
|
+
def _api_key(monkeypatch):
|
|
10
|
+
monkeypatch.setenv("VANOE_API_KEY", "sk_live_test")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_missing_api_key_raises(monkeypatch):
|
|
14
|
+
monkeypatch.delenv("VANOE_API_KEY", raising=False)
|
|
15
|
+
monkeypatch.delenv("INTEL_API_KEY", raising=False)
|
|
16
|
+
with pytest.raises(client.MissingApiKeyError):
|
|
17
|
+
import asyncio
|
|
18
|
+
asyncio.run(client.get("/v1/macro"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_get_success_returns_json(monkeypatch):
|
|
22
|
+
async def fake_get(self, url, params=None, headers=None):
|
|
23
|
+
assert headers["Authorization"] == "Bearer sk_live_test"
|
|
24
|
+
assert url == "https://api.vanoe.ai/v1/macro"
|
|
25
|
+
return httpx.Response(200, json={"data": {"regime": "risk_on"}}, request=httpx.Request("GET", url))
|
|
26
|
+
|
|
27
|
+
monkeypatch.setattr(httpx.AsyncClient, "get", fake_get)
|
|
28
|
+
import asyncio
|
|
29
|
+
result = asyncio.run(client.get("/v1/macro"))
|
|
30
|
+
assert result == {"data": {"regime": "risk_on"}}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_get_uses_custom_base_url(monkeypatch):
|
|
34
|
+
monkeypatch.setenv("VANOE_API_BASE_URL", "https://api.example.com/")
|
|
35
|
+
|
|
36
|
+
async def fake_get(self, url, params=None, headers=None):
|
|
37
|
+
assert url == "https://api.example.com/v1/usage"
|
|
38
|
+
return httpx.Response(200, json={}, request=httpx.Request("GET", url))
|
|
39
|
+
|
|
40
|
+
monkeypatch.setattr(httpx.AsyncClient, "get", fake_get)
|
|
41
|
+
import asyncio
|
|
42
|
+
asyncio.run(client.get("/v1/usage"))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_error_response_raises_intel_api_error_with_structured_body(monkeypatch):
|
|
46
|
+
async def fake_get(self, url, params=None, headers=None):
|
|
47
|
+
return httpx.Response(
|
|
48
|
+
402,
|
|
49
|
+
json={"error": {"code": "quota_exceeded", "message": "Monthly quota exceeded.", "request_id": "abc"}},
|
|
50
|
+
request=httpx.Request("GET", url),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
monkeypatch.setattr(httpx.AsyncClient, "get", fake_get)
|
|
54
|
+
import asyncio
|
|
55
|
+
with pytest.raises(client.IntelAPIError) as exc_info:
|
|
56
|
+
asyncio.run(client.get("/v1/macro"))
|
|
57
|
+
assert exc_info.value.status_code == 402
|
|
58
|
+
assert exc_info.value.code == "quota_exceeded"
|
|
59
|
+
assert exc_info.value.message == "Monthly quota exceeded."
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_error_response_with_unparseable_body_still_raises(monkeypatch):
|
|
63
|
+
async def fake_get(self, url, params=None, headers=None):
|
|
64
|
+
return httpx.Response(500, content=b"not json", request=httpx.Request("GET", url))
|
|
65
|
+
|
|
66
|
+
monkeypatch.setattr(httpx.AsyncClient, "get", fake_get)
|
|
67
|
+
import asyncio
|
|
68
|
+
with pytest.raises(client.IntelAPIError) as exc_info:
|
|
69
|
+
asyncio.run(client.get("/v1/macro"))
|
|
70
|
+
assert exc_info.value.status_code == 500
|
|
71
|
+
assert exc_info.value.code == "unknown_error"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_connection_error_raises_intel_api_error(monkeypatch):
|
|
75
|
+
async def fake_get(self, url, params=None, headers=None):
|
|
76
|
+
raise httpx.ConnectError("connection refused")
|
|
77
|
+
|
|
78
|
+
monkeypatch.setattr(httpx.AsyncClient, "get", fake_get)
|
|
79
|
+
import asyncio
|
|
80
|
+
with pytest.raises(client.IntelAPIError) as exc_info:
|
|
81
|
+
asyncio.run(client.get("/v1/macro"))
|
|
82
|
+
assert exc_info.value.code == "connection_error"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_legacy_env_names_still_work(monkeypatch):
|
|
86
|
+
monkeypatch.delenv("VANOE_API_KEY", raising=False)
|
|
87
|
+
monkeypatch.setenv("INTEL_API_KEY", "sk_live_legacy")
|
|
88
|
+
monkeypatch.setenv("INTEL_API_BASE_URL", "http://localhost:8000")
|
|
89
|
+
|
|
90
|
+
async def fake_get(self, url, params=None, headers=None):
|
|
91
|
+
assert headers["Authorization"] == "Bearer sk_live_legacy"
|
|
92
|
+
assert url == "http://localhost:8000/v1/usage"
|
|
93
|
+
return httpx.Response(200, json={"ok": True}, request=httpx.Request("GET", url))
|
|
94
|
+
|
|
95
|
+
monkeypatch.setattr(httpx.AsyncClient, "get", fake_get)
|
|
96
|
+
import asyncio
|
|
97
|
+
assert asyncio.run(client.get("/v1/usage")) == {"ok": True}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""MCP tool tests — call tools in-process via mcp.call_tool(), with
|
|
2
|
+
mcp_server.client.get monkeypatched (no network, no real MCP transport).
|
|
3
|
+
Tools must never raise (see server.py's docstring) — every failure path
|
|
4
|
+
asserts a clean {"error": {...}} JSON text result instead of an exception.
|
|
5
|
+
"""
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from mcp_server import client, server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _call(name: str, args: dict) -> dict:
|
|
15
|
+
result = asyncio.run(server.mcp.call_tool(name, args))
|
|
16
|
+
assert result.is_error is False # tools never raise -> is_error is always False
|
|
17
|
+
return json.loads(result.content[0].text)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_get_macro_passes_through_path(monkeypatch):
|
|
21
|
+
async def fake_get(path, params=None):
|
|
22
|
+
assert path == "/v1/macro"
|
|
23
|
+
assert params is None
|
|
24
|
+
return {"data": {"regime": "risk_on"}}
|
|
25
|
+
|
|
26
|
+
monkeypatch.setattr(client, "get", fake_get)
|
|
27
|
+
assert _call("get_macro", {}) == {"data": {"regime": "risk_on"}}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_get_stage_passes_tickers_param(monkeypatch):
|
|
31
|
+
async def fake_get(path, params=None):
|
|
32
|
+
assert path == "/v1/signals/stage"
|
|
33
|
+
assert params == {"tickers": "AAPL,MSFT"}
|
|
34
|
+
return {"data": {"AAPL": {"stage": 2}}}
|
|
35
|
+
|
|
36
|
+
monkeypatch.setattr(client, "get", fake_get)
|
|
37
|
+
result = _call("get_stage", {"tickers": "AAPL,MSFT"})
|
|
38
|
+
assert result["data"]["AAPL"]["stage"] == 2
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_get_stage_history_default_weeks(monkeypatch):
|
|
42
|
+
async def fake_get(path, params=None):
|
|
43
|
+
assert path == "/v1/signals/stage/history"
|
|
44
|
+
assert params == {"ticker": "AAPL", "weeks": 104}
|
|
45
|
+
return {"data": {}}
|
|
46
|
+
|
|
47
|
+
monkeypatch.setattr(client, "get", fake_get)
|
|
48
|
+
_call("get_stage_history", {"ticker": "AAPL"})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_get_verdict_builds_path_with_ticker(monkeypatch):
|
|
52
|
+
async def fake_get(path, params=None):
|
|
53
|
+
assert path == "/v1/verdict/AAPL"
|
|
54
|
+
return {"data": {"verdict": "accumulate"}}
|
|
55
|
+
|
|
56
|
+
monkeypatch.setattr(client, "get", fake_get)
|
|
57
|
+
assert _call("get_verdict", {"ticker": "AAPL"})["data"]["verdict"] == "accumulate"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_get_calendar_default_within_days(monkeypatch):
|
|
61
|
+
async def fake_get(path, params=None):
|
|
62
|
+
assert params == {"within_days": 10}
|
|
63
|
+
return {"data": []}
|
|
64
|
+
|
|
65
|
+
monkeypatch.setattr(client, "get", fake_get)
|
|
66
|
+
_call("get_calendar", {})
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_missing_api_key_returns_error_json_not_exception(monkeypatch):
|
|
70
|
+
async def fake_get(path, params=None):
|
|
71
|
+
raise client.MissingApiKeyError()
|
|
72
|
+
|
|
73
|
+
monkeypatch.setattr(client, "get", fake_get)
|
|
74
|
+
result = _call("get_usage", {})
|
|
75
|
+
assert result["error"]["code"] == "missing_api_key"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_intel_api_error_returns_structured_error_json(monkeypatch):
|
|
79
|
+
async def fake_get(path, params=None):
|
|
80
|
+
raise client.IntelAPIError(402, "quota_exceeded", "Monthly quota exceeded.")
|
|
81
|
+
|
|
82
|
+
monkeypatch.setattr(client, "get", fake_get)
|
|
83
|
+
result = _call("get_macro", {})
|
|
84
|
+
assert result["error"] == {
|
|
85
|
+
"code": "quota_exceeded",
|
|
86
|
+
"message": "Monthly quota exceeded.",
|
|
87
|
+
"http_status": 402,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_unexpected_exception_still_returns_error_json_not_raise(monkeypatch):
|
|
92
|
+
async def fake_get(path, params=None):
|
|
93
|
+
raise ValueError("something unrelated broke")
|
|
94
|
+
|
|
95
|
+
monkeypatch.setattr(client, "get", fake_get)
|
|
96
|
+
result = _call("get_macro", {})
|
|
97
|
+
assert result["error"]["code"] == "mcp_server_error"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_all_nine_tools_are_registered():
|
|
101
|
+
expected = {
|
|
102
|
+
"get_macro", "get_stage", "get_stage_history", "get_pnf",
|
|
103
|
+
"get_sector_breadth", "get_verdict", "get_filings", "get_calendar",
|
|
104
|
+
"get_usage",
|
|
105
|
+
}
|
|
106
|
+
tools = asyncio.run(server.mcp.list_tools())
|
|
107
|
+
names = {t.name for t in tools}
|
|
108
|
+
assert expected <= names
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vanoe-intelligence-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for the Vanoe Market Intelligence API: composite verdicts, trend-stage analysis, Point & Figure, sector breadth, macro regime and SEC filings as tools for Claude and other MCP clients.
|
|
5
|
+
Author-email: VanoeAI Ltd <support@vanoe.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://api.vanoe.ai
|
|
8
|
+
Project-URL: Documentation, https://api.vanoe.ai/docs
|
|
9
|
+
Project-URL: Get an API key, https://api.vanoe.ai/signup
|
|
10
|
+
Keywords: mcp,model-context-protocol,finance,stocks,trading,signals,claude
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: mcp<3.0,>=2.0
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# Vanoe Market Intelligence MCP server
|
|
32
|
+
|
|
33
|
+
Exposes the [Vanoe Market Intelligence API](https://api.vanoe.ai) as tools any
|
|
34
|
+
MCP-capable AI assistant (Claude Desktop, Claude Code, Cursor, etc.) can call
|
|
35
|
+
directly: a composite 0–100 verdict per ticker, trend-stage analysis (current
|
|
36
|
+
and a backtest-safe weekly history), Point & Figure signals, sector breadth,
|
|
37
|
+
the macro risk regime, SEC insider and 8-K filings, the macro calendar, and
|
|
38
|
+
your usage.
|
|
39
|
+
|
|
40
|
+
This is a thin authenticated client, not a free tier: every tool call is a
|
|
41
|
+
real, metered call against your plan using your own API key, exactly like
|
|
42
|
+
calling the REST API yourself. Get a free key (1,000 credits a month, no card)
|
|
43
|
+
at https://api.vanoe.ai/signup.
|
|
44
|
+
|
|
45
|
+
> Informational data only. Not financial advice. See https://api.vanoe.ai/terms.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
No install step is needed with [uv](https://docs.astral.sh/uv/) — `uvx` fetches
|
|
50
|
+
and runs the package on demand:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
uvx vanoe-intelligence-mcp
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or install it permanently with `pipx install vanoe-intelligence-mcp` or
|
|
57
|
+
`pip install vanoe-intelligence-mcp`, which puts a `vanoe-intelligence-mcp`
|
|
58
|
+
command on your PATH.
|
|
59
|
+
|
|
60
|
+
## Configure your MCP client
|
|
61
|
+
|
|
62
|
+
Set one environment variable, `VANOE_API_KEY`. `VANOE_API_BASE_URL` is optional
|
|
63
|
+
and defaults to `https://api.vanoe.ai`.
|
|
64
|
+
|
|
65
|
+
**Claude Desktop** (`claude_desktop_config.json`) and most other clients:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"vanoe": {
|
|
71
|
+
"command": "uvx",
|
|
72
|
+
"args": ["vanoe-intelligence-mcp"],
|
|
73
|
+
"env": { "VANOE_API_KEY": "sk_live_your_key_here" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Claude Code**:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
claude mcp add vanoe -e VANOE_API_KEY=sk_live_your_key_here -- uvx vanoe-intelligence-mcp
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then ask your assistant things like *"What's the verdict on NVDA and why?"*,
|
|
86
|
+
*"Which stage are AAPL, MSFT and AMD in?"*, or *"Is the macro regime risk-on
|
|
87
|
+
right now?"* — it will call the tools below.
|
|
88
|
+
|
|
89
|
+
## Tools
|
|
90
|
+
|
|
91
|
+
| Tool | API call | Credits |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `get_verdict(ticker)` | `GET /v1/verdict/{ticker}` | 5 |
|
|
94
|
+
| `get_stage(tickers)` | `GET /v1/signals/stage` | 1 per ticker |
|
|
95
|
+
| `get_stage_history(ticker, weeks)` | `GET /v1/signals/stage/history` | 5 |
|
|
96
|
+
| `get_pnf(tickers)` | `GET /v1/signals/pnf` | 1 per ticker |
|
|
97
|
+
| `get_sector_breadth()` | `GET /v1/sector-breadth` | 1 |
|
|
98
|
+
| `get_macro()` | `GET /v1/macro` | 1 |
|
|
99
|
+
| `get_filings(ticker)` | `GET /v1/filings/{ticker}` | 1 |
|
|
100
|
+
| `get_calendar()` | `GET /v1/calendar` | 1 |
|
|
101
|
+
| `get_usage()` | `GET /v1/usage` | 0 |
|
|
102
|
+
|
|
103
|
+
Tools never raise. Any failure — missing key, network error, quota exceeded,
|
|
104
|
+
an unknown ticker — comes back as `{"error": {"code", "message",
|
|
105
|
+
"http_status"}}` carrying the API's own message, so the assistant can explain
|
|
106
|
+
what happened instead of crashing the call.
|
|
107
|
+
|
|
108
|
+
Webhook management is deliberately not exposed: it is account administration,
|
|
109
|
+
not something to do mid-conversation. Use the REST API for that.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
cd mcp_server
|
|
115
|
+
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
116
|
+
pip install -e '.[test]'
|
|
117
|
+
pytest tests
|
|
118
|
+
VANOE_API_BASE_URL=http://localhost:8000 VANOE_API_KEY=sk_live_… vanoe-intelligence-mcp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Install it in its own virtualenv, separate from the API server's own
|
|
122
|
+
`requirements.txt`: the `mcp` SDK pulls in a newer `starlette` than the API's
|
|
123
|
+
FastAPI pin tolerates, and sharing one environment breaks the API server with
|
|
124
|
+
`Router.__init__() got an unexpected keyword argument 'on_startup'`. End users
|
|
125
|
+
never hit this (the MCP server runs on their machine, next to their assistant).
|
|
126
|
+
|
|
127
|
+
`tests/manual_e2e_smoke.py` runs the real server as a subprocess over stdio
|
|
128
|
+
against a reachable API with a real key; it is not part of `pytest`.
|
|
129
|
+
|
|
130
|
+
The MCP Python SDK is on v2: the server class is
|
|
131
|
+
`mcp.server.mcpserver.MCPServer` (older tutorials show `FastMCP` at a
|
|
132
|
+
different import path).
|
|
133
|
+
|
|
134
|
+
## Releasing
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
cd mcp_server
|
|
138
|
+
python -m build
|
|
139
|
+
twine upload dist/*
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Bump `version` in `pyproject.toml` first. The older `INTEL_API_KEY` /
|
|
143
|
+
`INTEL_API_BASE_URL` variable names are still accepted for compatibility.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
__init__.py
|
|
4
|
+
__main__.py
|
|
5
|
+
client.py
|
|
6
|
+
pyproject.toml
|
|
7
|
+
server.py
|
|
8
|
+
./__init__.py
|
|
9
|
+
./__main__.py
|
|
10
|
+
./client.py
|
|
11
|
+
./server.py
|
|
12
|
+
tests/test_client.py
|
|
13
|
+
tests/test_server.py
|
|
14
|
+
vanoe_intelligence_mcp.egg-info/PKG-INFO
|
|
15
|
+
vanoe_intelligence_mcp.egg-info/SOURCES.txt
|
|
16
|
+
vanoe_intelligence_mcp.egg-info/dependency_links.txt
|
|
17
|
+
vanoe_intelligence_mcp.egg-info/entry_points.txt
|
|
18
|
+
vanoe_intelligence_mcp.egg-info/requires.txt
|
|
19
|
+
vanoe_intelligence_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcp_server
|