the13f-mcp 0.1.1__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.
- the13f_mcp-0.1.1/PKG-INFO +165 -0
- the13f_mcp-0.1.1/README.md +135 -0
- the13f_mcp-0.1.1/pyproject.toml +53 -0
- the13f_mcp-0.1.1/setup.cfg +4 -0
- the13f_mcp-0.1.1/src/the13f_mcp/__init__.py +24 -0
- the13f_mcp-0.1.1/src/the13f_mcp/_version.py +2 -0
- the13f_mcp-0.1.1/src/the13f_mcp/config.py +39 -0
- the13f_mcp-0.1.1/src/the13f_mcp/disclaimer.py +31 -0
- the13f_mcp-0.1.1/src/the13f_mcp/http_client.py +119 -0
- the13f_mcp-0.1.1/src/the13f_mcp/prompts.py +292 -0
- the13f_mcp-0.1.1/src/the13f_mcp/resources.py +229 -0
- the13f_mcp-0.1.1/src/the13f_mcp/server.py +324 -0
- the13f_mcp-0.1.1/src/the13f_mcp/tools/__init__.py +30 -0
- the13f_mcp-0.1.1/src/the13f_mcp/tools/read_tools.py +188 -0
- the13f_mcp-0.1.1/src/the13f_mcp.egg-info/PKG-INFO +165 -0
- the13f_mcp-0.1.1/src/the13f_mcp.egg-info/SOURCES.txt +21 -0
- the13f_mcp-0.1.1/src/the13f_mcp.egg-info/dependency_links.txt +1 -0
- the13f_mcp-0.1.1/src/the13f_mcp.egg-info/entry_points.txt +2 -0
- the13f_mcp-0.1.1/src/the13f_mcp.egg-info/requires.txt +9 -0
- the13f_mcp-0.1.1/src/the13f_mcp.egg-info/top_level.txt +1 -0
- the13f_mcp-0.1.1/tests/test_resources_prompts.py +204 -0
- the13f_mcp-0.1.1/tests/test_smoke_against_flask.py +298 -0
- the13f_mcp-0.1.1/tests/test_tools.py +105 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: the13f-mcp
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Model Context Protocol server for the13f - institutional 13F intelligence in Claude Desktop, Cursor, and VS Code.
|
|
5
|
+
Author-email: Pickel Fintech <tom@pickelfintech.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://the13f.com/developers/
|
|
8
|
+
Project-URL: Documentation, https://the13f.com/developers/mcp.html
|
|
9
|
+
Project-URL: Signup, https://the13f.com/developers/
|
|
10
|
+
Project-URL: Source, https://gitlab.com/pickel-fintech/The13F
|
|
11
|
+
Project-URL: Changelog, https://gitlab.com/pickel-fintech/The13F/-/blob/main/mcp_server/CHANGELOG.md
|
|
12
|
+
Project-URL: Issues, https://gitlab.com/pickel-fintech/The13F/-/issues
|
|
13
|
+
Keywords: 13f,sec,institutional,investment,mcp,claude,cursor,anthropic
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: mcp>=1.0
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: pydantic>=2.6
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
28
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
30
|
+
|
|
31
|
+
# the13f-mcp
|
|
32
|
+
|
|
33
|
+
> Model Context Protocol server for [the13f](https://the13f.com). Brings
|
|
34
|
+
> institutional 13F intelligence into Claude Desktop, Cursor, VS Code +
|
|
35
|
+
> Continue, and any MCP-compatible host — no HTTP code required on your side.
|
|
36
|
+
|
|
37
|
+
## Important limitations
|
|
38
|
+
|
|
39
|
+
Research data only. Sourced from publicly disclosed SEC Form 13F filings,
|
|
40
|
+
which lag quarter-end by up to 45 days. Past institutional positioning does
|
|
41
|
+
not predict future performance. Nothing here constitutes investment advice.
|
|
42
|
+
Every tool's response includes a `disclaimer` field; the host LLM should
|
|
43
|
+
surface it alongside any analysis it produces.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
uvx the13f-mcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or `pip install the13f-mcp` if you prefer to manage Python environments
|
|
52
|
+
yourself. Python 3.11+ required.
|
|
53
|
+
|
|
54
|
+
## Get a free API key
|
|
55
|
+
|
|
56
|
+
Free, no payment, no credit card:
|
|
57
|
+
|
|
58
|
+
1. Visit <https://the13f.com/developers/>
|
|
59
|
+
2. Enter your email and solve the Cloudflare Turnstile challenge
|
|
60
|
+
3. Copy your `pf13f_` key — **shown once**
|
|
61
|
+
4. Paste it into your MCP host's config as `THE13F_API_KEY` (see below)
|
|
62
|
+
5. Restart the host
|
|
63
|
+
|
|
64
|
+
Free tier: 100 read calls per day; quota rolls at UTC midnight. Signal and
|
|
65
|
+
report tools arrive in later minor versions when paid tiers launch.
|
|
66
|
+
|
|
67
|
+
## Configure your MCP host
|
|
68
|
+
|
|
69
|
+
### Claude Desktop
|
|
70
|
+
|
|
71
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
72
|
+
(macOS) or `%APPDATA%/Claude/claude_desktop_config.json` (Windows):
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"the13f": {
|
|
78
|
+
"command": "uvx",
|
|
79
|
+
"args": ["the13f-mcp"],
|
|
80
|
+
"env": {
|
|
81
|
+
"THE13F_API_KEY": "pf13f_..."
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Restart Claude Desktop. The13f tools should appear in the MCP tool list.
|
|
89
|
+
|
|
90
|
+
### Cursor
|
|
91
|
+
|
|
92
|
+
Settings → MCP → Add Server. Paste the same JSON block into the "Custom"
|
|
93
|
+
field.
|
|
94
|
+
|
|
95
|
+
### VS Code with Continue
|
|
96
|
+
|
|
97
|
+
Settings → `experimental.modelContextProtocolServers` → add a `stdio`
|
|
98
|
+
transport running `uvx the13f-mcp` with `THE13F_API_KEY` in env. The key
|
|
99
|
+
reveal page at <https://the13f.com/developers/signup-success> shows a
|
|
100
|
+
copy-paste-ready snippet with the key already filled in.
|
|
101
|
+
|
|
102
|
+
## Available tools — v0.1.0 (Read tier, free)
|
|
103
|
+
|
|
104
|
+
| Tool | What it does |
|
|
105
|
+
|------|---------------|
|
|
106
|
+
| `list_quarters` | All quarters with holdings data + the latest quarter |
|
|
107
|
+
| `search_managers` | Autocomplete 13F filers by name |
|
|
108
|
+
| `get_manager_holdings` | Full positions for a CIK + quarter |
|
|
109
|
+
| `get_manager_holdings_bulk` | Up to 25 `(cik, quarter)` pairs in one call |
|
|
110
|
+
| `list_all_managers` | Universe of 8,600+ filers with per-manager summary stats |
|
|
111
|
+
| `find_similar_managers` | Match a portfolio against the universe |
|
|
112
|
+
| `get_consensus_portfolio` | Most-widely-held securities per quarter |
|
|
113
|
+
| `get_market_regime` | Institutional regime snapshot (IIOI composite, state, transition) |
|
|
114
|
+
| `get_sector_flows` | Per-sector capital flows and risk posture |
|
|
115
|
+
|
|
116
|
+
## Planned tools — later versions
|
|
117
|
+
|
|
118
|
+
| Version | Tools | Requires |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| v0.2.0 | `get_security_signals`, `get_security_signals_bulk`, `get_portfolio_signals` | Standard tier subscription |
|
|
121
|
+
| v0.3.0 | `generate_manager_report`, `generate_security_report`, `generate_sector_report`, `check_report_status`, `wait_and_download_report` | Standard tier + stored card; two-step confirm |
|
|
122
|
+
|
|
123
|
+
The free-tier Read tools are enough to explore the data and build prompts
|
|
124
|
+
against it. Signal and report tools deliberately wait on the paid-tier
|
|
125
|
+
billing plumbing.
|
|
126
|
+
|
|
127
|
+
## Example prompts
|
|
128
|
+
|
|
129
|
+
> "Pull Berkshire Hathaway's top 20 positions as of Q4 2025 and summarize
|
|
130
|
+
> what grew the most quarter-over-quarter."
|
|
131
|
+
|
|
132
|
+
> "I own AAPL, MSFT, and NVDA equal-weight. Find the 5 institutional
|
|
133
|
+
> managers whose portfolios most closely resemble mine."
|
|
134
|
+
|
|
135
|
+
> "What sector did 13F filers most aggressively reduce last quarter? Show
|
|
136
|
+
> me the top three managers leading the reduction."
|
|
137
|
+
|
|
138
|
+
The MCP host's LLM picks the right tool, fills in arguments, surfaces the
|
|
139
|
+
`disclaimer`, and returns the result as structured JSON.
|
|
140
|
+
|
|
141
|
+
## Environment variables
|
|
142
|
+
|
|
143
|
+
| Var | Default | What it controls |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `THE13F_API_KEY` | (none) | Your `pf13f_` key. Without it, tools return a structured "free signup required" response with a link to the signup page. |
|
|
146
|
+
| `THE13F_API_BASE_URL` | `https://api.the13f.com` | Override for local development against a running copy of the13f's gui_server. |
|
|
147
|
+
| `THE13F_MCP_TIMEOUT` | `30` | Per-request HTTP timeout in seconds. Minimum 5. |
|
|
148
|
+
|
|
149
|
+
## Source
|
|
150
|
+
|
|
151
|
+
Published from this directory of <https://gitlab.com/pickel-fintech/The13F>.
|
|
152
|
+
Tag pattern `sdk-mcp-vX.Y.Z` triggers a PyPI release via GitLab CI; see
|
|
153
|
+
`docs/mcp_launch.plan.md` in the repo root for the extraction story and the
|
|
154
|
+
release runbook.
|
|
155
|
+
|
|
156
|
+
## Support
|
|
157
|
+
|
|
158
|
+
tom@pickelfintech.com — quote the `request_id` field returned by any failing
|
|
159
|
+
tool call for fastest triage.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT. See [LICENSE](../LICENSE). The MCP server source is MIT-licensed; use
|
|
164
|
+
of the the13f API itself is governed by the Terms of Service at
|
|
165
|
+
<https://the13f.com/terms.html>.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# the13f-mcp
|
|
2
|
+
|
|
3
|
+
> Model Context Protocol server for [the13f](https://the13f.com). Brings
|
|
4
|
+
> institutional 13F intelligence into Claude Desktop, Cursor, VS Code +
|
|
5
|
+
> Continue, and any MCP-compatible host — no HTTP code required on your side.
|
|
6
|
+
|
|
7
|
+
## Important limitations
|
|
8
|
+
|
|
9
|
+
Research data only. Sourced from publicly disclosed SEC Form 13F filings,
|
|
10
|
+
which lag quarter-end by up to 45 days. Past institutional positioning does
|
|
11
|
+
not predict future performance. Nothing here constitutes investment advice.
|
|
12
|
+
Every tool's response includes a `disclaimer` field; the host LLM should
|
|
13
|
+
surface it alongside any analysis it produces.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
uvx the13f-mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or `pip install the13f-mcp` if you prefer to manage Python environments
|
|
22
|
+
yourself. Python 3.11+ required.
|
|
23
|
+
|
|
24
|
+
## Get a free API key
|
|
25
|
+
|
|
26
|
+
Free, no payment, no credit card:
|
|
27
|
+
|
|
28
|
+
1. Visit <https://the13f.com/developers/>
|
|
29
|
+
2. Enter your email and solve the Cloudflare Turnstile challenge
|
|
30
|
+
3. Copy your `pf13f_` key — **shown once**
|
|
31
|
+
4. Paste it into your MCP host's config as `THE13F_API_KEY` (see below)
|
|
32
|
+
5. Restart the host
|
|
33
|
+
|
|
34
|
+
Free tier: 100 read calls per day; quota rolls at UTC midnight. Signal and
|
|
35
|
+
report tools arrive in later minor versions when paid tiers launch.
|
|
36
|
+
|
|
37
|
+
## Configure your MCP host
|
|
38
|
+
|
|
39
|
+
### Claude Desktop
|
|
40
|
+
|
|
41
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
42
|
+
(macOS) or `%APPDATA%/Claude/claude_desktop_config.json` (Windows):
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"the13f": {
|
|
48
|
+
"command": "uvx",
|
|
49
|
+
"args": ["the13f-mcp"],
|
|
50
|
+
"env": {
|
|
51
|
+
"THE13F_API_KEY": "pf13f_..."
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Restart Claude Desktop. The13f tools should appear in the MCP tool list.
|
|
59
|
+
|
|
60
|
+
### Cursor
|
|
61
|
+
|
|
62
|
+
Settings → MCP → Add Server. Paste the same JSON block into the "Custom"
|
|
63
|
+
field.
|
|
64
|
+
|
|
65
|
+
### VS Code with Continue
|
|
66
|
+
|
|
67
|
+
Settings → `experimental.modelContextProtocolServers` → add a `stdio`
|
|
68
|
+
transport running `uvx the13f-mcp` with `THE13F_API_KEY` in env. The key
|
|
69
|
+
reveal page at <https://the13f.com/developers/signup-success> shows a
|
|
70
|
+
copy-paste-ready snippet with the key already filled in.
|
|
71
|
+
|
|
72
|
+
## Available tools — v0.1.0 (Read tier, free)
|
|
73
|
+
|
|
74
|
+
| Tool | What it does |
|
|
75
|
+
|------|---------------|
|
|
76
|
+
| `list_quarters` | All quarters with holdings data + the latest quarter |
|
|
77
|
+
| `search_managers` | Autocomplete 13F filers by name |
|
|
78
|
+
| `get_manager_holdings` | Full positions for a CIK + quarter |
|
|
79
|
+
| `get_manager_holdings_bulk` | Up to 25 `(cik, quarter)` pairs in one call |
|
|
80
|
+
| `list_all_managers` | Universe of 8,600+ filers with per-manager summary stats |
|
|
81
|
+
| `find_similar_managers` | Match a portfolio against the universe |
|
|
82
|
+
| `get_consensus_portfolio` | Most-widely-held securities per quarter |
|
|
83
|
+
| `get_market_regime` | Institutional regime snapshot (IIOI composite, state, transition) |
|
|
84
|
+
| `get_sector_flows` | Per-sector capital flows and risk posture |
|
|
85
|
+
|
|
86
|
+
## Planned tools — later versions
|
|
87
|
+
|
|
88
|
+
| Version | Tools | Requires |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| v0.2.0 | `get_security_signals`, `get_security_signals_bulk`, `get_portfolio_signals` | Standard tier subscription |
|
|
91
|
+
| v0.3.0 | `generate_manager_report`, `generate_security_report`, `generate_sector_report`, `check_report_status`, `wait_and_download_report` | Standard tier + stored card; two-step confirm |
|
|
92
|
+
|
|
93
|
+
The free-tier Read tools are enough to explore the data and build prompts
|
|
94
|
+
against it. Signal and report tools deliberately wait on the paid-tier
|
|
95
|
+
billing plumbing.
|
|
96
|
+
|
|
97
|
+
## Example prompts
|
|
98
|
+
|
|
99
|
+
> "Pull Berkshire Hathaway's top 20 positions as of Q4 2025 and summarize
|
|
100
|
+
> what grew the most quarter-over-quarter."
|
|
101
|
+
|
|
102
|
+
> "I own AAPL, MSFT, and NVDA equal-weight. Find the 5 institutional
|
|
103
|
+
> managers whose portfolios most closely resemble mine."
|
|
104
|
+
|
|
105
|
+
> "What sector did 13F filers most aggressively reduce last quarter? Show
|
|
106
|
+
> me the top three managers leading the reduction."
|
|
107
|
+
|
|
108
|
+
The MCP host's LLM picks the right tool, fills in arguments, surfaces the
|
|
109
|
+
`disclaimer`, and returns the result as structured JSON.
|
|
110
|
+
|
|
111
|
+
## Environment variables
|
|
112
|
+
|
|
113
|
+
| Var | Default | What it controls |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `THE13F_API_KEY` | (none) | Your `pf13f_` key. Without it, tools return a structured "free signup required" response with a link to the signup page. |
|
|
116
|
+
| `THE13F_API_BASE_URL` | `https://api.the13f.com` | Override for local development against a running copy of the13f's gui_server. |
|
|
117
|
+
| `THE13F_MCP_TIMEOUT` | `30` | Per-request HTTP timeout in seconds. Minimum 5. |
|
|
118
|
+
|
|
119
|
+
## Source
|
|
120
|
+
|
|
121
|
+
Published from this directory of <https://gitlab.com/pickel-fintech/The13F>.
|
|
122
|
+
Tag pattern `sdk-mcp-vX.Y.Z` triggers a PyPI release via GitLab CI; see
|
|
123
|
+
`docs/mcp_launch.plan.md` in the repo root for the extraction story and the
|
|
124
|
+
release runbook.
|
|
125
|
+
|
|
126
|
+
## Support
|
|
127
|
+
|
|
128
|
+
tom@pickelfintech.com — quote the `request_id` field returned by any failing
|
|
129
|
+
tool call for fastest triage.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT. See [LICENSE](../LICENSE). The MCP server source is MIT-licensed; use
|
|
134
|
+
of the the13f API itself is governed by the Terms of Service at
|
|
135
|
+
<https://the13f.com/terms.html>.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "the13f-mcp"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Model Context Protocol server for the13f - institutional 13F intelligence in Claude Desktop, Cursor, and VS Code."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Pickel Fintech", email = "tom@pickelfintech.com" }]
|
|
13
|
+
keywords = ["13f", "sec", "institutional", "investment", "mcp", "claude", "cursor", "anthropic"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
dependencies = [
|
|
24
|
+
"mcp>=1.0",
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"pydantic>=2.6",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=8.0",
|
|
32
|
+
"pytest-asyncio>=0.23",
|
|
33
|
+
"respx>=0.21",
|
|
34
|
+
"ruff>=0.4",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
the13f-mcp = "the13f_mcp.server:main"
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://the13f.com/developers/"
|
|
42
|
+
Documentation = "https://the13f.com/developers/mcp.html"
|
|
43
|
+
Signup = "https://the13f.com/developers/"
|
|
44
|
+
Source = "https://gitlab.com/pickel-fintech/The13F"
|
|
45
|
+
Changelog = "https://gitlab.com/pickel-fintech/The13F/-/blob/main/mcp_server/CHANGELOG.md"
|
|
46
|
+
Issues = "https://gitlab.com/pickel-fintech/The13F/-/issues"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
where = ["src"]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""the13f-mcp - Model Context Protocol server for the13f Platform.
|
|
2
|
+
|
|
3
|
+
Exposes institutional 13F intelligence as MCP tools to Claude Desktop, Cursor,
|
|
4
|
+
VS Code, Continue, and any MCP-compatible host. Read tools work for any signed-up
|
|
5
|
+
user (free tier); signal tools and PDF report generation require Standard+
|
|
6
|
+
subscription on platform.the13f.com.
|
|
7
|
+
|
|
8
|
+
Install:
|
|
9
|
+
uvx the13f-mcp
|
|
10
|
+
|
|
11
|
+
Configure in your MCP host (e.g. ~/Library/Application Support/Claude/claude_desktop_config.json):
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"the13f": {
|
|
15
|
+
"command": "uvx",
|
|
16
|
+
"args": ["the13f-mcp"],
|
|
17
|
+
"env": {"THE13F_API_KEY": "pf13f_..."}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
"""
|
|
22
|
+
from ._version import __version__
|
|
23
|
+
|
|
24
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Configuration loader for the MCP server.
|
|
2
|
+
|
|
3
|
+
Reads three environment variables:
|
|
4
|
+
|
|
5
|
+
THE13F_API_KEY - the user's pf13f_ key issued at
|
|
6
|
+
https://the13f.com/developers/signup. Optional:
|
|
7
|
+
read tools can surface a signup CTA on the first
|
|
8
|
+
call when the key is absent. THIS IS THE
|
|
9
|
+
CONVERSION GATE.
|
|
10
|
+
|
|
11
|
+
THE13F_API_BASE_URL - defaults to https://api.the13f.com. Override for
|
|
12
|
+
local development (e.g. http://127.0.0.1:5053).
|
|
13
|
+
|
|
14
|
+
THE13F_MCP_TIMEOUT - HTTP timeout in seconds. Default 30.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
DEFAULT_BASE_URL = "https://api.the13f.com"
|
|
21
|
+
DEFAULT_TIMEOUT_SECONDS = 30
|
|
22
|
+
SIGNUP_URL = "https://the13f.com/developers/"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def api_key() -> str | None:
|
|
26
|
+
raw = os.environ.get("THE13F_API_KEY", "").strip()
|
|
27
|
+
return raw or None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def base_url() -> str:
|
|
31
|
+
return (os.environ.get("THE13F_API_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def timeout_seconds() -> float:
|
|
35
|
+
raw = os.environ.get("THE13F_MCP_TIMEOUT", str(DEFAULT_TIMEOUT_SECONDS))
|
|
36
|
+
try:
|
|
37
|
+
return max(5.0, float(raw))
|
|
38
|
+
except ValueError:
|
|
39
|
+
return float(DEFAULT_TIMEOUT_SECONDS)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Canonical research-only disclaimer text for the MCP server.
|
|
2
|
+
|
|
3
|
+
Mirrors api_gateway/config/disclaimer.yml. Update both at the same time
|
|
4
|
+
(or - better - have the MCP fetch it dynamically from the API at startup;
|
|
5
|
+
that's a v0.2 enhancement).
|
|
6
|
+
"""
|
|
7
|
+
CANONICAL_TEXT = (
|
|
8
|
+
"Research data only. Sourced from publicly disclosed SEC Form 13F "
|
|
9
|
+
"filings, which lag quarter-end by up to 45 days and reflect long-only "
|
|
10
|
+
"equity positions of the filing manager. Past institutional positioning "
|
|
11
|
+
"does not predict future performance and nothing here constitutes "
|
|
12
|
+
"investment advice, a recommendation, or a solicitation. Do your own "
|
|
13
|
+
"analysis before making investment decisions."
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
SHORT_FORM = "Research data only - not investment advice."
|
|
17
|
+
|
|
18
|
+
# Prefix for every tool description. The MCP host displays these to the LLM
|
|
19
|
+
# as part of the tool catalog; the LLM should re-surface our framing.
|
|
20
|
+
TOOL_DESCRIPTION_PREFIX = (
|
|
21
|
+
"Research data only. Returns disclosed institutional positions; "
|
|
22
|
+
"do not infer manager intent or future direction. "
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def wrap(data: dict) -> dict:
|
|
27
|
+
"""Add the disclaimer field to a tool result. Single helper for consistency."""
|
|
28
|
+
if not isinstance(data, dict):
|
|
29
|
+
data = {"data": data}
|
|
30
|
+
data.setdefault("disclaimer", CANONICAL_TEXT)
|
|
31
|
+
return data
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Shared HTTP client for the MCP server.
|
|
2
|
+
|
|
3
|
+
Wraps httpx with the platform's auth header, sane timeouts, retry-on-429
|
|
4
|
+
honoring Retry-After, and a small set of typed exceptions that the tool
|
|
5
|
+
modules surface back to the MCP host.
|
|
6
|
+
|
|
7
|
+
Importing this module is cheap; the httpx.Client is built lazily on first call.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from . import config
|
|
17
|
+
|
|
18
|
+
_client: httpx.Client | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class The13FApiError(RuntimeError):
|
|
22
|
+
"""Base exception for any non-2xx response or transport error."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, message: str, status_code: int | None = None, body: dict | None = None) -> None:
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.status_code = status_code
|
|
27
|
+
self.body = body or {}
|
|
28
|
+
self.request_id = self.body.get("request_id")
|
|
29
|
+
self.docs_url = self.body.get("docs_url")
|
|
30
|
+
self.disclaimer = self.body.get("disclaimer")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FreeSignupRequiredError(The13FApiError):
|
|
34
|
+
"""Raised when no THE13F_API_KEY is set; surfaces signup CTA to the user."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TierUpgradeRequiredError(The13FApiError):
|
|
38
|
+
"""403 - the user's tier doesn't unlock this feature."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PaymentRequiredError(The13FApiError):
|
|
42
|
+
"""402 - quota exhausted + no credits OR card declined OR no card on file."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _client_lazy() -> httpx.Client:
|
|
46
|
+
global _client
|
|
47
|
+
if _client is None:
|
|
48
|
+
headers: dict[str, str] = {"User-Agent": _user_agent()}
|
|
49
|
+
api_key = config.api_key()
|
|
50
|
+
if api_key:
|
|
51
|
+
headers["X-API-Key"] = api_key
|
|
52
|
+
_client = httpx.Client(
|
|
53
|
+
base_url=config.base_url(),
|
|
54
|
+
timeout=config.timeout_seconds(),
|
|
55
|
+
headers=headers,
|
|
56
|
+
)
|
|
57
|
+
return _client
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _user_agent() -> str:
|
|
61
|
+
from ._version import __version__
|
|
62
|
+
return f"the13f-mcp/{__version__} (python-mcp; +{config.SIGNUP_URL})"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def request(method: str, path: str, **kwargs: Any) -> dict[str, Any]:
|
|
66
|
+
"""Perform one API call with retry on 429.
|
|
67
|
+
|
|
68
|
+
Returns the parsed JSON dict. Raises one of the typed exceptions on
|
|
69
|
+
non-2xx responses.
|
|
70
|
+
"""
|
|
71
|
+
if not config.api_key():
|
|
72
|
+
raise FreeSignupRequiredError(
|
|
73
|
+
f"Free signup required. Visit {config.SIGNUP_URL}, complete the "
|
|
74
|
+
"free email signup (Cloudflare Turnstile), copy the issued "
|
|
75
|
+
"pf13f_ key, and set THE13F_API_KEY in your MCP host config "
|
|
76
|
+
"(Claude Desktop, Cursor, or VS Code + Continue). Then restart "
|
|
77
|
+
"the host.",
|
|
78
|
+
status_code=401,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
client = _client_lazy()
|
|
82
|
+
attempts = 0
|
|
83
|
+
while True:
|
|
84
|
+
attempts += 1
|
|
85
|
+
try:
|
|
86
|
+
response = client.request(method, path, **kwargs)
|
|
87
|
+
except httpx.TimeoutException as timeout_err:
|
|
88
|
+
raise The13FApiError(f"Timed out talking to the13f Platform: {timeout_err}") from timeout_err
|
|
89
|
+
except httpx.HTTPError as transport_err:
|
|
90
|
+
raise The13FApiError(f"Transport error: {transport_err}") from transport_err
|
|
91
|
+
|
|
92
|
+
if response.status_code == 429 and attempts < 3:
|
|
93
|
+
retry_after = response.headers.get("Retry-After", "1")
|
|
94
|
+
try:
|
|
95
|
+
wait_s = float(retry_after)
|
|
96
|
+
except ValueError:
|
|
97
|
+
wait_s = 1.0
|
|
98
|
+
time.sleep(min(wait_s, 30.0))
|
|
99
|
+
continue
|
|
100
|
+
return _interpret(response)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _interpret(response: httpx.Response) -> dict[str, Any]:
|
|
104
|
+
"""Parse the response. Raise typed exceptions for non-2xx."""
|
|
105
|
+
try:
|
|
106
|
+
body = response.json()
|
|
107
|
+
except ValueError:
|
|
108
|
+
body = {"error": response.text[:500]}
|
|
109
|
+
if 200 <= response.status_code < 300:
|
|
110
|
+
return body if isinstance(body, dict) else {"data": body}
|
|
111
|
+
|
|
112
|
+
message = body.get("error", f"HTTP {response.status_code}")
|
|
113
|
+
if response.status_code == 401:
|
|
114
|
+
raise FreeSignupRequiredError(message, status_code=401, body=body)
|
|
115
|
+
if response.status_code == 402:
|
|
116
|
+
raise PaymentRequiredError(message, status_code=402, body=body)
|
|
117
|
+
if response.status_code == 403:
|
|
118
|
+
raise TierUpgradeRequiredError(message, status_code=403, body=body)
|
|
119
|
+
raise The13FApiError(message, status_code=response.status_code, body=body)
|