avanza-mcp 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl

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.
avanza_mcp/__init__.py CHANGED
@@ -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"]
@@ -0,0 +1,503 @@
1
+ """Usage guide resources for AI callers."""
2
+
3
+ from .. import mcp
4
+
5
+ USAGE_GUIDE = """# Avanza MCP Server - Usage Guide for AI Assistants
6
+
7
+ ## 🎯 Critical: When NOT to Use MCP Tools
8
+
9
+ ### MCP Tools Are For Interactive Exploration, NOT Bulk Data Processing
10
+
11
+ **Use MCP tools for**:
12
+ - ✅ Single instrument lookups (1-5 items)
13
+ - ✅ Small comparisons (2-10 instruments)
14
+ - ✅ Quick searches and exploration
15
+ - ✅ Interactive data analysis
16
+
17
+ **DO NOT use MCP tools for**:
18
+ - ❌ Fetching data for 50+ instruments
19
+ - ❌ Bulk screening operations
20
+ - ❌ Large-scale data analysis
21
+ - ❌ Building datasets
22
+
23
+ ## 📏 The Threshold Rule
24
+
25
+ | Number of Items | Action |
26
+ |----------------|---------|
27
+ | 1-20 items | Use MCP tools directly |
28
+ | 20-50 items | Ask user if they want to continue with tools OR get a script |
29
+ | 50+ items | **DO NOT USE MCP TOOLS** - Provide a script instead |
30
+
31
+ ## 🚫 Common Anti-Pattern (DON'T DO THIS)
32
+
33
+ ```
34
+ User: "Analyze the top 100 Swedish stocks"
35
+
36
+ ❌ BAD Response:
37
+ for stock_id in top_100_stocks:
38
+ get_stock_info(stock_id) # This will make 100 MCP calls!
39
+ get_stock_chart(stock_id) # Another 100 calls!
40
+ ```
41
+
42
+ **Problem**: This creates 200+ MCP tool calls, which is:
43
+ - Slow and inefficient
44
+ - Overloads the MCP connection
45
+ - Not what MCP tools are designed for
46
+
47
+ ## ✅ Correct Approach for Large Data Fetching
48
+
49
+ ### Option 1: Provide a Python Script
50
+
51
+ ```python
52
+ User: "Analyze the top 100 Swedish stocks"
53
+
54
+ ✅ GOOD Response:
55
+ "For analyzing 100 stocks, you should fetch the data directly using a script
56
+ rather than MCP tools. Here's a Python script you can run:
57
+
58
+ ```python
59
+ import httpx
60
+ import asyncio
61
+ import json
62
+
63
+ async def fetch_stock_info(client, stock_id):
64
+ url = f"https://www.avanza.se/_api/market-guide/stock/{stock_id}"
65
+ response = await client.get(url)
66
+ return response.json()
67
+
68
+ async def main():
69
+ stock_ids = ["5247", "5361", ...] # Your 100 stock IDs
70
+
71
+ async with httpx.AsyncClient() as client:
72
+ tasks = [fetch_stock_info(client, sid) for sid in stock_ids]
73
+ results = await asyncio.gather(*tasks)
74
+
75
+ # Save to file
76
+ with open('stocks_data.json', 'w') as f:
77
+ json.dump(results, f, indent=2)
78
+
79
+ print(f"Fetched {len(results)} stocks")
80
+
81
+ asyncio.run(main())
82
+ ```
83
+
84
+ This will efficiently fetch all 100 stocks in parallel and save to a JSON file."
85
+ ```
86
+
87
+ ### Option 2: Provide curl Commands
88
+
89
+ ```bash
90
+ User: "Get data for 50 certificates"
91
+
92
+ ✅ GOOD Response:
93
+ "For fetching 50 certificates, use the filter endpoint directly with a script:
94
+
95
+ ```bash
96
+ # Get all certificates at once with pagination
97
+ curl 'https://www.avanza.se/_api/market-certificate-filter/' \\
98
+ -H 'Content-Type: application/json' \\
99
+ --data-raw '{
100
+ "filter": {},
101
+ "offset": 0,
102
+ "limit": 50,
103
+ "sortBy": {"field": "name", "order": "asc"}
104
+ }' > certificates.json
105
+
106
+ # For more than 50, fetch next page:
107
+ curl 'https://www.avanza.se/_api/market-certificate-filter/' \\
108
+ -H 'Content-Type: application/json' \\
109
+ --data-raw '{
110
+ "filter": {},
111
+ "offset": 50,
112
+ "limit": 50,
113
+ "sortBy": {"field": "name", "order": "asc"}
114
+ }' >> certificates.json
115
+ ```
116
+
117
+ This fetches the data directly from Avanza's public API."
118
+ ```
119
+
120
+ ### Option 3: Provide a Shell Script for Multiple Items
121
+
122
+ ```bash
123
+ User: "Get stock info for these 100 companies"
124
+
125
+ ✅ GOOD Response:
126
+ "Here's a shell script to fetch data for all 100 stocks:
127
+
128
+ ```bash
129
+ #!/bin/bash
130
+
131
+ # Array of stock IDs
132
+ stock_ids=(5247 5361 5239 ...) # All 100 IDs
133
+
134
+ # Create output directory
135
+ mkdir -p stock_data
136
+
137
+ # Fetch each stock
138
+ for id in "${stock_ids[@]}"; do
139
+ echo "Fetching stock $id..."
140
+ curl -s "https://www.avanza.se/_api/market-guide/stock/$id" \\
141
+ > "stock_data/stock_$id.json"
142
+ sleep 0.5 # Be nice to the API
143
+ done
144
+
145
+ echo "Done! Data saved in stock_data/"
146
+ ```
147
+
148
+ Run with: `chmod +x fetch_stocks.sh && ./fetch_stocks.sh`"
149
+ ```
150
+
151
+ ## 📋 API Endpoint Reference (For Script Writing)
152
+
153
+ When instructing users to write scripts, these are the public endpoints:
154
+
155
+ ### Search & Discovery
156
+ ```bash
157
+ # Search instruments
158
+ curl 'https://www.avanza.se/_api/search/filtered-search' \\
159
+ -H 'Content-Type: application/json' \\
160
+ --data-raw '{"query": "Tesla", "instrumentTypes": ["STOCK"]}'
161
+ ```
162
+
163
+ ### Stock Data
164
+ ```bash
165
+ # Stock info
166
+ curl 'https://www.avanza.se/_api/market-guide/stock/{id}'
167
+
168
+ # Stock quote
169
+ curl 'https://www.avanza.se/_api/market-guide/stock/{id}/quote'
170
+
171
+ # Stock chart
172
+ curl 'https://www.avanza.se/_api/price-chart/stock/{id}?timePeriod=one_month'
173
+
174
+ # Order book
175
+ curl 'https://www.avanza.se/_api/market-guide/stock/{id}/orderdepth'
176
+ ```
177
+
178
+ ### Filter Endpoints (Bulk Operations)
179
+ ```bash
180
+ # Filter certificates (POST request)
181
+ curl 'https://www.avanza.se/_api/market-certificate-filter/' \\
182
+ -H 'Content-Type: application/json' \\
183
+ --data-raw '{
184
+ "filter": {"directions": ["long"]},
185
+ "offset": 0,
186
+ "limit": 100,
187
+ "sortBy": {"field": "name", "order": "asc"}
188
+ }'
189
+
190
+ # Filter ETFs (POST request)
191
+ curl 'https://www.avanza.se/_api/market-etf-filter/' \\
192
+ -H 'Content-Type: application/json' \\
193
+ --data-raw '{
194
+ "filter": {"exposures": ["usa"]},
195
+ "offset": 0,
196
+ "limit": 100,
197
+ "sortBy": {"field": "managementFee", "order": "asc"}
198
+ }'
199
+
200
+ # Filter warrants (POST request)
201
+ curl 'https://www.avanza.se/_api/market-warrant-filter/' \\
202
+ -H 'Content-Type: application/json' \\
203
+ --data-raw '{
204
+ "filter": {"subTypes": ["TURBO"]},
205
+ "offset": 0,
206
+ "limit": 100,
207
+ "sortBy": {"field": "name", "order": "asc"}
208
+ }'
209
+ ```
210
+
211
+ ### Fund Data
212
+ ```bash
213
+ # Fund info
214
+ curl 'https://www.avanza.se/_api/fund-guide/guide/{id}'
215
+
216
+ # Fund chart
217
+ curl 'https://www.avanza.se/_api/fund-guide/chart/{id}/three_years'
218
+ ```
219
+
220
+ ## 🔄 Decision Tree: Tool vs Script
221
+
222
+ ```
223
+ User request → Count number of items needed
224
+
225
+ ├─ 1-20 items? → Use MCP tools directly
226
+
227
+ ├─ 20-50 items? → Ask user:
228
+ │ "This will require ~{count} API calls. Would you like me to:
229
+ │ 1. Proceed with MCP tools (slower, interactive)
230
+ │ 2. Provide a script for you to run (faster, bulk)"
231
+
232
+ └─ 50+ items? → ALWAYS provide script
233
+
234
+ ├─ User is technical? → Python script with async
235
+ ├─ User wants simple? → Bash script with curl
236
+ └─ User wants data file? → curl command + jq
237
+ ```
238
+
239
+ ## 🎓 Examples of Correct Responses
240
+
241
+ ### Example 1: Small Request (Use Tools)
242
+
243
+ ```
244
+ User: "What's the current price of Volvo?"
245
+
246
+ ✅ Response:
247
+ search_instruments(query="Volvo B", instrument_types=["STOCK"])
248
+ get_stock_quote(instrument_id="5247")
249
+ "Volvo B is currently trading at 265.40 SEK, up 1.2%"
250
+ ```
251
+
252
+ ### Example 2: Medium Request (Ask User)
253
+
254
+ ```
255
+ User: "Compare these 30 ETFs"
256
+
257
+ ✅ Response:
258
+ "Comparing 30 ETFs will require approximately 60 API calls through MCP tools.
259
+ I have two options for you:
260
+
261
+ 1. **Use MCP Tools** (slower, ~2-3 minutes):
262
+ I'll fetch each ETF's data interactively. Good if you want to explore
263
+ the data step-by-step.
264
+
265
+ 2. **Provide a Script** (faster, ~30 seconds):
266
+ I'll give you a Python script that fetches all 30 ETFs in parallel,
267
+ saves to JSON, and you can analyze the results.
268
+
269
+ Which would you prefer?"
270
+ ```
271
+
272
+ ### Example 3: Large Request (Provide Script)
273
+
274
+ ```
275
+ User: "Analyze all Swedish stocks with market cap > 1B SEK"
276
+
277
+ ✅ Response:
278
+ "To analyze 100+ stocks efficiently, you should fetch the data directly
279
+ rather than using MCP tools. Here's a Python script:
280
+
281
+ ```python
282
+ import httpx
283
+ import asyncio
284
+ import pandas as pd
285
+
286
+ async def fetch_stock(client, stock_id):
287
+ try:
288
+ response = await client.get(
289
+ f"https://www.avanza.se/_api/market-guide/stock/{stock_id}"
290
+ )
291
+ return response.json()
292
+ except Exception as e:
293
+ print(f"Error fetching {stock_id}: {e}")
294
+ return None
295
+
296
+ async def main():
297
+ # First, get Swedish stock IDs (you'd need to search or have a list)
298
+ stock_ids = [...] # Your list of stock IDs
299
+
300
+ async with httpx.AsyncClient(timeout=30.0) as client:
301
+ tasks = [fetch_stock(client, sid) for sid in stock_ids]
302
+ results = await asyncio.gather(*tasks)
303
+
304
+ # Filter for market cap > 1B
305
+ large_caps = [
306
+ s for s in results
307
+ if s and s.get('company', {}).get('marketCapital', 0) > 1_000_000_000
308
+ ]
309
+
310
+ # Convert to DataFrame for analysis
311
+ df = pd.DataFrame([{
312
+ 'name': s['name'],
313
+ 'price': s['quote']['last'],
314
+ 'market_cap': s['company']['marketCapital'],
315
+ 'pe_ratio': s.get('keyIndicators', {}).get('priceEarningsRatio'),
316
+ } for s in large_caps])
317
+
318
+ df.to_csv('large_cap_stocks.csv', index=False)
319
+ print(f"Analyzed {len(large_caps)} large-cap stocks")
320
+ print(df.head())
321
+
322
+ asyncio.run(main())
323
+ ```
324
+
325
+ Save this as `analyze_stocks.py` and run with:
326
+ ```bash
327
+ pip install httpx pandas
328
+ python analyze_stocks.py
329
+ ```
330
+
331
+ After running this, I can help you analyze the results in `large_cap_stocks.csv`."
332
+ ```
333
+
334
+ ## 💡 Key Principles
335
+
336
+ 1. **MCP tools = Interactive exploration** (1-20 items)
337
+ 2. **Scripts = Bulk data processing** (50+ items)
338
+ 3. **Always explain why** you're recommending a script over tools
339
+ 4. **Provide working code** that users can run immediately
340
+ 5. **Include error handling** in scripts (timeouts, retries)
341
+ 6. **Be nice to the API** (rate limiting, delays)
342
+
343
+ ## 🚀 Script Templates You Can Use
344
+
345
+ ### Python: Async Bulk Fetcher
346
+ ```python
347
+ import httpx
348
+ import asyncio
349
+
350
+ async def fetch_data(url):
351
+ async with httpx.AsyncClient() as client:
352
+ response = await client.get(url, timeout=30.0)
353
+ return response.json()
354
+
355
+ async def bulk_fetch(urls):
356
+ tasks = [fetch_data(url) for url in urls]
357
+ return await asyncio.gather(*tasks, return_exceptions=True)
358
+
359
+ # Usage
360
+ urls = [f"https://www.avanza.se/_api/market-guide/stock/{i}" for i in stock_ids]
361
+ results = asyncio.run(bulk_fetch(urls))
362
+ ```
363
+
364
+ ### Bash: Simple Sequential Fetcher
365
+ ```bash
366
+ #!/bin/bash
367
+ for id in $(cat stock_ids.txt); do
368
+ curl -s "https://www.avanza.se/_api/market-guide/stock/$id" \\
369
+ > "data/stock_$id.json"
370
+ sleep 0.5
371
+ done
372
+ ```
373
+
374
+ ### curl + jq: Filter and Extract
375
+ ```bash
376
+ # Fetch and extract just the fields you need
377
+ curl -s 'https://www.avanza.se/_api/market-certificate-filter/' \\
378
+ -H 'Content-Type: application/json' \\
379
+ --data-raw '{"filter":{},"offset":0,"limit":100,"sortBy":{"field":"name","order":"asc"}}' \\
380
+ | jq '.certificates[] | {name, orderbookId, leverage, direction}'
381
+ ```
382
+
383
+ ## ⚠️ Important Reminders
384
+
385
+ - **No authentication required** - All endpoints shown are public
386
+ - **Rate limiting** - Add delays (0.5-1s) between requests in loops
387
+ - **Timeout handling** - Use appropriate timeouts (10-30s)
388
+ - **Error handling** - Check for HTTP errors and null responses
389
+ - **Respect the API** - Don't hammer the servers with concurrent requests
390
+
391
+ ## 📊 Summary
392
+
393
+ | Scenario | Items | Action |
394
+ |----------|-------|--------|
395
+ | Quick lookup | 1-5 | Use MCP tools |
396
+ | Comparison | 5-20 | Use MCP tools |
397
+ | Small screening | 20-50 | Ask user preference |
398
+ | Bulk screening | 50-200 | Provide script |
399
+ | Large analysis | 200+ | Provide script + pagination guide |
400
+
401
+ **Golden Rule**: If you're thinking about calling the same tool in a loop more than 20 times, provide a script instead.
402
+ """
403
+
404
+
405
+ @mcp.resource("avanza://docs/usage")
406
+ async def get_usage_guide() -> str:
407
+ """Get comprehensive usage guide for AI assistants.
408
+
409
+ URI: avanza://docs/usage
410
+
411
+ This resource provides critical guidance on:
412
+ - When to use MCP tools (small, interactive queries)
413
+ - When to provide scripts instead (bulk data fetching)
414
+ - How to write curl commands and Python scripts for users
415
+ - API endpoint reference for script writing
416
+
417
+ Returns:
418
+ Markdown-formatted usage guide
419
+ """
420
+ return USAGE_GUIDE
421
+
422
+
423
+ @mcp.resource("avanza://docs/quick-start")
424
+ async def get_quick_start() -> str:
425
+ """Get quick start guide focused on tool vs script decision.
426
+
427
+ URI: avanza://docs/quick-start
428
+
429
+ Returns:
430
+ Quick reference guide
431
+ """
432
+ return """# Avanza MCP - Quick Decision Guide
433
+
434
+ ## 🚦 When to Use What
435
+
436
+ ### Use MCP Tools (✅)
437
+ - Single lookups: "What's Tesla's price?"
438
+ - Small comparisons: "Compare these 5 funds"
439
+ - Quick exploration: "Find ETFs with USA exposure"
440
+ - Interactive analysis: Step-by-step investigation
441
+
442
+ ### Provide Script (📝)
443
+ - Bulk operations: "Analyze 100 stocks"
444
+ - Large screenings: "Get all certificates"
445
+ - Dataset building: "Fetch all Swedish funds"
446
+ - Repeated operations: "Daily price monitoring"
447
+
448
+ ## 📏 The Rule
449
+
450
+ ```
451
+ if items > 50:
452
+ provide_script()
453
+ elif items > 20:
454
+ ask_user_preference()
455
+ else:
456
+ use_mcp_tools()
457
+ ```
458
+
459
+ ## 🔧 Quick Script Templates
460
+
461
+ ### Fetch Multiple Stocks (Python)
462
+ ```python
463
+ import httpx
464
+ import asyncio
465
+
466
+ async def main():
467
+ stock_ids = ["5247", "5361", ...]
468
+ async with httpx.AsyncClient() as client:
469
+ for sid in stock_ids:
470
+ r = await client.get(f"https://www.avanza.se/_api/market-guide/stock/{sid}")
471
+ print(r.json()['name'], r.json()['quote']['last'])
472
+
473
+ asyncio.run(main())
474
+ ```
475
+
476
+ ### Bulk Filter (curl)
477
+ ```bash
478
+ curl 'https://www.avanza.se/_api/market-etf-filter/' \\
479
+ -H 'Content-Type: application/json' \\
480
+ --data-raw '{"filter":{},"offset":0,"limit":100,"sortBy":{"field":"name","order":"asc"}}'
481
+ ```
482
+
483
+ ## 💭 Example Responses
484
+
485
+ **User**: "Check Volvo stock"
486
+ **You**: ✅ Use `get_stock_quote("5247")`
487
+
488
+ **User**: "Compare 10 funds"
489
+ **You**: ✅ Use `get_fund_info()` for each
490
+
491
+ **User**: "Analyze 80 Swedish stocks"
492
+ **You**: 📝 "Here's a Python script to fetch all 80 stocks..."
493
+
494
+ **User**: "Screen all ETFs"
495
+ **You**: 📝 "Here's a curl command using filter_etfs endpoint..."
496
+
497
+ ## 🎯 Remember
498
+
499
+ MCP = Interactive exploration (1-20 items)
500
+ Script = Bulk processing (50+ items)
501
+
502
+ Read full guide: `avanza://docs/usage`
503
+ """
@@ -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
 
@@ -1,4 +1,4 @@
1
- avanza_mcp/__init__.py,sha256=2pf3Q75vzuidsLXDpi9E_beR2PvHvJQiGbUyNuiOiT8,1002
1
+ avanza_mcp/__init__.py,sha256=ccShZ9OeQ4V6UgFirUJZI9OV5cD8H93MgIIkfyO-bnw,1002
2
2
  avanza_mcp/client/__init__.py,sha256=Yab-cUgQclyZgqdUkl7I15abQjEXVLoTvD3430oxpuY,515
3
3
  avanza_mcp/client/base.py,sha256=92WrXDf0oTvE9gLJvEcbNkgbaT3rQkhoCAoGIpbaz0M,12853
4
4
  avanza_mcp/client/endpoints.py,sha256=S7LK9Ab2ATHMEZ_680SbPbi5PogzbpGa-2USoMGthjo,2732
@@ -15,10 +15,12 @@ avanza_mcp/models/instrument_data.py,sha256=-5MJTCFy4W8zVU-oVjTBxWo55SzGG62577X3
15
15
  avanza_mcp/models/search.py,sha256=HrP3V2T9Y1LBk_wwnNJdqg6XbHaz96AqvVUQ9iwuNyw,2816
16
16
  avanza_mcp/models/stock.py,sha256=KR7SxeaiGe-7Me0-mXuqE37xwyhW94FPn8zjOtpyh8I,6420
17
17
  avanza_mcp/models/warrant.py,sha256=k8uzeSOeFxPZuV9CE0vZwvOpmDZwlNto9PzPQKtzJ8o,2324
18
- avanza_mcp/prompts/__init__.py,sha256=P7ClxG1itpYEKXGP9nQzqJ1zo2S0QeiHgOsfgwdf7VQ,140
18
+ avanza_mcp/prompts/__init__.py,sha256=6GOy34A-1ZIhMk7yFyIJ8pHnE8j42aaBRReiuAOVlBE,191
19
19
  avanza_mcp/prompts/analysis.py,sha256=uW3P_jdupoNp7QsyjPsjXJjCNlR5646oYyVV9SMwXeM,3694
20
- avanza_mcp/resources/__init__.py,sha256=jXyXHN_BV7HKkVh-2EfqHui7PzfcPlpEY7GaCWJuDQA,150
20
+ avanza_mcp/prompts/workflows.py,sha256=x56DjSRQZvV-yNHGL5BXwgb8MCCyy-rYXrSsLp0KJzw,13414
21
+ avanza_mcp/resources/__init__.py,sha256=IgzNHfy_fXl-oUfzL10HctB8GC5jOUIY58z3uKeXUg8,193
21
22
  avanza_mcp/resources/instruments.py,sha256=1pZbQ19NAWNMD_qmBOcniwf988jpFKUK4QiXrdOyYGw,3948
23
+ avanza_mcp/resources/usage.py,sha256=sGtlR_dF-nHvFYEQYYugmispCgeKnT7OiFd_YfDhvNw,13602
22
24
  avanza_mcp/services/__init__.py,sha256=hCCYSVLvFglaGfNo4MXQP8mzsKHmV8yG_iNH0VsIsPs,190
23
25
  avanza_mcp/services/market_data_service.py,sha256=EEjF1rwUAZ9ejLeLJJu49cntTaHuZ4opApJ8faEj6so,19212
24
26
  avanza_mcp/services/search_service.py,sha256=op2BKokwraqoOSv0SdnLMFBJJODyqCAhOaFiCJ1hc30,1930
@@ -31,8 +33,8 @@ avanza_mcp/tools/instrument_data.py,sha256=EkE4dPSm1d4m1CeofCBdualwQqbPB8UtxCGon
31
33
  avanza_mcp/tools/market_data.py,sha256=qqvnrnYpcvPucZlqvLHODh4DsRF5XPC7WmguDmaEzEk,16474
32
34
  avanza_mcp/tools/search.py,sha256=srB7K-9VCJhB6Zvs4aLX_jfFekiMUIkYgOAp5IrCApg,3722
33
35
  avanza_mcp/tools/warrants.py,sha256=jLaC3FK5X1DQcU_f5oXXP6asjTTTp2GfhDcE47uF94M,4515
34
- avanza_mcp-1.2.0.dist-info/METADATA,sha256=Xwb4iHc8wqNJ9LiNd7Mj_j304WihW6jvCugOhXjCb-U,5173
35
- avanza_mcp-1.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
36
- avanza_mcp-1.2.0.dist-info/entry_points.txt,sha256=4MF6-imOIBHK-U5DLdRrufwrV3MXfqGG0OIA_e4Jl_M,47
37
- avanza_mcp-1.2.0.dist-info/licenses/LICENSE.md,sha256=WVxy2qGCtophbFXxeAHHY0IksvRJLsUIuRgcGNtqH5Q,1065
38
- avanza_mcp-1.2.0.dist-info/RECORD,,
36
+ avanza_mcp-1.3.0.dist-info/METADATA,sha256=h_5cJ6f4AdJVNUXjYqDzObJQLSquipu5m-5BBSZ5viM,6058
37
+ avanza_mcp-1.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
38
+ avanza_mcp-1.3.0.dist-info/entry_points.txt,sha256=4MF6-imOIBHK-U5DLdRrufwrV3MXfqGG0OIA_e4Jl_M,47
39
+ avanza_mcp-1.3.0.dist-info/licenses/LICENSE.md,sha256=WVxy2qGCtophbFXxeAHHY0IksvRJLsUIuRgcGNtqH5Q,1065
40
+ avanza_mcp-1.3.0.dist-info/RECORD,,