duckduckgo-mcp-server 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,171 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # PyPI configuration file
171
+ .pypirc
@@ -0,0 +1 @@
1
+ 3.13.2
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nick Clyde
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: duckduckgo-mcp-server
3
+ Version: 0.1.0
4
+ Summary: MCP Server for searching via DuckDuckGo
5
+ Project-URL: Homepage, https://github.com/nickclyde/duckduckgo-mcp-server
6
+ Project-URL: Issues, https://github.com/nickclyde/duckduckgo-mcp-server/issues
7
+ Author-email: Nick Clyde <nick@clyde.tech>
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: beautifulsoup4>=4.13.3
19
+ Requires-Dist: httpx>=0.28.1
20
+ Requires-Dist: mcp[cli]>=1.3.0
21
+ Description-Content-Type: text/markdown
22
+
23
+ # DuckDuckGo Search MCP Server
24
+
25
+ A Model Context Protocol (MCP) server that provides web search capabilities through DuckDuckGo, with additional features for content fetching and parsing.
26
+
27
+ ## Features
28
+
29
+ - **Web Search**: Search DuckDuckGo with advanced rate limiting and result formatting
30
+ - **Content Fetching**: Retrieve and parse webpage content with intelligent text extraction
31
+ - **Rate Limiting**: Built-in protection against rate limits for both search and content fetching
32
+ - **Error Handling**: Comprehensive error handling and logging
33
+ - **LLM-Friendly Output**: Results formatted specifically for large language model consumption
34
+
35
+ ## Installation
36
+
37
+ This server requires Python 3.10 or higher. Install dependencies using `uv` (recommended) or `pip`:
38
+
39
+ ```bash
40
+ # Using uv (recommended)
41
+ uv add "mcp[cli]" httpx beautifulsoup4
42
+
43
+ # Using pip
44
+ pip install "mcp[cli]" httpx beautifulsoup4
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ### Running with Claude Desktop
50
+
51
+ 1. Download [Claude Desktop](https://claude.ai/download)
52
+ 2. Create or edit `claude_desktop_config.json`:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "ddg-search": {
58
+ "command": "python",
59
+ "args": ["path/to/ddg_search_server.py"]
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ 3. Restart Claude Desktop
66
+
67
+ ### Direct Usage
68
+
69
+ Run the server directly:
70
+
71
+ ```bash
72
+ python ddg_search_server.py
73
+ ```
74
+
75
+ Or use the MCP CLI for development:
76
+
77
+ ```bash
78
+ mcp dev ddg_search_server.py
79
+ ```
80
+
81
+ ## Available Tools
82
+
83
+ ### 1. Search Tool
84
+
85
+ ```python
86
+ async def search(query: str, max_results: int = 10) -> str
87
+ ```
88
+
89
+ Performs a web search on DuckDuckGo and returns formatted results.
90
+
91
+ **Parameters:**
92
+ - `query`: Search query string
93
+ - `max_results`: Maximum number of results to return (default: 10)
94
+
95
+ **Returns:**
96
+ Formatted string containing search results with titles, URLs, and snippets.
97
+
98
+ ### 2. Content Fetching Tool
99
+
100
+ ```python
101
+ async def fetch_content(url: str) -> str
102
+ ```
103
+
104
+ Fetches and parses content from a webpage.
105
+
106
+ **Parameters:**
107
+ - `url`: The webpage URL to fetch content from
108
+
109
+ **Returns:**
110
+ Cleaned and formatted text content from the webpage.
111
+
112
+ ## Features in Detail
113
+
114
+ ### Rate Limiting
115
+
116
+ - Search: Limited to 30 requests per minute
117
+ - Content Fetching: Limited to 20 requests per minute
118
+ - Automatic queue management and wait times
119
+
120
+ ### Result Processing
121
+
122
+ - Removes ads and irrelevant content
123
+ - Cleans up DuckDuckGo redirect URLs
124
+ - Formats results for optimal LLM consumption
125
+ - Truncates long content appropriately
126
+
127
+ ### Error Handling
128
+
129
+ - Comprehensive error catching and reporting
130
+ - Detailed logging through MCP context
131
+ - Graceful degradation on rate limits or timeouts
132
+
133
+ ## Contributing
134
+
135
+ Issues and pull requests are welcome! Some areas for potential improvement:
136
+
137
+ - Additional search parameters (region, language, etc.)
138
+ - Enhanced content parsing options
139
+ - Caching layer for frequently accessed content
140
+ - Additional rate limiting strategies
141
+
142
+ ## License
143
+
144
+ This project is licensed under the MIT License.
@@ -0,0 +1,122 @@
1
+ # DuckDuckGo Search MCP Server
2
+
3
+ A Model Context Protocol (MCP) server that provides web search capabilities through DuckDuckGo, with additional features for content fetching and parsing.
4
+
5
+ ## Features
6
+
7
+ - **Web Search**: Search DuckDuckGo with advanced rate limiting and result formatting
8
+ - **Content Fetching**: Retrieve and parse webpage content with intelligent text extraction
9
+ - **Rate Limiting**: Built-in protection against rate limits for both search and content fetching
10
+ - **Error Handling**: Comprehensive error handling and logging
11
+ - **LLM-Friendly Output**: Results formatted specifically for large language model consumption
12
+
13
+ ## Installation
14
+
15
+ This server requires Python 3.10 or higher. Install dependencies using `uv` (recommended) or `pip`:
16
+
17
+ ```bash
18
+ # Using uv (recommended)
19
+ uv add "mcp[cli]" httpx beautifulsoup4
20
+
21
+ # Using pip
22
+ pip install "mcp[cli]" httpx beautifulsoup4
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Running with Claude Desktop
28
+
29
+ 1. Download [Claude Desktop](https://claude.ai/download)
30
+ 2. Create or edit `claude_desktop_config.json`:
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "ddg-search": {
36
+ "command": "python",
37
+ "args": ["path/to/ddg_search_server.py"]
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ 3. Restart Claude Desktop
44
+
45
+ ### Direct Usage
46
+
47
+ Run the server directly:
48
+
49
+ ```bash
50
+ python ddg_search_server.py
51
+ ```
52
+
53
+ Or use the MCP CLI for development:
54
+
55
+ ```bash
56
+ mcp dev ddg_search_server.py
57
+ ```
58
+
59
+ ## Available Tools
60
+
61
+ ### 1. Search Tool
62
+
63
+ ```python
64
+ async def search(query: str, max_results: int = 10) -> str
65
+ ```
66
+
67
+ Performs a web search on DuckDuckGo and returns formatted results.
68
+
69
+ **Parameters:**
70
+ - `query`: Search query string
71
+ - `max_results`: Maximum number of results to return (default: 10)
72
+
73
+ **Returns:**
74
+ Formatted string containing search results with titles, URLs, and snippets.
75
+
76
+ ### 2. Content Fetching Tool
77
+
78
+ ```python
79
+ async def fetch_content(url: str) -> str
80
+ ```
81
+
82
+ Fetches and parses content from a webpage.
83
+
84
+ **Parameters:**
85
+ - `url`: The webpage URL to fetch content from
86
+
87
+ **Returns:**
88
+ Cleaned and formatted text content from the webpage.
89
+
90
+ ## Features in Detail
91
+
92
+ ### Rate Limiting
93
+
94
+ - Search: Limited to 30 requests per minute
95
+ - Content Fetching: Limited to 20 requests per minute
96
+ - Automatic queue management and wait times
97
+
98
+ ### Result Processing
99
+
100
+ - Removes ads and irrelevant content
101
+ - Cleans up DuckDuckGo redirect URLs
102
+ - Formats results for optimal LLM consumption
103
+ - Truncates long content appropriately
104
+
105
+ ### Error Handling
106
+
107
+ - Comprehensive error catching and reporting
108
+ - Detailed logging through MCP context
109
+ - Graceful degradation on rate limits or timeouts
110
+
111
+ ## Contributing
112
+
113
+ Issues and pull requests are welcome! Some areas for potential improvement:
114
+
115
+ - Additional search parameters (region, language, etc.)
116
+ - Enhanced content parsing options
117
+ - Caching layer for frequently accessed content
118
+ - Additional rate limiting strategies
119
+
120
+ ## License
121
+
122
+ This project is licensed under the MIT License.
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "duckduckgo-mcp-server"
3
+ version = "0.1.0"
4
+ description = "MCP Server for searching via DuckDuckGo"
5
+ readme = "README.md"
6
+ authors = [{ name = "Nick Clyde", email = "nick@clyde.tech" }]
7
+ requires-python = ">=3.10"
8
+ dependencies = ["beautifulsoup4>=4.13.3", "httpx>=0.28.1", "mcp[cli]>=1.3.0"]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "Intended Audience :: Developers",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/nickclyde/duckduckgo-mcp-server"
22
+ Issues = "https://github.com/nickclyde/duckduckgo-mcp-server/issues"
23
+
24
+ [project.scripts]
25
+ duckduckgo-mcp = "duckduckgo_mcp_server.server:main"
26
+
27
+ [build-system]
28
+ requires = ["hatchling"]
29
+ build-backend = "hatchling.build"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,249 @@
1
+ from mcp.server.fastmcp import FastMCP, Context
2
+ import httpx
3
+ from bs4 import BeautifulSoup
4
+ from typing import List, Dict, Optional, Any
5
+ from dataclasses import dataclass
6
+ import urllib.parse
7
+ import sys
8
+ import traceback
9
+ import asyncio
10
+ from datetime import datetime, timedelta
11
+ import time
12
+ import re
13
+
14
+
15
+ @dataclass
16
+ class SearchResult:
17
+ title: str
18
+ link: str
19
+ snippet: str
20
+ position: int
21
+
22
+
23
+ class RateLimiter:
24
+ def __init__(self, requests_per_minute: int = 30):
25
+ self.requests_per_minute = requests_per_minute
26
+ self.requests = []
27
+
28
+ async def acquire(self):
29
+ now = datetime.now()
30
+ # Remove requests older than 1 minute
31
+ self.requests = [
32
+ req for req in self.requests if now - req < timedelta(minutes=1)
33
+ ]
34
+
35
+ if len(self.requests) >= self.requests_per_minute:
36
+ # Wait until we can make another request
37
+ wait_time = 60 - (now - self.requests[0]).total_seconds()
38
+ if wait_time > 0:
39
+ await asyncio.sleep(wait_time)
40
+
41
+ self.requests.append(now)
42
+
43
+
44
+ class DuckDuckGoSearcher:
45
+ BASE_URL = "https://html.duckduckgo.com/html"
46
+ HEADERS = {
47
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
48
+ }
49
+
50
+ def __init__(self):
51
+ self.rate_limiter = RateLimiter()
52
+
53
+ def format_results_for_llm(self, results: List[SearchResult]) -> str:
54
+ """Format results in a natural language style that's easier for LLMs to process"""
55
+ if not results:
56
+ return "No results were found for your search query. This could be due to DuckDuckGo's bot detection or the query returned no matches. Please try rephrasing your search or try again in a few minutes."
57
+
58
+ output = []
59
+ output.append(f"Found {len(results)} search results:\n")
60
+
61
+ for result in results:
62
+ output.append(f"{result.position}. {result.title}")
63
+ output.append(f" URL: {result.link}")
64
+ output.append(f" Summary: {result.snippet}")
65
+ output.append("") # Empty line between results
66
+
67
+ return "\n".join(output)
68
+
69
+ async def search(
70
+ self, query: str, ctx: Context, max_results: int = 10
71
+ ) -> List[SearchResult]:
72
+ try:
73
+ # Apply rate limiting
74
+ await self.rate_limiter.acquire()
75
+
76
+ # Create form data for POST request
77
+ data = {
78
+ "q": query,
79
+ "b": "",
80
+ "kl": "",
81
+ }
82
+
83
+ await ctx.info(f"Searching DuckDuckGo for: {query}")
84
+
85
+ async with httpx.AsyncClient() as client:
86
+ response = await client.post(
87
+ self.BASE_URL, data=data, headers=self.HEADERS, timeout=30.0
88
+ )
89
+ response.raise_for_status()
90
+
91
+ # Parse HTML response
92
+ soup = BeautifulSoup(response.text, "html.parser")
93
+ if not soup:
94
+ await ctx.error("Failed to parse HTML response")
95
+ return []
96
+
97
+ results = []
98
+ for result in soup.select(".result"):
99
+ title_elem = result.select_one(".result__title")
100
+ if not title_elem:
101
+ continue
102
+
103
+ link_elem = title_elem.find("a")
104
+ if not link_elem:
105
+ continue
106
+
107
+ title = link_elem.get_text(strip=True)
108
+ link = link_elem.get("href", "")
109
+
110
+ # Skip ad results
111
+ if "y.js" in link:
112
+ continue
113
+
114
+ # Clean up DuckDuckGo redirect URLs
115
+ if link.startswith("//duckduckgo.com/l/?uddg="):
116
+ link = urllib.parse.unquote(link.split("uddg=")[1].split("&")[0])
117
+
118
+ snippet_elem = result.select_one(".result__snippet")
119
+ snippet = snippet_elem.get_text(strip=True) if snippet_elem else ""
120
+
121
+ results.append(
122
+ SearchResult(
123
+ title=title,
124
+ link=link,
125
+ snippet=snippet,
126
+ position=len(results) + 1,
127
+ )
128
+ )
129
+
130
+ if len(results) >= max_results:
131
+ break
132
+
133
+ await ctx.info(f"Successfully found {len(results)} results")
134
+ return results
135
+
136
+ except httpx.TimeoutError:
137
+ await ctx.error("Search request timed out")
138
+ return []
139
+ except httpx.HTTPError as e:
140
+ await ctx.error(f"HTTP error occurred: {str(e)}")
141
+ return []
142
+ except Exception as e:
143
+ await ctx.error(f"Unexpected error during search: {str(e)}")
144
+ traceback.print_exc(file=sys.stderr)
145
+ return []
146
+
147
+
148
+ class WebContentFetcher:
149
+ def __init__(self):
150
+ self.rate_limiter = RateLimiter(requests_per_minute=20)
151
+
152
+ async def fetch_and_parse(self, url: str, ctx: Context) -> str:
153
+ """Fetch and parse content from a webpage"""
154
+ try:
155
+ await self.rate_limiter.acquire()
156
+
157
+ await ctx.info(f"Fetching content from: {url}")
158
+
159
+ async with httpx.AsyncClient() as client:
160
+ response = await client.get(
161
+ url,
162
+ headers={
163
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
164
+ },
165
+ follow_redirects=True,
166
+ timeout=30.0,
167
+ )
168
+ response.raise_for_status()
169
+
170
+ # Parse the HTML
171
+ soup = BeautifulSoup(response.text, "html.parser")
172
+
173
+ # Remove script and style elements
174
+ for element in soup(["script", "style", "nav", "header", "footer"]):
175
+ element.decompose()
176
+
177
+ # Get the text content
178
+ text = soup.get_text()
179
+
180
+ # Clean up the text
181
+ lines = (line.strip() for line in text.splitlines())
182
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
183
+ text = " ".join(chunk for chunk in chunks if chunk)
184
+
185
+ # Remove extra whitespace
186
+ text = re.sub(r"\s+", " ", text).strip()
187
+
188
+ # Truncate if too long
189
+ if len(text) > 8000:
190
+ text = text[:8000] + "... [content truncated]"
191
+
192
+ await ctx.info(
193
+ f"Successfully fetched and parsed content ({len(text)} characters)"
194
+ )
195
+ return text
196
+
197
+ except httpx.TimeoutError:
198
+ await ctx.error(f"Request timed out for URL: {url}")
199
+ return "Error: The request timed out while trying to fetch the webpage."
200
+ except httpx.HTTPError as e:
201
+ await ctx.error(f"HTTP error occurred while fetching {url}: {str(e)}")
202
+ return f"Error: Could not access the webpage ({str(e)})"
203
+ except Exception as e:
204
+ await ctx.error(f"Error fetching content from {url}: {str(e)}")
205
+ return f"Error: An unexpected error occurred while fetching the webpage ({str(e)})"
206
+
207
+
208
+ # Initialize FastMCP server
209
+ mcp = FastMCP("ddg-search")
210
+ searcher = DuckDuckGoSearcher()
211
+ fetcher = WebContentFetcher()
212
+
213
+
214
+ @mcp.tool()
215
+ async def search(query: str, ctx: Context, max_results: int = 10) -> str:
216
+ """
217
+ Search DuckDuckGo and return formatted results.
218
+
219
+ Args:
220
+ query: The search query string
221
+ max_results: Maximum number of results to return (default: 10)
222
+ ctx: MCP context for logging
223
+ """
224
+ try:
225
+ results = await searcher.search(query, ctx, max_results)
226
+ return searcher.format_results_for_llm(results)
227
+ except Exception as e:
228
+ traceback.print_exc(file=sys.stderr)
229
+ return f"An error occurred while searching: {str(e)}"
230
+
231
+
232
+ @mcp.tool()
233
+ async def fetch_content(url: str, ctx: Context) -> str:
234
+ """
235
+ Fetch and parse content from a webpage URL.
236
+
237
+ Args:
238
+ url: The webpage URL to fetch content from
239
+ ctx: MCP context for logging
240
+ """
241
+ return await fetcher.fetch_and_parse(url, ctx)
242
+
243
+
244
+ def main():
245
+ mcp.run()
246
+
247
+
248
+ if __name__ == "__main__":
249
+ main()
@@ -0,0 +1,421 @@
1
+ version = 1
2
+ revision = 1
3
+ requires-python = ">=3.10"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.7.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "anyio"
16
+ version = "4.8.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
20
+ { name = "idna" },
21
+ { name = "sniffio" },
22
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
23
+ ]
24
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 }
25
+ wheels = [
26
+ { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 },
27
+ ]
28
+
29
+ [[package]]
30
+ name = "beautifulsoup4"
31
+ version = "4.13.3"
32
+ source = { registry = "https://pypi.org/simple" }
33
+ dependencies = [
34
+ { name = "soupsieve" },
35
+ { name = "typing-extensions" },
36
+ ]
37
+ sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 }
38
+ wheels = [
39
+ { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 },
40
+ ]
41
+
42
+ [[package]]
43
+ name = "certifi"
44
+ version = "2025.1.31"
45
+ source = { registry = "https://pypi.org/simple" }
46
+ sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
47
+ wheels = [
48
+ { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
49
+ ]
50
+
51
+ [[package]]
52
+ name = "click"
53
+ version = "8.1.8"
54
+ source = { registry = "https://pypi.org/simple" }
55
+ dependencies = [
56
+ { name = "colorama", marker = "sys_platform == 'win32'" },
57
+ ]
58
+ sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
59
+ wheels = [
60
+ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
61
+ ]
62
+
63
+ [[package]]
64
+ name = "colorama"
65
+ version = "0.4.6"
66
+ source = { registry = "https://pypi.org/simple" }
67
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
68
+ wheels = [
69
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
70
+ ]
71
+
72
+ [[package]]
73
+ name = "duckduckgo-mcp-server"
74
+ version = "0.1.0"
75
+ source = { editable = "." }
76
+ dependencies = [
77
+ { name = "beautifulsoup4" },
78
+ { name = "httpx" },
79
+ { name = "mcp", extra = ["cli"] },
80
+ ]
81
+
82
+ [package.metadata]
83
+ requires-dist = [
84
+ { name = "beautifulsoup4", specifier = ">=4.13.3" },
85
+ { name = "httpx", specifier = ">=0.28.1" },
86
+ { name = "mcp", extras = ["cli"], specifier = ">=1.3.0" },
87
+ ]
88
+
89
+ [[package]]
90
+ name = "exceptiongroup"
91
+ version = "1.2.2"
92
+ source = { registry = "https://pypi.org/simple" }
93
+ sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
94
+ wheels = [
95
+ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
96
+ ]
97
+
98
+ [[package]]
99
+ name = "h11"
100
+ version = "0.14.0"
101
+ source = { registry = "https://pypi.org/simple" }
102
+ sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
103
+ wheels = [
104
+ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
105
+ ]
106
+
107
+ [[package]]
108
+ name = "httpcore"
109
+ version = "1.0.7"
110
+ source = { registry = "https://pypi.org/simple" }
111
+ dependencies = [
112
+ { name = "certifi" },
113
+ { name = "h11" },
114
+ ]
115
+ sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
116
+ wheels = [
117
+ { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
118
+ ]
119
+
120
+ [[package]]
121
+ name = "httpx"
122
+ version = "0.28.1"
123
+ source = { registry = "https://pypi.org/simple" }
124
+ dependencies = [
125
+ { name = "anyio" },
126
+ { name = "certifi" },
127
+ { name = "httpcore" },
128
+ { name = "idna" },
129
+ ]
130
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
131
+ wheels = [
132
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
133
+ ]
134
+
135
+ [[package]]
136
+ name = "httpx-sse"
137
+ version = "0.4.0"
138
+ source = { registry = "https://pypi.org/simple" }
139
+ sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
140
+ wheels = [
141
+ { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
142
+ ]
143
+
144
+ [[package]]
145
+ name = "idna"
146
+ version = "3.10"
147
+ source = { registry = "https://pypi.org/simple" }
148
+ sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
149
+ wheels = [
150
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
151
+ ]
152
+
153
+ [[package]]
154
+ name = "markdown-it-py"
155
+ version = "3.0.0"
156
+ source = { registry = "https://pypi.org/simple" }
157
+ dependencies = [
158
+ { name = "mdurl" },
159
+ ]
160
+ sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
161
+ wheels = [
162
+ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
163
+ ]
164
+
165
+ [[package]]
166
+ name = "mcp"
167
+ version = "1.3.0"
168
+ source = { registry = "https://pypi.org/simple" }
169
+ dependencies = [
170
+ { name = "anyio" },
171
+ { name = "httpx" },
172
+ { name = "httpx-sse" },
173
+ { name = "pydantic" },
174
+ { name = "pydantic-settings" },
175
+ { name = "sse-starlette" },
176
+ { name = "starlette" },
177
+ { name = "uvicorn" },
178
+ ]
179
+ sdist = { url = "https://files.pythonhosted.org/packages/6b/b6/81e5f2490290351fc97bf46c24ff935128cb7d34d68e3987b522f26f7ada/mcp-1.3.0.tar.gz", hash = "sha256:f409ae4482ce9d53e7ac03f3f7808bcab735bdfc0fba937453782efb43882d45", size = 150235 }
180
+ wheels = [
181
+ { url = "https://files.pythonhosted.org/packages/d0/d2/a9e87b506b2094f5aa9becc1af5178842701b27217fa43877353da2577e3/mcp-1.3.0-py3-none-any.whl", hash = "sha256:2829d67ce339a249f803f22eba5e90385eafcac45c94b00cab6cef7e8f217211", size = 70672 },
182
+ ]
183
+
184
+ [package.optional-dependencies]
185
+ cli = [
186
+ { name = "python-dotenv" },
187
+ { name = "typer" },
188
+ ]
189
+
190
+ [[package]]
191
+ name = "mdurl"
192
+ version = "0.1.2"
193
+ source = { registry = "https://pypi.org/simple" }
194
+ sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
195
+ wheels = [
196
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
197
+ ]
198
+
199
+ [[package]]
200
+ name = "pydantic"
201
+ version = "2.10.6"
202
+ source = { registry = "https://pypi.org/simple" }
203
+ dependencies = [
204
+ { name = "annotated-types" },
205
+ { name = "pydantic-core" },
206
+ { name = "typing-extensions" },
207
+ ]
208
+ sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 }
209
+ wheels = [
210
+ { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
211
+ ]
212
+
213
+ [[package]]
214
+ name = "pydantic-core"
215
+ version = "2.27.2"
216
+ source = { registry = "https://pypi.org/simple" }
217
+ dependencies = [
218
+ { name = "typing-extensions" },
219
+ ]
220
+ sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
221
+ wheels = [
222
+ { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 },
223
+ { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 },
224
+ { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 },
225
+ { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 },
226
+ { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 },
227
+ { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 },
228
+ { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 },
229
+ { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 },
230
+ { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 },
231
+ { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 },
232
+ { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 },
233
+ { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 },
234
+ { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 },
235
+ { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 },
236
+ { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 },
237
+ { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 },
238
+ { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 },
239
+ { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 },
240
+ { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 },
241
+ { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 },
242
+ { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 },
243
+ { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 },
244
+ { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 },
245
+ { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 },
246
+ { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 },
247
+ { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 },
248
+ { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 },
249
+ { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 },
250
+ { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 },
251
+ { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 },
252
+ { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 },
253
+ { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 },
254
+ { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 },
255
+ { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 },
256
+ { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 },
257
+ { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 },
258
+ { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 },
259
+ { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 },
260
+ { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 },
261
+ { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 },
262
+ { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 },
263
+ { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 },
264
+ { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 },
265
+ { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 },
266
+ { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 },
267
+ { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 },
268
+ { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 },
269
+ { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 },
270
+ { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 },
271
+ { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 },
272
+ { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 },
273
+ { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 },
274
+ { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 },
275
+ { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 },
276
+ { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 },
277
+ { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 },
278
+ { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 },
279
+ { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 },
280
+ { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 },
281
+ { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 },
282
+ { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 },
283
+ { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 },
284
+ { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 },
285
+ { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 },
286
+ ]
287
+
288
+ [[package]]
289
+ name = "pydantic-settings"
290
+ version = "2.8.0"
291
+ source = { registry = "https://pypi.org/simple" }
292
+ dependencies = [
293
+ { name = "pydantic" },
294
+ { name = "python-dotenv" },
295
+ ]
296
+ sdist = { url = "https://files.pythonhosted.org/packages/ca/a2/ad2511ede77bb424f3939e5148a56d968cdc6b1462620d24b2a1f4ab65b4/pydantic_settings-2.8.0.tar.gz", hash = "sha256:88e2ca28f6e68ea102c99c3c401d6c9078e68a5df600e97b43891c34e089500a", size = 83347 }
297
+ wheels = [
298
+ { url = "https://files.pythonhosted.org/packages/c1/a9/3b9642025174bbe67e900785fb99c9bfe91ea584b0b7126ff99945c24a0e/pydantic_settings-2.8.0-py3-none-any.whl", hash = "sha256:c782c7dc3fb40e97b238e713c25d26f64314aece2e91abcff592fcac15f71820", size = 30746 },
299
+ ]
300
+
301
+ [[package]]
302
+ name = "pygments"
303
+ version = "2.19.1"
304
+ source = { registry = "https://pypi.org/simple" }
305
+ sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
306
+ wheels = [
307
+ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
308
+ ]
309
+
310
+ [[package]]
311
+ name = "python-dotenv"
312
+ version = "1.0.1"
313
+ source = { registry = "https://pypi.org/simple" }
314
+ sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
315
+ wheels = [
316
+ { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
317
+ ]
318
+
319
+ [[package]]
320
+ name = "rich"
321
+ version = "13.9.4"
322
+ source = { registry = "https://pypi.org/simple" }
323
+ dependencies = [
324
+ { name = "markdown-it-py" },
325
+ { name = "pygments" },
326
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
327
+ ]
328
+ sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
329
+ wheels = [
330
+ { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
331
+ ]
332
+
333
+ [[package]]
334
+ name = "shellingham"
335
+ version = "1.5.4"
336
+ source = { registry = "https://pypi.org/simple" }
337
+ sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
338
+ wheels = [
339
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
340
+ ]
341
+
342
+ [[package]]
343
+ name = "sniffio"
344
+ version = "1.3.1"
345
+ source = { registry = "https://pypi.org/simple" }
346
+ sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
347
+ wheels = [
348
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
349
+ ]
350
+
351
+ [[package]]
352
+ name = "soupsieve"
353
+ version = "2.6"
354
+ source = { registry = "https://pypi.org/simple" }
355
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 }
356
+ wheels = [
357
+ { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 },
358
+ ]
359
+
360
+ [[package]]
361
+ name = "sse-starlette"
362
+ version = "2.2.1"
363
+ source = { registry = "https://pypi.org/simple" }
364
+ dependencies = [
365
+ { name = "anyio" },
366
+ { name = "starlette" },
367
+ ]
368
+ sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 }
369
+ wheels = [
370
+ { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 },
371
+ ]
372
+
373
+ [[package]]
374
+ name = "starlette"
375
+ version = "0.45.3"
376
+ source = { registry = "https://pypi.org/simple" }
377
+ dependencies = [
378
+ { name = "anyio" },
379
+ ]
380
+ sdist = { url = "https://files.pythonhosted.org/packages/ff/fb/2984a686808b89a6781526129a4b51266f678b2d2b97ab2d325e56116df8/starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f", size = 2574076 }
381
+ wheels = [
382
+ { url = "https://files.pythonhosted.org/packages/d9/61/f2b52e107b1fc8944b33ef56bf6ac4ebbe16d91b94d2b87ce013bf63fb84/starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d", size = 71507 },
383
+ ]
384
+
385
+ [[package]]
386
+ name = "typer"
387
+ version = "0.15.1"
388
+ source = { registry = "https://pypi.org/simple" }
389
+ dependencies = [
390
+ { name = "click" },
391
+ { name = "rich" },
392
+ { name = "shellingham" },
393
+ { name = "typing-extensions" },
394
+ ]
395
+ sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789 }
396
+ wheels = [
397
+ { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 },
398
+ ]
399
+
400
+ [[package]]
401
+ name = "typing-extensions"
402
+ version = "4.12.2"
403
+ source = { registry = "https://pypi.org/simple" }
404
+ sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
405
+ wheels = [
406
+ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
407
+ ]
408
+
409
+ [[package]]
410
+ name = "uvicorn"
411
+ version = "0.34.0"
412
+ source = { registry = "https://pypi.org/simple" }
413
+ dependencies = [
414
+ { name = "click" },
415
+ { name = "h11" },
416
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
417
+ ]
418
+ sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 }
419
+ wheels = [
420
+ { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 },
421
+ ]