avanza-mcp 1.2.0__tar.gz → 1.3.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.
Files changed (69) hide show
  1. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/PKG-INFO +20 -1
  2. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/README.md +19 -0
  3. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/justfile +1 -1
  4. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/__init__.py +1 -1
  5. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/prompts/__init__.py +2 -1
  6. avanza_mcp-1.3.0/src/avanza_mcp/prompts/workflows.py +488 -0
  7. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/resources/__init__.py +2 -1
  8. avanza_mcp-1.3.0/src/avanza_mcp/resources/usage.py +503 -0
  9. avanza_mcp-1.2.0/CHANGELOG.md +0 -60
  10. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.github/PUBLISHING.md +0 -0
  11. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.github/workflows/ci.yml +0 -0
  12. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.github/workflows/publish.yml +0 -0
  13. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.gitignore +0 -0
  14. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.python-version +0 -0
  15. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/CLAUDE.md +0 -0
  16. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/DEVELOPMENT.md +0 -0
  17. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/LICENSE.md +0 -0
  18. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/pyproject.toml +0 -0
  19. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/client/__init__.py +0 -0
  20. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/client/base.py +0 -0
  21. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/client/endpoints.py +0 -0
  22. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/client/exceptions.py +0 -0
  23. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/__init__.py +0 -0
  24. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/certificate.py +0 -0
  25. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/chart.py +0 -0
  26. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/common.py +0 -0
  27. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/etf.py +0 -0
  28. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/filter.py +0 -0
  29. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/fund.py +0 -0
  30. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/future_forward.py +0 -0
  31. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/instrument_data.py +0 -0
  32. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/search.py +0 -0
  33. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/stock.py +0 -0
  34. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/warrant.py +0 -0
  35. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/prompts/analysis.py +0 -0
  36. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/resources/instruments.py +0 -0
  37. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/services/__init__.py +0 -0
  38. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/services/market_data_service.py +0 -0
  39. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/services/search_service.py +0 -0
  40. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/__init__.py +0 -0
  41. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/certificates.py +0 -0
  42. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/etfs.py +0 -0
  43. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/funds.py +0 -0
  44. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/futures_forwards.py +0 -0
  45. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/instrument_data.py +0 -0
  46. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/market_data.py +0 -0
  47. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/search.py +0 -0
  48. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/warrants.py +0 -0
  49. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/__init__.py +0 -0
  50. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/conftest.py +0 -0
  51. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/__init__.py +0 -0
  52. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_certificates_integration.py +0 -0
  53. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_chart_integration.py +0 -0
  54. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_etfs_integration.py +0 -0
  55. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_futures_forwards_integration.py +0 -0
  56. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_instrument_data_integration.py +0 -0
  57. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_tools.py +0 -0
  58. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_warrants_integration.py +0 -0
  59. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/__init__.py +0 -0
  60. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_certificate_models.py +0 -0
  61. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_chart_models.py +0 -0
  62. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_client.py +0 -0
  63. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_etf_models.py +0 -0
  64. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_filter_models.py +0 -0
  65. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_future_forward_models.py +0 -0
  66. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_instrument_data_models.py +0 -0
  67. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_models.py +0 -0
  68. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_warrant_models.py +0 -0
  69. {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: avanza-mcp
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: MCP server for Avanza public market data API - Swedish stocks, funds, and more
5
5
  License-File: LICENSE.md
6
6
  Requires-Python: >=3.12
@@ -95,10 +95,29 @@ The author of this software is not responsible for any indirect damages (foresee
95
95
 
96
96
  ## 💡 MCP Prompts
97
97
 
98
+ ### Analysis Prompts
98
99
  - `analyze_stock` - Comprehensive stock analysis workflow
99
100
  - `compare_funds` - Multi-fund comparison template
100
101
  - `screen_dividend_stocks` - Dividend stock screening
101
102
 
103
+ ### Workflow Prompts (Teach AI Efficient Data Fetching)
104
+ - `search_and_analyze_instrument` - Guide for finding and analyzing instruments efficiently
105
+ - `filter_instruments_efficiently` - Guide for filtering large datasets with pagination
106
+ - `compare_multiple_instruments` - Guide for comparing instruments efficiently
107
+ - `explore_market_segment` - Guide for exploring market segments
108
+ - `get_historical_analysis` - Guide for analyzing historical data
109
+ - `screen_by_criteria` - Guide for screening by custom criteria
110
+
111
+ ## 📚 MCP Resources
112
+
113
+ ### Documentation Resources
114
+ - `avanza://docs/usage` - Comprehensive usage guide for AI assistants
115
+ - `avanza://docs/quick-start` - Quick reference for common tasks
116
+
117
+ ### Instrument Resources
118
+ - `avanza://stock/{instrument_id}` - Get stock information as markdown
119
+ - `avanza://fund/{instrument_id}` - Get fund information as markdown
120
+
102
121
 
103
122
  ### Configuration for MCP Clients
104
123
 
@@ -78,10 +78,29 @@ The author of this software is not responsible for any indirect damages (foresee
78
78
 
79
79
  ## 💡 MCP Prompts
80
80
 
81
+ ### Analysis Prompts
81
82
  - `analyze_stock` - Comprehensive stock analysis workflow
82
83
  - `compare_funds` - Multi-fund comparison template
83
84
  - `screen_dividend_stocks` - Dividend stock screening
84
85
 
86
+ ### Workflow Prompts (Teach AI Efficient Data Fetching)
87
+ - `search_and_analyze_instrument` - Guide for finding and analyzing instruments efficiently
88
+ - `filter_instruments_efficiently` - Guide for filtering large datasets with pagination
89
+ - `compare_multiple_instruments` - Guide for comparing instruments efficiently
90
+ - `explore_market_segment` - Guide for exploring market segments
91
+ - `get_historical_analysis` - Guide for analyzing historical data
92
+ - `screen_by_criteria` - Guide for screening by custom criteria
93
+
94
+ ## 📚 MCP Resources
95
+
96
+ ### Documentation Resources
97
+ - `avanza://docs/usage` - Comprehensive usage guide for AI assistants
98
+ - `avanza://docs/quick-start` - Quick reference for common tasks
99
+
100
+ ### Instrument Resources
101
+ - `avanza://stock/{instrument_id}` - Get stock information as markdown
102
+ - `avanza://fund/{instrument_id}` - Get fund information as markdown
103
+
85
104
 
86
105
  ### Configuration for MCP Clients
87
106
 
@@ -63,7 +63,7 @@ release version_part="patch":
63
63
  git add src/avanza_mcp/__init__.py
64
64
  git commit -m "Bump version to $new_version"
65
65
  git tag "v$new_version"
66
- git push origin master
66
+ git push origin main
67
67
  git push origin "v$new_version"
68
68
 
69
69
  echo ""
@@ -12,7 +12,7 @@ Provides access to:
12
12
  - Real-time market status and trading hours
13
13
  """
14
14
 
15
- __version__ = "1.2.0"
15
+ __version__ = "1.3.0"
16
16
 
17
17
  from fastmcp import FastMCP
18
18
 
@@ -2,5 +2,6 @@
2
2
 
3
3
  # Import to register prompts via decorators
4
4
  from . import analysis # noqa: F401
5
+ from . import workflows # noqa: F401
5
6
 
6
- __all__ = ["analysis"]
7
+ __all__ = ["analysis", "workflows"]
@@ -0,0 +1,488 @@
1
+ """Workflow prompt templates emphasizing when to provide scripts vs using tools."""
2
+
3
+ from .. import mcp
4
+
5
+
6
+ @mcp.prompt()
7
+ def bulk_data_script_guide(item_count: int, operation_type: str) -> str:
8
+ """Guide for providing scripts for bulk data operations.
9
+
10
+ Use this when user requests data for 50+ items.
11
+
12
+ Args:
13
+ item_count: Number of items to fetch
14
+ operation_type: Type of operation (e.g., "stock analysis", "ETF screening")
15
+
16
+ Returns:
17
+ Prompt explaining why and how to provide a script
18
+ """
19
+ return f"""For {operation_type} involving {item_count} items, I should provide a script instead of using MCP tools.
20
+
21
+ ## Why Not Use MCP Tools?
22
+
23
+ Making {item_count} MCP tool calls would:
24
+ - Take 5-10 minutes to complete
25
+ - Overload the MCP connection
26
+ - Be inefficient and error-prone
27
+
28
+ ## What to Provide Instead
29
+
30
+ I'll give you a ready-to-run script that:
31
+ - Fetches all {item_count} items in parallel (30-60 seconds)
32
+ - Saves data to a file for analysis
33
+ - Includes error handling and rate limiting
34
+
35
+ ### Python Script Template (Recommended)
36
+ ```python
37
+ import httpx
38
+ import asyncio
39
+ import json
40
+ from datetime import datetime
41
+
42
+ async def fetch_item(client, item_id):
43
+ \"\"\"Fetch single item with error handling.\"\"\"
44
+ try:
45
+ url = "https://www.avanza.se/_api/..." # Specific endpoint
46
+ response = await client.get(url, timeout=30.0)
47
+ response.raise_for_status()
48
+ return response.json()
49
+ except Exception as e:
50
+ print(f"Error fetching {{item_id}}: {{e}}")
51
+ return None
52
+
53
+ async def main():
54
+ item_ids = [...] # Your {item_count} IDs
55
+
56
+ print(f"Fetching {{len(item_ids)}} items...")
57
+
58
+ async with httpx.AsyncClient() as client:
59
+ tasks = [fetch_item(client, iid) for iid in item_ids]
60
+ results = await asyncio.gather(*tasks)
61
+
62
+ # Filter out errors
63
+ valid_results = [r for r in results if r is not None]
64
+
65
+ # Save to file
66
+ output_file = f"data_{{datetime.now().strftime('%Y%m%d_%H%M%S')}}.json"
67
+ with open(output_file, 'w') as f:
68
+ json.dump(valid_results, f, indent=2)
69
+
70
+ print(f"✅ Fetched {{len(valid_results)}}/{{len(item_ids)}} items")
71
+ print(f"📁 Saved to {{output_file}}")
72
+
73
+ if __name__ == "__main__":
74
+ asyncio.run(main())
75
+ ```
76
+
77
+ ### How to Run
78
+ ```bash
79
+ # Install dependencies
80
+ pip install httpx
81
+
82
+ # Run the script
83
+ python fetch_data.py
84
+
85
+ # Analyze the results
86
+ # I can help you analyze the JSON file after you run this!
87
+ ```
88
+
89
+ ## Next Steps
90
+
91
+ After you run the script:
92
+ 1. Share the output file with me (or key findings)
93
+ 2. I can help analyze patterns, create visualizations, etc.
94
+ 3. We can drill down into specific interesting items using MCP tools
95
+ """
96
+
97
+
98
+ @mcp.prompt()
99
+ def decide_tool_or_script(user_request: str, estimated_items: int) -> str:
100
+ """Help decide whether to use tools or provide a script.
101
+
102
+ Args:
103
+ user_request: What the user wants to do
104
+ estimated_items: Estimated number of items to process
105
+
106
+ Returns:
107
+ Decision prompt
108
+ """
109
+ if estimated_items <= 20:
110
+ approach = "use MCP tools"
111
+ reason = "small enough for interactive exploration"
112
+ elif estimated_items <= 50:
113
+ approach = "ask user preference"
114
+ reason = "medium size - tools work but script is faster"
115
+ else:
116
+ approach = "provide script"
117
+ reason = "too many for MCP tools - script is much more efficient"
118
+
119
+ return f"""Request: "{user_request}"
120
+ Estimated items: {estimated_items}
121
+
122
+ ## Decision: {approach.upper()}
123
+
124
+ **Reason**: {reason}
125
+
126
+ **Approach**:
127
+ {"I'll use MCP tools to fetch data interactively." if estimated_items <= 20 else
128
+ "I'll ask if you prefer tools (interactive) or script (faster)." if estimated_items <= 50 else
129
+ "I'll provide a Python/bash script for efficient bulk fetching."}
130
+
131
+ {"" if estimated_items <= 20 else f'''
132
+ ## Script Template
133
+
134
+ For {estimated_items} items, here's what I'll provide:
135
+
136
+ ```python
137
+ # Efficient parallel fetching
138
+ import httpx
139
+ import asyncio
140
+
141
+ async def fetch_all(ids):
142
+ async with httpx.AsyncClient() as client:
143
+ tasks = [client.get(f"https://www.avanza.se/_api/.../{{id}}") for id in ids]
144
+ responses = await asyncio.gather(*tasks, return_exceptions=True)
145
+ return [r.json() for r in responses if hasattr(r, 'json')]
146
+
147
+ # Run and save
148
+ results = asyncio.run(fetch_all(your_{estimated_items}_ids))
149
+ ```
150
+
151
+ This completes in ~30-60 seconds vs {estimated_items * 2}+ seconds with MCP tools.
152
+ '''}
153
+ """
154
+
155
+
156
+ @mcp.prompt()
157
+ def filter_large_dataset(instrument_type: str, criteria: str) -> str:
158
+ """Guide for filtering large datasets using API endpoints directly.
159
+
160
+ Args:
161
+ instrument_type: Type (certificates, etfs, warrants, etc.)
162
+ criteria: Filtering criteria
163
+
164
+ Returns:
165
+ Prompt with curl/script for bulk filtering
166
+ """
167
+ filter_endpoints = {
168
+ "certificates": "market-certificate-filter",
169
+ "etfs": "market-etf-filter",
170
+ "warrants": "market-warrant-filter",
171
+ }
172
+
173
+ endpoint = filter_endpoints.get(instrument_type.lower(), "market-etf-filter")
174
+
175
+ return f"""To screen {instrument_type} with criteria: {criteria}
176
+
177
+ ## Use the Filter API Directly (Don't Loop Through MCP Tools)
178
+
179
+ Instead of calling MCP tools repeatedly, use the filter endpoint:
180
+
181
+ ### Option 1: curl Command (Quick & Simple)
182
+
183
+ ```bash
184
+ curl 'https://www.avanza.se/_api/{endpoint}/' \\
185
+ -H 'Content-Type: application/json' \\
186
+ --data-raw '{{
187
+ "filter": {{
188
+ {{"// Add your filters here based on criteria"}}
189
+ }},
190
+ "offset": 0,
191
+ "limit": 100,
192
+ "sortBy": {{"field": "name", "order": "asc"}}
193
+ }}' | jq '.' > results.json
194
+ ```
195
+
196
+ ### Option 2: Python Script (For Processing)
197
+
198
+ ```python
199
+ import httpx
200
+ import json
201
+
202
+ def filter_{instrument_type}(filters, limit=100):
203
+ \"\"\"Filter {instrument_type} directly via API.\"\"\"
204
+ url = "https://www.avanza.se/_api/{endpoint}/"
205
+
206
+ payload = {{
207
+ "filter": filters,
208
+ "offset": 0,
209
+ "limit": limit,
210
+ "sortBy": {{"field": "name", "order": "asc"}}
211
+ }}
212
+
213
+ response = httpx.post(url, json=payload, timeout=30.0)
214
+ return response.json()
215
+
216
+ # Example: Apply your criteria
217
+ filters = {{
218
+ # Map your criteria to filter parameters
219
+ }}
220
+
221
+ results = filter_{instrument_type}(filters, limit=100)
222
+
223
+ # Save
224
+ with open('{instrument_type}_filtered.json', 'w') as f:
225
+ json.dump(results, f, indent=2)
226
+
227
+ print(f"Found {{results.get('totalNumberOfOrderbooks', 0)}} matches")
228
+ ```
229
+
230
+ ## Available Filter Parameters
231
+
232
+ **For {instrument_type}**:
233
+ {_get_filter_params(instrument_type)}
234
+
235
+ ## Pagination for Large Results
236
+
237
+ If results exceed 100:
238
+
239
+ ```bash
240
+ # Get next page
241
+ curl 'https://www.avanza.se/_api/{endpoint}/' \\
242
+ -H 'Content-Type: application/json' \\
243
+ --data-raw '{{
244
+ "filter": {{}},
245
+ "offset": 100, # Next page
246
+ "limit": 100,
247
+ "sortBy": {{"field": "name", "order": "asc"}}
248
+ }}'
249
+ ```
250
+
251
+ ## After Fetching
252
+
253
+ Once you have the results:
254
+ 1. I can help analyze patterns in the data
255
+ 2. Pick interesting items for deep dives using MCP tools
256
+ 3. Create comparisons or visualizations
257
+ """
258
+
259
+
260
+ def _get_filter_params(instrument_type: str) -> str:
261
+ """Get filter parameters for instrument type."""
262
+ params = {
263
+ "certificates": """
264
+ - `directions`: ["long", "short"]
265
+ - `leverages`: [1.0, 2.0, 3.0, ...]
266
+ - `issuers`: ["Valour", "WisdomTree", ...]
267
+ - `underlyingInstruments`: [orderbookIds]
268
+ """,
269
+ "etfs": """
270
+ - `exposures`: ["usa", "europe", "global", ...]
271
+ - `assetCategories`: ["stock", "bond", "commodity", ...]
272
+ - `riskScores`: ["risk_one", "risk_two", ...]
273
+ - `managementFee`: (use sortBy to filter)
274
+ """,
275
+ "warrants": """
276
+ - `directions`: ["long", "short"]
277
+ - `subTypes`: ["TURBO", "MINI", ...]
278
+ - `issuers`: ["Societe Generale", ...]
279
+ - `underlyingInstruments`: [orderbookIds]
280
+ """,
281
+ }
282
+ return params.get(instrument_type.lower(), "Check API documentation")
283
+
284
+
285
+ @mcp.prompt()
286
+ def analyze_vs_fetch(operation_description: str, requires_bulk_data: bool) -> str:
287
+ """Distinguish between analysis (use tools) and fetching (use scripts).
288
+
289
+ Args:
290
+ operation_description: What the user wants to do
291
+ requires_bulk_data: Whether it needs 50+ items
292
+
293
+ Returns:
294
+ Guidance prompt
295
+ """
296
+ if requires_bulk_data:
297
+ return f"""Operation: "{operation_description}"
298
+
299
+ ## This Requires Bulk Data Fetching
300
+
301
+ ### Two-Step Approach:
302
+
303
+ **Step 1: Fetch Data (You Do This)**
304
+ I'll provide a script to fetch the data:
305
+ - Runs in 30-60 seconds
306
+ - Saves to JSON/CSV file
307
+ - Handles errors gracefully
308
+
309
+ **Step 2: Analyze Data (I Help With This)**
310
+ After you have the data:
311
+ - Share the file or key metrics
312
+ - I'll use MCP tools for specific deep dives
313
+ - We can explore patterns together
314
+
315
+ ### Why This Approach?
316
+
317
+ - **Efficient**: Parallel fetching vs sequential MCP calls
318
+ - **Reliable**: Better error handling, can resume if failed
319
+ - **Flexible**: You own the data for other analyses
320
+
321
+ ### Script I'll Provide
322
+
323
+ ```python
324
+ # Fast parallel data fetcher
325
+ import httpx, asyncio, json
326
+
327
+ async def fetch_all():
328
+ ids = [...] # Your list
329
+ async with httpx.AsyncClient() as c:
330
+ tasks = [c.get(f"https://www.avanza.se/_api/.../{{id}}") for id in ids]
331
+ return await asyncio.gather(*tasks, return_exceptions=True)
332
+
333
+ results = asyncio.run(fetch_all())
334
+ with open('data.json', 'w') as f:
335
+ json.dump([r.json() for r in results if hasattr(r, 'json')], f)
336
+ ```
337
+
338
+ ### Next: Share Results & Analyze Together
339
+ """
340
+ else:
341
+ return f"""Operation: "{operation_description}"
342
+
343
+ ## This Can Use MCP Tools Directly
344
+
345
+ I'll use MCP tools interactively because:
346
+ - Small enough dataset (< 20 items)
347
+ - Interactive exploration is valuable
348
+ - Results available immediately
349
+
350
+ I'll proceed with using the appropriate MCP tools for this analysis.
351
+ """
352
+
353
+
354
+ @mcp.prompt()
355
+ def script_template_selector(
356
+ task: str, data_source: str, output_format: str = "json"
357
+ ) -> str:
358
+ """Provide appropriate script template based on task.
359
+
360
+ Args:
361
+ task: What to accomplish
362
+ data_source: Where to get data (endpoint type)
363
+ output_format: Desired output format
364
+
365
+ Returns:
366
+ Complete script template
367
+ """
368
+ return f"""Task: {task}
369
+ Data Source: {data_source}
370
+ Output: {output_format}
371
+
372
+ ## Recommended Script
373
+
374
+ ```python
375
+ #!/usr/bin/env python3
376
+ \"\"\"
377
+ {task}
378
+
379
+ Fetches data from Avanza's public API and saves to {output_format}.
380
+ No authentication required.
381
+ \"\"\"
382
+
383
+ import httpx
384
+ import asyncio
385
+ import json
386
+ from pathlib import Path
387
+ from datetime import datetime
388
+
389
+ # Configuration
390
+ BASE_URL = "https://www.avanza.se/_api"
391
+ OUTPUT_DIR = Path("avanza_data")
392
+ MAX_CONCURRENT = 10 # Limit concurrent requests
393
+
394
+ async def fetch_item(client, item_id, semaphore):
395
+ \"\"\"Fetch single item with rate limiting.\"\"\"
396
+ async with semaphore:
397
+ try:
398
+ url = f"{{BASE_URL}}/{data_source}/{{item_id}}"
399
+ response = await client.get(url, timeout=30.0)
400
+ response.raise_for_status()
401
+ return {{"id": item_id, "data": response.json(), "error": None}}
402
+ except Exception as e:
403
+ return {{"id": item_id, "data": None, "error": str(e)}}
404
+
405
+ async def main():
406
+ # TODO: Replace with your actual IDs
407
+ item_ids = [
408
+ # "5247", # Example: Volvo
409
+ # "5361", # Example: Ericsson
410
+ # Add your IDs here
411
+ ]
412
+
413
+ if not item_ids:
414
+ print("⚠️ Please add item IDs to the script")
415
+ return
416
+
417
+ print(f"📊 Fetching {{len(item_ids)}} items...")
418
+
419
+ # Create output directory
420
+ OUTPUT_DIR.mkdir(exist_ok=True)
421
+
422
+ # Rate limiting semaphore
423
+ semaphore = asyncio.Semaphore(MAX_CONCURRENT)
424
+
425
+ # Fetch all items
426
+ async with httpx.AsyncClient() as client:
427
+ tasks = [fetch_item(client, iid, semaphore) for iid in item_ids]
428
+ results = await asyncio.gather(*tasks)
429
+
430
+ # Separate successful and failed
431
+ successful = [r for r in results if r["error"] is None]
432
+ failed = [r for r in results if r["error"] is not None]
433
+
434
+ # Save results
435
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
436
+ output_file = OUTPUT_DIR / f"results_{{timestamp}}.{output_format}"
437
+
438
+ with open(output_file, "w", encoding="utf-8") as f:
439
+ json.dump(
440
+ {{
441
+ "metadata": {{
442
+ "timestamp": timestamp,
443
+ "total": len(item_ids),
444
+ "successful": len(successful),
445
+ "failed": len(failed),
446
+ }},
447
+ "data": [r["data"] for r in successful],
448
+ "errors": [{{"id": r["id"], "error": r["error"]}} for r in failed],
449
+ }},
450
+ f,
451
+ indent=2,
452
+ ensure_ascii=False,
453
+ )
454
+
455
+ # Print summary
456
+ print(f"\\n✅ Successfully fetched: {{len(successful)}}/{{len(item_ids)}}")
457
+ if failed:
458
+ print(f"❌ Failed: {{len(failed)}}")
459
+ for fail in failed[:5]: # Show first 5 errors
460
+ print(f" - {{fail['id']}}: {{fail['error']}}")
461
+ print(f"\\n📁 Saved to: {{output_file}}")
462
+
463
+ if __name__ == "__main__":
464
+ asyncio.run(main())
465
+ ```
466
+
467
+ ## How to Use
468
+
469
+ 1. **Save the script**: `save as fetch_data.py`
470
+ 2. **Add your IDs**: Edit the `item_ids` list
471
+ 3. **Install dependency**: `pip install httpx`
472
+ 4. **Run**: `python fetch_data.py`
473
+
474
+ ## What You Get
475
+
476
+ - JSON file with all results
477
+ - Error tracking for failed requests
478
+ - Timestamp for record keeping
479
+ - Summary of success/failure rates
480
+
481
+ ## After Running
482
+
483
+ Share the results file and I can help you:
484
+ - Analyze patterns
485
+ - Create visualizations
486
+ - Filter and sort data
487
+ - Deep dive on specific items
488
+ """
@@ -2,5 +2,6 @@
2
2
 
3
3
  # Import to register resources via decorators
4
4
  from . import instruments # noqa: F401
5
+ from . import usage # noqa: F401
5
6
 
6
- __all__ = ["instruments"]
7
+ __all__ = ["instruments", "usage"]