-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathllms.txt
More file actions
355 lines (261 loc) · 11 KB
/
llms.txt
File metadata and controls
355 lines (261 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# MCP-Toolbox Development Guide for LLMs
This guide is designed to help you (an LLM) effectively contribute to the mcp-toolbox project. It provides essential information about the project structure, development workflow, and best practices.
## Project Overview
MCP-Toolbox is a Python package that provides tools for enhancing LLMs through the Model Context Protocol (MCP). The project implements various API integrations as MCP tools, allowing LLMs to interact with external services.
### Key Components
- **mcp_toolbox/app.py**: Initializes the FastMCP server
- **mcp_toolbox/cli.py**: Command-line interface for running the MCP server
- **mcp_toolbox/config.py**: Configuration management using Pydantic
- **mcp_toolbox/figma/**: Figma API integration tools
- **tests/**: Test files for the project
## Environment Setup
Always help the user set up a proper development environment using `uv`. This is the preferred package manager for this project.
### Setting Up the Environment
```bash
# Install uv if not already installed
curl -LsSf https://astral.sh/uv/install.sh | sh # For macOS/Linux
# or
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # For Windows
# Clone the repository (if not already done)
git clone https://github.com/username/mcp-toolbox.git
cd mcp-toolbox
# Create and activate a virtual environment
uv venv
source .venv/bin/activate # For macOS/Linux
# or
.venv\Scripts\activate # For Windows
# Install the package in development mode
uv pip install -e .
# Install development dependencies
uv pip install -e ".[dev]"
```
## GitHub Workflow
Always follow proper GitHub workflow when making changes:
1. **Create a branch with a descriptive name**:
```bash
# Assume the user already has their own fork
git checkout -b feature/add-spotify-integration
```
2. **Make your changes**: Implement the requested features or fixes
3. **Run tests and checks**:
```bash
make check # Run linting and formatting
make test # Run tests
```
4. **Commit your changes with descriptive messages**:
```bash
git add .
git commit -m "feat: add Spotify API integration"
```
5. **Push your changes**:
```bash
git push origin feature/add-spotify-integration
```
6. **Create a pull request**: Guide the user to create a PR from their branch to the main repository
## Adding New Tools
When adding new API integrations or tools, follow this pattern. Here's an example of adding Spotify API integration:
### 1. Update Config Class
First, update the `config.py` file to include the new API key:
```python
class Config(BaseSettings):
figma_api_key: str | None = None
spotify_client_id: str | None = None
spotify_client_secret: str | None = None
cache_dir: str = (HOME / "cache").expanduser().resolve().absolute().as_posix()
```
### 2. Create Module Structure
Create a new module for the integration:
```bash
mkdir -p mcp_toolbox/spotify
touch mcp_toolbox/spotify/__init__.py
touch mcp_toolbox/spotify/tools.py
```
### 3. Implement API Client and Tools
In `mcp_toolbox/spotify/tools.py`:
```python
import json
from typing import Any, List, Dict, Optional
import httpx
from pydantic import BaseModel
from mcp_toolbox.app import mcp
from mcp_toolbox.config import Config
class SpotifyApiClient:
BASE_URL = "https://api.spotify.com/v1"
def __init__(self):
self.config = Config()
self.access_token = None
async def get_access_token(self) -> str:
"""Get or refresh the Spotify access token."""
if not self.config.spotify_client_id or not self.config.spotify_client_secret:
raise ValueError(
"Spotify credentials not provided. Set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables."
)
auth_url = "https://accounts.spotify.com/api/token"
async with httpx.AsyncClient() as client:
response = await client.post(
auth_url,
data={"grant_type": "client_credentials"},
auth=(self.config.spotify_client_id, self.config.spotify_client_secret),
)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
return self.access_token
async def make_request(self, path: str, method: str = "GET", params: Dict[str, Any] = None) -> Dict[str, Any]:
"""Make a request to the Spotify API."""
token = await self.get_access_token()
async with httpx.AsyncClient() as client:
headers = {"Authorization": f"Bearer {token}"}
url = f"{self.BASE_URL}{path}"
try:
if method == "GET":
response = await client.get(url, headers=headers, params=params)
else:
raise ValueError(f"Unsupported HTTP method: {method}")
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
spotify_error = e.response.json() if e.response.content else {"status": e.response.status_code, "error": str(e)}
raise ValueError(f"Spotify API error: {spotify_error}") from e
except httpx.RequestError as e:
raise ValueError(f"Request error: {e!s}") from e
# Initialize API client
api_client = SpotifyApiClient()
# Tool implementations
@mcp.tool(
description="Search for tracks on Spotify. Args: query (required, The search query), limit (optional, Maximum number of results to return)"
)
async def spotify_search_tracks(query: str, limit: int = 10) -> Dict[str, Any]:
"""Search for tracks on Spotify.
Args:
query: The search query
limit: Maximum number of results to return (default: 10)
"""
params = {"q": query, "type": "track", "limit": limit}
return await api_client.make_request("/search", params=params)
@mcp.tool(
description="Get details about a specific track. Args: track_id (required, The Spotify ID of the track)"
)
async def spotify_get_track(track_id: str) -> Dict[str, Any]:
"""Get details about a specific track.
Args:
track_id: The Spotify ID of the track
"""
return await api_client.make_request(f"/tracks/{track_id}")
@mcp.tool(
description="Get an artist's top tracks. Args: artist_id (required, The Spotify ID of the artist), market (optional, An ISO 3166-1 alpha-2 country code)"
)
async def spotify_get_artist_top_tracks(artist_id: str, market: str = "US") -> Dict[str, Any]:
"""Get an artist's top tracks.
Args:
artist_id: The Spotify ID of the artist
market: An ISO 3166-1 alpha-2 country code (default: US)
"""
params = {"market": market}
return await api_client.make_request(f"/artists/{artist_id}/top-tracks", params=params)
```
### 4. Create Tests
Create test files for your new tools:
```bash
mkdir -p tests/spotify
touch tests/spotify/test_tools.py
mkdir -p tests/mock/spotify
```
### 5. Update README
Always update the README.md when adding new environment variables or tools:
```markdown
## Environment Variables
The following environment variables can be configured:
- `FIGMA_API_KEY`: API key for Figma integration
- `SPOTIFY_CLIENT_ID`: Client ID for Spotify API
- `SPOTIFY_CLIENT_SECRET`: Client Secret for Spotify API
```
## Error Handling Best Practices
When implementing tools, follow these error handling best practices:
1. **Graceful Degradation**: If one API key is missing, other tools should still work
```python
async def get_access_token(self) -> str:
if not self.config.spotify_client_id or not self.config.spotify_client_secret:
raise ValueError(
"Spotify credentials not provided. Set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables."
)
# Rest of the method...
```
2. **Descriptive Error Messages**: Provide clear error messages that help users understand what went wrong
```python
except httpx.HTTPStatusError as e:
spotify_error = e.response.json() if e.response.content else {"status": e.response.status_code, "error": str(e)}
raise ValueError(f"Spotify API error: {spotify_error}") from e
```
3. **Proper Exception Handling**: Catch specific exceptions and handle them appropriately
4. **Fallbacks**: Implement fallback mechanisms when possible
## Testing
Always write tests for new functionality:
```python
import json
from pathlib import Path
from unittest.mock import patch
import pytest
from mcp_toolbox.spotify.tools import (
SpotifyApiClient,
spotify_search_tracks,
spotify_get_track,
spotify_get_artist_top_tracks,
)
# Helper function to load mock data
def load_mock_data(filename):
mock_dir = Path(__file__).parent.parent / "mock" / "spotify"
file_path = mock_dir / filename
if not file_path.exists():
# Create empty mock data if it doesn't exist
mock_data = {"mock": "data"}
with open(file_path, "w") as f:
json.dump(mock_data, f)
with open(file_path) as f:
return json.load(f)
# Patch the SpotifyApiClient.make_request method
@pytest.fixture
def mock_make_request():
with patch.object(SpotifyApiClient, "make_request") as mock:
def side_effect(path, method="GET", params=None):
if path == "/search":
return load_mock_data("search_tracks.json")
elif path.startswith("/tracks/"):
return load_mock_data("get_track.json")
elif path.endswith("/top-tracks"):
return load_mock_data("get_artist_top_tracks.json")
return {"mock": "data"}
mock.side_effect = side_effect
yield mock
# Test spotify_search_tracks function
@pytest.mark.asyncio
async def test_spotify_search_tracks(mock_make_request):
result = await spotify_search_tracks("test query")
mock_make_request.assert_called_once()
assert mock_make_request.call_args[0][0] == "/search"
# Test spotify_get_track function
@pytest.mark.asyncio
async def test_spotify_get_track(mock_make_request):
result = await spotify_get_track("track_id")
mock_make_request.assert_called_once()
assert mock_make_request.call_args[0][0] == "/tracks/track_id"
# Test spotify_get_artist_top_tracks function
@pytest.mark.asyncio
async def test_spotify_get_artist_top_tracks(mock_make_request):
result = await spotify_get_artist_top_tracks("artist_id")
mock_make_request.assert_called_once()
assert mock_make_request.call_args[0][0] == "/artists/artist_id/top-tracks"
```
## Documentation
When adding new tools, make sure to:
1. Add clear docstrings to all functions and classes
2. Include detailed argument descriptions in the `@mcp.tool` decorator
3. Add type hints to all functions and methods
4. Update the README.md with new environment variables and tools
## Final Checklist
Before submitting your changes:
1. ✅ Run `make check` to ensure code quality
2. ✅ Run `make test` to ensure all tests pass
3. ✅ Update documentation if needed
4. ✅ Add new environment variables to README.md
5. ✅ Follow proper Git workflow (branch, commit, push)