├── .env.example ├── .gitignore ├── .python-version ├── .vscode └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── _config.yml ├── database └── .gitkeep ├── pyproject.toml ├── smithery.yaml ├── src └── mcp_pinecone │ ├── __init__.py │ ├── chunking.py │ ├── constants.py │ ├── pinecone.py │ ├── prompts.py │ ├── server.py │ ├── tools.py │ └── utils.py └── uv.lock /.env.example: -------------------------------------------------------------------------------- 1 | PINECONE_API_KEY= 2 | PINECONE_INDEX_NAME= 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Mac OS 10 | .DS_Store 11 | 12 | # Virtual environments 13 | .venv 14 | 15 | # Apple Notes database for development 16 | database/* 17 | !database/.gitkeep 18 | 19 | # Environment variables 20 | .env 21 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "charliermarsh.ruff" 5 | } 6 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the MCP-Pinecone project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.1.8] - 2025-01-04 9 | ### Added 10 | - Added `pinecone-store` prompt to store documents in Pinecone 11 | - Added `pinecone-stats` tool to get stats about the Pinecone index 12 | ### Changed 13 | - Refactoring across the codebase to make it more modular and easier to extend 14 | - Removed `embed-document` tool 15 | - Removed `chunk-document` tool 16 | 17 | ## [0.1.7] - 2025-01-01 18 | ### Added 19 | - Updated prompt to `pinecone-query` because Zed can't use tools. 20 | 21 | ## [0.1.6] - 2024-12-31 22 | ### Added 23 | - Added `chunk_enabled` argument to `process-document` tool to enable/disable chunking. Defaults to false. 24 | - Added `list-documents` tool to list all documents in a namespace 25 | 26 | ## [0.1.5] - 2024-12-29 27 | ### Added 28 | - Added `process-document` tool to combine chunking, embedding, and upserting documents into Pinecone 29 | - Added `chunk-document` tool to explicitly chunk documents into chunks 30 | - Added `embed-document` tool to explicitly embed documents into Pinecone 31 | - Mention Pinecone api in README 32 | 33 | ## [0.1.4] - 2024-12-20 34 | ### Added 35 | - Added `langchain` dependency for chunking 36 | - Auto chunk documents by markdown headers 37 | 38 | ## [0.1.3] - 2024-12-20 39 | ### Added 40 | - Namespace support for all vector operations (search, read, upsert) 41 | - Explicit namespace parameter in tool schemas 42 | 43 | ### Changed 44 | - Updated MCP package to latest version 45 | 46 | ## [0.1.0 - 0.1.2] 47 | ### Added 48 | - Initial public release 49 | - Basic Pinecone integration with MCP 50 | - Semantic search capabilities 51 | - Document reading and writing 52 | - Metadata support -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Use a Python image with uv pre-installed 3 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv 4 | 5 | # Set the working directory 6 | WORKDIR /app 7 | 8 | # Copy the project files to the working directory 9 | ADD . /app 10 | 11 | # Enable bytecode compilation 12 | ENV UV_COMPILE_BYTECODE=1 13 | 14 | # Copy from the cache instead of linking since it's a mounted volume 15 | ENV UV_LINK_MODE=copy 16 | 17 | # Sync the dependencies and lockfile 18 | RUN --mount=type=cache,target=/root/.cache/uv --mount=type=bind,source=uv.lock,target=uv.lock --mount=type=bind,source=pyproject.toml,target=pyproject.toml uv sync --frozen --no-install-project --no-dev --no-editable 19 | 20 | # Install the project 21 | RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev --no-editable 22 | 23 | FROM python:3.12-slim-bookworm 24 | 25 | WORKDIR /app 26 | 27 | COPY --from=uv /root/.local /root/.local 28 | COPY --from=uv --chown=app:app /app/.venv /app/.venv 29 | 30 | # Place executables in the environment at the front of the path 31 | ENV PATH="/app/.venv/bin:$PATH" 32 | 33 | # Entry point for running the MCP server 34 | ENTRYPOINT ["uv", "run", "mcp-pinecone"] 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Navishkar Rao 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. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include .env 2 | 3 | # Variables 4 | PACKAGE_NAME := mcp-pinecone 5 | 6 | # Colors for better visibility 7 | CYAN := \033[36m 8 | GREEN := \033[32m 9 | RED := \033[31m 10 | RESET := \033[0m 11 | 12 | # Default make command 13 | all: help 14 | 15 | ## reinstall-deps: Reinstall dependencies with uv 16 | reinstall-deps: 17 | uv sync --reinstall 18 | 19 | ## lint: Lint the code 20 | lint: 21 | uv run ruff check . 22 | 23 | ## build: Build the package 24 | build: 25 | uv build 26 | 27 | ## lock-upgrade: Lock dependencies to the latest version 28 | lock-upgrade: 29 | uv lock --upgrade 30 | 31 | ## publish: Publish the package to PyPI 32 | publish: 33 | @if [ -z "$(PYPI_TOKEN)" ]; then \ 34 | echo "$(RED)Error: PYPI_TOKEN is not set$(RESET)"; \ 35 | exit 1; \ 36 | fi 37 | uv publish --username __token__ --password ${PYPI_TOKEN} 38 | 39 | ## release: Create and push a new release tag 40 | release: 41 | @if [ -z "$(VERSION)" ]; then \ 42 | echo "$(RED)Error: VERSION is required. Use 'make release VERSION=x.x.x'$(RESET)"; \ 43 | exit 1; \ 44 | fi 45 | @echo "$(GREEN)Creating release v$(VERSION)...$(RESET)" 46 | git tag -a v$(VERSION) -m "Release v$(VERSION)" 47 | git push origin v$(VERSION) 48 | @echo "\nRelease v$(VERSION) created!" 49 | @echo "Users can install with:" 50 | @echo " uvx install github:sirmews/$(PACKAGE_NAME)@v$(VERSION)" 51 | @echo " uv pip install git+https://github.com/sirmews/$(PACKAGE_NAME).git@v$(VERSION)" 52 | 53 | ## inspect-local-server: Inspect the local MCP server 54 | inspect-local-server: 55 | npx @modelcontextprotocol/inspector uv --directory . run $(PACKAGE_NAME) 56 | 57 | ## help: Show a list of commands 58 | help : Makefile 59 | @echo "Usage:" 60 | @echo " make $(CYAN)$(RESET)" 61 | @echo "" 62 | @echo "Targets:" 63 | @awk '/^[a-zA-Z\-_0-9%:\\]+/ { \ 64 | helpMessage = match(lastLine, /^## (.*)/); \ 65 | if (helpMessage) { \ 66 | helpCommand = $$1; \ 67 | helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ 68 | gsub("\\\\", "", helpCommand); \ 69 | gsub(":+$$", "", helpCommand); \ 70 | printf " $(CYAN)%-20s$(RESET) %s\n", helpCommand, helpMessage; \ 71 | } \ 72 | } \ 73 | { lastLine = $$0 }' $(MAKEFILE_LIST) 74 | 75 | 76 | .PHONY: all help -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pinecone Model Context Protocol Server for Claude Desktop. 2 | 3 | [![smithery badge](https://smithery.ai/badge/mcp-pinecone)](https://smithery.ai/server/mcp-pinecone) 4 | 5 | [![PyPI - Downloads](https://img.shields.io/pypi/dd/mcp-pinecone?style=flat)](https://pypi.org/project/mcp-pinecone/) 6 | 7 | Read and write to a Pinecone index. 8 | 9 | 10 | ## Components 11 | 12 | ```mermaid 13 | flowchart TB 14 | subgraph Client["MCP Client (e.g., Claude Desktop)"] 15 | UI[User Interface] 16 | end 17 | 18 | subgraph MCPServer["MCP Server (pinecone-mcp)"] 19 | Server[Server Class] 20 | 21 | subgraph Handlers["Request Handlers"] 22 | ListRes[list_resources] 23 | ReadRes[read_resource] 24 | ListTools[list_tools] 25 | CallTool[call_tool] 26 | GetPrompt[get_prompt] 27 | ListPrompts[list_prompts] 28 | end 29 | 30 | subgraph Tools["Implemented Tools"] 31 | SemSearch[semantic-search] 32 | ReadDoc[read-document] 33 | ListDocs[list-documents] 34 | PineconeStats[pinecone-stats] 35 | ProcessDoc[process-document] 36 | end 37 | end 38 | 39 | subgraph PineconeService["Pinecone Service"] 40 | PC[Pinecone Client] 41 | subgraph PineconeFunctions["Pinecone Operations"] 42 | Search[search_records] 43 | Upsert[upsert_records] 44 | Fetch[fetch_records] 45 | List[list_records] 46 | Embed[generate_embeddings] 47 | end 48 | Index[(Pinecone Index)] 49 | end 50 | 51 | %% Connections 52 | UI --> Server 53 | Server --> Handlers 54 | 55 | ListTools --> Tools 56 | CallTool --> Tools 57 | 58 | Tools --> PC 59 | PC --> PineconeFunctions 60 | PineconeFunctions --> Index 61 | 62 | %% Data flow for semantic search 63 | SemSearch --> Search 64 | Search --> Embed 65 | Embed --> Index 66 | 67 | %% Data flow for document operations 68 | UpsertDoc --> Upsert 69 | ReadDoc --> Fetch 70 | ListRes --> List 71 | 72 | classDef primary fill:#2563eb,stroke:#1d4ed8,color:white 73 | classDef secondary fill:#4b5563,stroke:#374151,color:white 74 | classDef storage fill:#059669,stroke:#047857,color:white 75 | 76 | class Server,PC primary 77 | class Tools,Handlers secondary 78 | class Index storage 79 | ``` 80 | 81 | ### Resources 82 | 83 | The server implements the ability to read and write to a Pinecone index. 84 | 85 | ### Tools 86 | 87 | - `semantic-search`: Search for records in the Pinecone index. 88 | - `read-document`: Read a document from the Pinecone index. 89 | - `list-documents`: List all documents in the Pinecone index. 90 | - `pinecone-stats`: Get stats about the Pinecone index, including the number of records, dimensions, and namespaces. 91 | - `process-document`: Process a document into chunks and upsert them into the Pinecone index. This performs the overall steps of chunking, embedding, and upserting. 92 | 93 | Note: embeddings are generated via Pinecone's inference API and chunking is done with a token-based chunker. Written by copying a lot from langchain and debugging with Claude. 94 | ## Quickstart 95 | 96 | ### Installing via Smithery 97 | 98 | To install Pinecone MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-pinecone): 99 | 100 | ```bash 101 | npx -y @smithery/cli install mcp-pinecone --client claude 102 | ``` 103 | 104 | ### Install the server 105 | 106 | Recommend using [uv](https://docs.astral.sh/uv/getting-started/installation/) to install the server locally for Claude. 107 | 108 | ``` 109 | uvx install mcp-pinecone 110 | ``` 111 | OR 112 | ``` 113 | uv pip install mcp-pinecone 114 | ``` 115 | 116 | Add your config as described below. 117 | 118 | #### Claude Desktop 119 | 120 | On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json` 121 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json` 122 | 123 | Note: You might need to use the direct path to `uv`. Use `which uv` to find the path. 124 | 125 | 126 | __Development/Unpublished Servers Configuration__ 127 | 128 | ```json 129 | "mcpServers": { 130 | "mcp-pinecone": { 131 | "command": "uv", 132 | "args": [ 133 | "--directory", 134 | "{project_dir}", 135 | "run", 136 | "mcp-pinecone" 137 | ] 138 | } 139 | } 140 | ``` 141 | 142 | 143 | __Published Servers Configuration__ 144 | 145 | ```json 146 | "mcpServers": { 147 | "mcp-pinecone": { 148 | "command": "uvx", 149 | "args": [ 150 | "--index-name", 151 | "{your-index-name}", 152 | "--api-key", 153 | "{your-secret-api-key}", 154 | "mcp-pinecone" 155 | ] 156 | } 157 | } 158 | ``` 159 | 160 | #### Sign up to Pinecone 161 | 162 | You can sign up for a Pinecone account [here](https://www.pinecone.io/). 163 | 164 | #### Get an API key 165 | 166 | Create a new index in Pinecone, replacing `{your-index-name}` and get an API key from the Pinecone dashboard, replacing `{your-secret-api-key}` in the config. 167 | 168 | ## Development 169 | 170 | ### Building and Publishing 171 | 172 | To prepare the package for distribution: 173 | 174 | 1. Sync dependencies and update lockfile: 175 | ```bash 176 | uv sync 177 | ``` 178 | 179 | 2. Build package distributions: 180 | ```bash 181 | uv build 182 | ``` 183 | 184 | This will create source and wheel distributions in the `dist/` directory. 185 | 186 | 3. Publish to PyPI: 187 | ```bash 188 | uv publish 189 | ``` 190 | 191 | Note: You'll need to set PyPI credentials via environment variables or command flags: 192 | - Token: `--token` or `UV_PUBLISH_TOKEN` 193 | - Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD` 194 | 195 | ### Debugging 196 | 197 | Since MCP servers run over stdio, debugging can be challenging. For the best debugging 198 | experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). 199 | 200 | 201 | You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command: 202 | 203 | ```bash 204 | npx @modelcontextprotocol/inspector uv --directory {project_dir} run mcp-pinecone 205 | ``` 206 | 207 | 208 | Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging. 209 | 210 | ## License 211 | 212 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 213 | 214 | ## Source Code 215 | 216 | The source code is available on [GitHub](https://github.com/sirmews/mcp-pinecone). 217 | 218 | ## Contributing 219 | 220 | Send your ideas and feedback to me on [Bluesky](https://bsky.app/profile/perfectlycromulent.bsky.social) or by opening an issue. 221 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: pages-themes/hacker@v0.2.0 2 | plugins: 3 | - jekyll-remote-theme 4 | -------------------------------------------------------------------------------- /database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sirmews/mcp-pinecone/5477b421bace738621653ca659f2301ad03fa37a/database/.gitkeep -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mcp-pinecone" 3 | version = "0.1.8" 4 | description = "Read and write to Pinecone from Claude Desktop with Model Context Protocol." 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "httpx>=0.28.0", 9 | "jsonschema>=4.23.0", 10 | "mcp>=1.0.0", 11 | "pinecone>=5.4.1", 12 | "python-dotenv>=1.0.1", 13 | "tiktoken>=0.8.0", 14 | ] 15 | classifiers = [ 16 | "Programming Language :: Python :: 3", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: MacOS", 19 | ] 20 | [[project.authors]] 21 | name = "Navishkar Rao" 22 | email = "nav@perfectlycromulent.dev" 23 | 24 | [build-system] 25 | requires = [ "hatchling",] 26 | build-backend = "hatchling.build" 27 | 28 | [project.scripts] 29 | mcp-pinecone = "mcp_pinecone:main" 30 | 31 | [tool.mcp-pinecone] 32 | server_name = "mcp-pinecone" 33 | 34 | [project.urls] 35 | Homepage = "https://sirmews.github.io/mcp-pinecone/" 36 | Issues = "https://github.com/sirmews/mcp-pinecone/issues" 37 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - indexName 10 | - apiKey 11 | properties: 12 | indexName: 13 | type: string 14 | description: The name of the Pinecone index. 15 | apiKey: 16 | type: string 17 | description: The API key for accessing Pinecone. 18 | commandFunction: 19 | # A function that produces the CLI command to start the MCP on stdio. 20 | |- 21 | config => ({command: 'uv', args: ['run', 'mcp-pinecone', '--index-name', config.indexName, '--api-key', config.apiKey]}) 22 | -------------------------------------------------------------------------------- /src/mcp_pinecone/__init__.py: -------------------------------------------------------------------------------- 1 | from . import server 2 | import asyncio 3 | 4 | 5 | def main(): 6 | asyncio.run(server.main()) 7 | 8 | 9 | # Optionally expose other important items at package level 10 | __all__ = ["main", "server"] 11 | -------------------------------------------------------------------------------- /src/mcp_pinecone/chunking.py: -------------------------------------------------------------------------------- 1 | """ 2 | Smart document chunking with token awareness and recursive splitting. 3 | Provides configurable text splitting strategies optimized for LLM context windows. 4 | """ 5 | 6 | from typing import List, Dict, Any, Optional 7 | from pydantic import BaseModel, Field, model_validator 8 | import tiktoken 9 | import logging 10 | from abc import ABC, abstractmethod 11 | 12 | logger = logging.getLogger("smart_chunker") 13 | 14 | 15 | class ChunkingError(Exception): 16 | """Base exception for chunking errors""" 17 | 18 | pass 19 | 20 | 21 | class Chunk(BaseModel): 22 | """Represents a document chunk with metadata""" 23 | 24 | id: str 25 | content: str 26 | metadata: Dict[str, Any] 27 | 28 | def to_dict(self) -> dict: 29 | """Convert to dictionary format for embed-document""" 30 | return {"id": self.id, "content": self.content, "metadata": self.metadata} 31 | 32 | 33 | class ChunkingConfig(BaseModel): 34 | """Configuration for chunking behavior""" 35 | 36 | target_tokens: int = Field( 37 | default=512, 38 | description="Target chunk size in tokens", 39 | gt=0, # Must be positive 40 | ) 41 | max_tokens: int = Field( 42 | default=1000, 43 | description="Maximum allowed tokens per chunk", 44 | gt=0, 45 | ) 46 | overlap_tokens: int = Field( 47 | default=50, 48 | description="Number of tokens to overlap", 49 | ge=0, 50 | ) 51 | tokenizer_model: str = Field( 52 | default="cl100k_base", description="Tokenizer model to use" 53 | ) 54 | 55 | # Separators in priority order 56 | separators: List[str] = Field( 57 | default=[ 58 | "\n\n", # Paragraphs 59 | "\n", # Lines 60 | ". ", # Sentences 61 | "? ", # Questions 62 | "! ", # Exclamations 63 | ", ", # Clauses 64 | " ", # Words 65 | "", # Characters 66 | ], 67 | description="Separators in order of preference", 68 | ) 69 | 70 | @model_validator(mode="after") 71 | def validate_tokens(self): 72 | """Ensure overlap tokens are less than target tokens""" 73 | if self.overlap_tokens >= self.target_tokens: 74 | raise ValueError("overlap_tokens must be less than target_tokens") 75 | if self.max_tokens < self.target_tokens: 76 | raise ValueError( 77 | "max_tokens must be greater than or equal to target_tokens" 78 | ) 79 | return self 80 | 81 | 82 | class BaseChunker(ABC): 83 | """ 84 | Abstract base for all chunking strategies. 85 | We can add more chunking strategies here as we learn more approaches for certain document types. 86 | """ 87 | 88 | @abstractmethod 89 | def chunk_document( 90 | self, document_id: str, content: str, metadata: Dict[str, Any] 91 | ) -> List[Chunk]: 92 | pass 93 | 94 | 95 | class SmartChunker(BaseChunker): 96 | """ 97 | Intelligent chunking implementation that combines: 98 | - Token awareness 99 | - Recursive splitting 100 | - Smart overlap handling 101 | - Configurable behavior 102 | This is inspired by approaches highlighted in https://js.langchain.com/docs/concepts/text_splitters/ 103 | In order to keep dependencies minimal, we're not using LangChain here. 104 | Just taking inspiration from their approaches. 105 | """ 106 | 107 | def __init__(self, config: Optional[ChunkingConfig] = None): 108 | self.config = config or ChunkingConfig() 109 | self.tokenizer = tiktoken.get_encoding(self.config.tokenizer_model) 110 | 111 | def count_tokens(self, text: str) -> int: 112 | """ 113 | Get exact token count for text 114 | """ 115 | return len(self.tokenizer.encode(text)) 116 | 117 | def create_chunk( 118 | self, 119 | document_id: str, 120 | content: str, 121 | chunk_number: int, 122 | total_chunks: int, 123 | base_metadata: Dict[str, Any], 124 | ) -> Chunk: 125 | """Create a chunk with complete metadata""" 126 | token_count = self.count_tokens(content) 127 | 128 | metadata = { 129 | "document_id": document_id, 130 | "chunk_number": chunk_number, 131 | "total_chunks": total_chunks, 132 | "token_count": token_count, 133 | "char_count": len(content), 134 | "chunk_type": "smart", 135 | **base_metadata, 136 | } 137 | 138 | return Chunk( 139 | id=f"{document_id}#chunk{chunk_number}", 140 | content=content.strip(), 141 | metadata=metadata, 142 | ) 143 | 144 | def chunk_document( 145 | self, document_id: str, content: str, metadata: Dict[str, Any] 146 | ) -> List[Chunk]: 147 | """ 148 | Chunk document with intelligent boundary detection and token awareness 149 | This works by recursively splitting the document into chunks with overlap 150 | and then trying to find the best boundaries using progressively smaller separators 151 | """ 152 | if not content or not content.strip(): 153 | raise ChunkingError("Cannot chunk empty content") 154 | if not document_id: 155 | raise ChunkingError("Document ID is required") 156 | try: 157 | # Get initial splits 158 | chunks = self._split_with_overlap( 159 | content, 160 | self.config.separators, 161 | self.config.target_tokens, 162 | self.config.overlap_tokens, 163 | ) 164 | 165 | # Convert to chunk objects with metadata 166 | processed_chunks = [] 167 | for i, text in enumerate(chunks, 1): 168 | chunk = self.create_chunk( 169 | document_id=document_id, 170 | content=text, 171 | chunk_number=i, 172 | total_chunks=len(chunks), 173 | base_metadata=metadata, 174 | ) 175 | processed_chunks.append(chunk) 176 | 177 | # Log stats 178 | total_tokens = sum(c.metadata["token_count"] for c in processed_chunks) 179 | avg_tokens = total_tokens / len(processed_chunks) 180 | logger.info( 181 | f"Split document {document_id} into {len(processed_chunks)} chunks. " 182 | f"Average tokens per chunk: {avg_tokens:.0f}" 183 | ) 184 | 185 | return processed_chunks 186 | 187 | except Exception as e: 188 | raise ChunkingError(f"Error chunking document {document_id}: {e}") 189 | 190 | def _split_with_overlap( 191 | self, text: str, separators: List[str], target_tokens: int, overlap_tokens: int 192 | ) -> List[str]: 193 | """ 194 | Split text recursively while handling overlap 195 | 196 | Args: 197 | text: The text to split 198 | separators: List of separators to try, in order of preference 199 | target_tokens: Target number of tokens per chunk 200 | overlap_tokens: Number of tokens to overlap between chunks 201 | 202 | Returns: 203 | List of text chunks with overlap 204 | 205 | Raises: 206 | ChunkingError: If text cannot be split into chunks 207 | """ 208 | 209 | # Base case - text is small enough 210 | text_tokens = self.count_tokens(text) 211 | if text_tokens <= target_tokens: 212 | return [text] 213 | 214 | # Try each separator in order 215 | for separator in separators: 216 | splits = text.split(separator) 217 | 218 | # Skip if separator doesn't help 219 | if len(splits) == 1: 220 | continue 221 | 222 | # Process splits with overlap 223 | chunks = [] 224 | current_chunk = [] 225 | current_tokens = 0 226 | 227 | for split in splits: 228 | split_tokens = self.count_tokens(split) 229 | 230 | # Check if adding split would exceed target 231 | if current_tokens + split_tokens > target_tokens and current_chunk: 232 | # Add current chunk 233 | chunks.append(separator.join(current_chunk)) 234 | 235 | # Start new chunk with overlap 236 | overlap_tokens_remaining = overlap_tokens 237 | current_chunk = [] 238 | 239 | # Add previous splits until we hit overlap target 240 | prev_splits = current_chunk.copy() 241 | current_chunk = [] 242 | for prev_split in reversed(prev_splits): 243 | prev_tokens = self.count_tokens(prev_split) 244 | if overlap_tokens_remaining - prev_tokens < 0: 245 | break 246 | current_chunk.insert(0, prev_split) 247 | overlap_tokens_remaining -= prev_tokens 248 | 249 | current_tokens = self.count_tokens(separator.join(current_chunk)) 250 | 251 | current_chunk.append(split) 252 | current_tokens += split_tokens 253 | 254 | # Add final chunk 255 | if current_chunk: 256 | chunks.append(separator.join(current_chunk)) 257 | 258 | # If we found valid splits, return them 259 | if chunks: 260 | return chunks 261 | 262 | # If no good splits found, fall back to token boundary 263 | return self._split_by_tokens(text, target_tokens, overlap_tokens) 264 | 265 | def _split_by_tokens( 266 | self, text: str, target_tokens: int, overlap_tokens: int 267 | ) -> List[str]: 268 | """ 269 | Split on token boundaries as a last resort 270 | This is a simple approach that splits the document into chunks of the target size 271 | with an overlap of the overlap size. 272 | """ 273 | tokens = self.tokenizer.encode(text) 274 | chunks = [] 275 | 276 | for i in range(0, len(tokens), target_tokens - overlap_tokens): 277 | chunk_tokens = tokens[i : i + target_tokens] 278 | chunk_text = self.tokenizer.decode(chunk_tokens) 279 | chunks.append(chunk_text) 280 | 281 | return chunks 282 | 283 | 284 | # Factory for creating chunkers 285 | def create_chunker( 286 | chunk_type: str = "smart", config: Optional[ChunkingConfig] = None 287 | ) -> BaseChunker: 288 | """Create appropriate chunker based on type""" 289 | chunkers = {"smart": lambda: SmartChunker(config)} 290 | 291 | if chunk_type not in chunkers: 292 | raise ValueError(f"Unknown chunker type: {chunk_type}") 293 | 294 | return chunkers[chunk_type]() 295 | 296 | 297 | __all__ = [ 298 | "Chunk", 299 | "ChunkingConfig", 300 | "BaseChunker", 301 | "SmartChunker", 302 | "create_chunker", 303 | ] 304 | -------------------------------------------------------------------------------- /src/mcp_pinecone/constants.py: -------------------------------------------------------------------------------- 1 | # Index name 2 | import os 3 | import argparse 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | 8 | 9 | def get_pinecone_config(): 10 | parser = argparse.ArgumentParser(description="Pinecone MCP Configuration") 11 | parser.add_argument( 12 | "--index-name", 13 | default=None, 14 | help="Name of the Pinecone index to use. Will use environment variable PINECONE_INDEX_NAME if not provided.", 15 | ) 16 | parser.add_argument( 17 | "--api-key", 18 | default=None, 19 | help="API key for Pinecone. Will use environment variable PINECONE_API_KEY if not provided.", 20 | ) 21 | args = parser.parse_args() 22 | 23 | # Use command line arguments if provided, otherwise fall back to environment variables 24 | index_name = args.index_name or os.getenv("PINECONE_INDEX_NAME") 25 | api_key = args.api_key or os.getenv("PINECONE_API_KEY") 26 | 27 | # Set default index name if none provided 28 | if not index_name: 29 | index_name = "mcp-pinecone-index" 30 | print(f"No index name provided, using default: {index_name}") 31 | 32 | # Validate API key 33 | if not api_key: 34 | raise ValueError( 35 | "Pinecone API key is required. Provide it via --api-key argument or PINECONE_API_KEY environment variable" 36 | ) 37 | 38 | return index_name, api_key 39 | 40 | 41 | # Get configuration values 42 | PINECONE_INDEX_NAME, PINECONE_API_KEY = get_pinecone_config() 43 | 44 | # Validate configuration after loading 45 | if not PINECONE_INDEX_NAME or not PINECONE_API_KEY: 46 | raise ValueError( 47 | "Missing required configuration. Ensure PINECONE_INDEX_NAME and PINECONE_API_KEY " 48 | "are set either via environment variables or command line arguments." 49 | ) 50 | 51 | # Inference API model name 52 | INFERENCE_MODEL = "multilingual-e5-large" 53 | 54 | # Inference API embedding dimension 55 | INFERENCE_DIMENSION = 1024 56 | 57 | # Export values for use in other modules 58 | __all__ = [ 59 | "PINECONE_INDEX_NAME", 60 | "PINECONE_API_KEY", 61 | "INFERENCE_MODEL", 62 | "INFERENCE_DIMENSION", 63 | ] 64 | -------------------------------------------------------------------------------- /src/mcp_pinecone/pinecone.py: -------------------------------------------------------------------------------- 1 | from pinecone import Pinecone, ServerlessSpec, FetchResponse, UpsertResponse 2 | from typing import List, Dict, Any, Optional, Union 3 | 4 | from pydantic import BaseModel 5 | from .constants import ( 6 | INFERENCE_DIMENSION, 7 | PINECONE_INDEX_NAME, 8 | PINECONE_API_KEY, 9 | INFERENCE_MODEL, 10 | ) 11 | from dotenv import load_dotenv 12 | import logging 13 | 14 | load_dotenv() 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | # Pydantic moddel for a Pinecone record 20 | class PineconeRecord(BaseModel): 21 | """ 22 | Represents a record in Pinecone 23 | """ 24 | 25 | id: str 26 | embedding: List[float] 27 | text: str 28 | metadata: Dict[str, Any] 29 | 30 | def to_dict(self) -> dict: 31 | """ 32 | Convert to dictionary format for JSON serialization 33 | """ 34 | return { 35 | "id": self.id, 36 | "embedding": self.embedding, 37 | "text": self.text, 38 | "metadata": self.metadata, 39 | } 40 | 41 | 42 | class PineconeClient: 43 | """ 44 | A client for interacting with Pinecone. 45 | """ 46 | 47 | def __init__(self): 48 | self.pc = Pinecone(api_key=PINECONE_API_KEY) 49 | # Initialize index after checking/creating 50 | self.ensure_index_exists() 51 | desc = self.pc.describe_index(PINECONE_INDEX_NAME) 52 | self.index = self.pc.Index( 53 | name=PINECONE_INDEX_NAME, 54 | host=desc.host, # Get the proper host from the index description 55 | ) 56 | 57 | def ensure_index_exists(self): 58 | """ 59 | Check if index exists, create if it doesn't. 60 | """ 61 | try: 62 | indexes = self.pc.list_indexes() 63 | 64 | exists = any(index["name"] == PINECONE_INDEX_NAME for index in indexes) 65 | if exists: 66 | logger.warning(f"Index {PINECONE_INDEX_NAME} already exists") 67 | return 68 | 69 | self.create_index() 70 | 71 | except Exception as e: 72 | logger.error(f"Error checking/creating index: {e}") 73 | raise 74 | 75 | def create_index(self): 76 | """ 77 | Create a serverless index with integrated inference. 78 | """ 79 | try: 80 | return self.pc.create_index( 81 | name=PINECONE_INDEX_NAME, 82 | dimension=INFERENCE_DIMENSION, 83 | metric="cosine", 84 | deletion_protection="disabled", # Consider enabling for production 85 | spec=ServerlessSpec(cloud="aws", region="us-east-1"), 86 | ) 87 | except Exception as e: 88 | logger.error(f"Failed to create index: {e}") 89 | raise 90 | 91 | def generate_embeddings(self, text: str) -> List[float]: 92 | """ 93 | Generate embeddings for a given text using Pinecone Inference API. 94 | 95 | Parameters: 96 | text: The text to generate embeddings for. 97 | 98 | Returns: 99 | List[float]: The embeddings for the text. 100 | """ 101 | response = self.pc.inference.embed( 102 | model=INFERENCE_MODEL, 103 | inputs=[text], 104 | parameters={"input_type": "passage", "truncate": "END"}, 105 | ) 106 | # if the response is empty, raise an error 107 | if not response.data: 108 | raise ValueError(f"Failed to generate embeddings for text: {text}") 109 | return response.data[0].values 110 | 111 | def upsert_records( 112 | self, 113 | records: List[PineconeRecord], 114 | namespace: Optional[str] = None, 115 | ) -> UpsertResponse: 116 | """ 117 | Upsert records into the Pinecone index. 118 | 119 | Parameters: 120 | records: List of records to upsert. 121 | namespace: Optional namespace to upsert into. 122 | 123 | Returns: 124 | Dict[str, Any]: The response from Pinecone. 125 | """ 126 | try: 127 | vectors = [] 128 | for record in records: 129 | # Don't continue if there's no vector values 130 | if not record.embedding: 131 | continue 132 | 133 | vector_values = record.embedding 134 | raw_text = record.text 135 | record_id = record.id 136 | metadata = record.metadata 137 | 138 | logger.info(f"Record: {metadata}") 139 | 140 | # Add raw text to metadata 141 | metadata["text"] = raw_text 142 | vectors.append((record_id, vector_values, metadata)) 143 | 144 | return self.index.upsert(vectors=vectors, namespace=namespace) 145 | 146 | except Exception as e: 147 | logger.error(f"Error upserting records: {e}") 148 | raise 149 | 150 | def search_records( 151 | self, 152 | query: Union[str, List[float]], 153 | top_k: int = 10, 154 | namespace: Optional[str] = None, 155 | filter: Optional[Dict] = None, 156 | include_metadata: bool = True, 157 | ) -> Dict[str, Any]: 158 | """ 159 | Search records using integrated inference. 160 | 161 | Parameters: 162 | query: The query to search for. 163 | top_k: The number of results to return. 164 | namespace: Optional namespace to search in. 165 | filter: Optional filter to apply to the search. 166 | include_metadata: Whether to include metadata in the search results. 167 | 168 | Returns: 169 | Dict[str, Any]: The search results from Pinecone. 170 | """ 171 | try: 172 | # If query is text, use our custom function to get embeddings 173 | if isinstance(query, str): 174 | vector = self.generate_embeddings(query) 175 | else: 176 | vector = query 177 | 178 | return self.index.query( 179 | vector=vector, 180 | top_k=top_k, 181 | namespace=namespace, 182 | include_metadata=include_metadata, 183 | filter=filter, 184 | ) 185 | except Exception as e: 186 | logger.error(f"Error searching records: {e}") 187 | raise 188 | 189 | def stats(self) -> Dict[str, Any]: 190 | """ 191 | Get detailed statistics about the index including: 192 | - Total vector count 193 | - Index dimension 194 | - Index fullness 195 | - Namespace-specific statistics 196 | 197 | Returns: 198 | Dict[str, Any]: A dictionary containing: 199 | - namespaces: Dict mapping namespace names to their statistics 200 | - dimension: Dimension of the indexed vectors 201 | - index_fullness: Fullness of the index (0-1 scale) 202 | - total_vector_count: Total number of vectors across all namespaces 203 | 204 | """ 205 | try: 206 | stats = self.index.describe_index_stats() 207 | # Convert namespaces to dict - each NamespaceSummary needs to be converted to dict 208 | namespaces_dict = {} 209 | for ns_name, ns_summary in stats.namespaces.items(): 210 | namespaces_dict[ns_name] = { 211 | "vector_count": ns_summary.vector_count, 212 | } 213 | 214 | return { 215 | "namespaces": namespaces_dict, 216 | "dimension": stats.dimension, 217 | "index_fullness": stats.index_fullness, 218 | "total_vector_count": stats.total_vector_count, 219 | } 220 | except Exception as e: 221 | logger.error(f"Error getting stats: {e}") 222 | raise 223 | 224 | def delete_records( 225 | self, ids: List[str], namespace: Optional[str] = None 226 | ) -> Dict[str, Any]: 227 | """ 228 | Delete records by ID 229 | 230 | Parameters: 231 | ids: List of record IDs to delete 232 | namespace: Optional namespace to delete from 233 | """ 234 | try: 235 | return self.index.delete(ids=ids, namespace=namespace) 236 | except Exception as e: 237 | logger.error(f"Error deleting records: {e}") 238 | raise 239 | 240 | def fetch_records( 241 | self, ids: List[str], namespace: Optional[str] = None 242 | ) -> FetchResponse: 243 | """ 244 | Fetch specific records by ID 245 | 246 | Parameters: 247 | ids: List of record IDs to fetch 248 | namespace: Optional namespace to fetch from 249 | 250 | Returns: 251 | FetchResponse: The response from Pinecone. 252 | 253 | Raises: 254 | Exception: If there is an error fetching the records. 255 | """ 256 | try: 257 | return self.index.fetch(ids=ids, namespace=namespace) 258 | except Exception as e: 259 | logger.error(f"Error fetching records: {e}") 260 | raise 261 | 262 | def list_records( 263 | self, 264 | prefix: Optional[str] = None, 265 | limit: int = 100, 266 | namespace: Optional[str] = None, 267 | ) -> Dict[str, Any]: 268 | """ 269 | List records in the index using pagination. 270 | 271 | Parameters: 272 | prefix: Optional prefix to filter records by. 273 | limit: The number of records to return per page. 274 | namespace: Optional namespace to list records from. 275 | """ 276 | try: 277 | # Using list_paginated for single-page results 278 | response = self.index.list_paginated( 279 | prefix=prefix, limit=limit, namespace=namespace 280 | ) 281 | 282 | # Check if response is None 283 | if response is None: 284 | logger.error("Received None response from Pinecone list_paginated") 285 | return {"vectors": [], "namespace": namespace, "pagination_token": None} 286 | 287 | # Handle the case where vectors might be None 288 | vectors = response.vectors if hasattr(response, "vectors") else [] 289 | 290 | return { 291 | "vectors": [ 292 | { 293 | "id": getattr(v, "id", None), 294 | "metadata": getattr(v, "metadata", {}), 295 | } 296 | for v in vectors 297 | ], 298 | "namespace": getattr(response, "namespace", namespace), 299 | "pagination_token": getattr(response.pagination, "next", None) 300 | if hasattr(response, "pagination") 301 | else None, 302 | } 303 | except Exception as e: 304 | logger.error(f"Error listing records: {e}") 305 | # Return empty result instead of raising 306 | return {"vectors": [], "namespace": namespace, "pagination_token": None} 307 | -------------------------------------------------------------------------------- /src/mcp_pinecone/prompts.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from enum import Enum 3 | import mcp.types as types 4 | from mcp.server import Server 5 | from .pinecone import PineconeClient 6 | from datetime import datetime 7 | 8 | 9 | logger = logging.getLogger("pinecone-mcp") 10 | 11 | 12 | class PromptName(str, Enum): 13 | PINECONE_QUERY = "pinecone-query" 14 | PINECONE_STORE = "pinecone-store" 15 | 16 | 17 | ServerPrompts = [ 18 | types.Prompt( 19 | name=PromptName.PINECONE_QUERY, 20 | description="Search Pinecone index and construct an answer based on relevant pinecone documents", 21 | arguments=[ 22 | types.PromptArgument( 23 | name="query", 24 | description="The question to answer, or the context to search for", 25 | required=True, 26 | ) 27 | ], 28 | ), 29 | types.Prompt( 30 | name=PromptName.PINECONE_STORE, 31 | description="Store content as document in Pinecone", 32 | arguments=[ 33 | types.PromptArgument( 34 | name="content", 35 | description="The content to store as a Pinecone document", 36 | required=True, 37 | ), 38 | types.PromptArgument( 39 | name="namespace", 40 | description="The namespace to store the document in", 41 | required=False, 42 | ), 43 | ], 44 | ), 45 | ] 46 | 47 | 48 | def register_prompts(server: Server, pinecone_client: PineconeClient): 49 | @server.list_prompts() 50 | async def handle_list_prompts() -> list[types.Prompt]: 51 | return ServerPrompts 52 | 53 | @server.get_prompt() 54 | async def handle_get_prompt( 55 | name: str, arguments: dict[str, str] | None 56 | ) -> types.GetPromptResult: 57 | try: 58 | if name == PromptName.PINECONE_QUERY: 59 | return pinecone_query(arguments, pinecone_client) 60 | elif name == PromptName.PINECONE_STORE: 61 | return pinecone_store(arguments, pinecone_client) 62 | else: 63 | raise ValueError(f"Unknown prompt: {name}") 64 | 65 | except Exception as e: 66 | logger.error(f"Error calling prompt {name}: {e}") 67 | raise 68 | 69 | 70 | def pinecone_store( 71 | arguments: dict | None, pinecone_client: PineconeClient 72 | ) -> list[types.TextContent]: 73 | """ 74 | Store content as document in Pinecone 75 | """ 76 | content = arguments.get("content") 77 | namespace = arguments.get("namespace") 78 | 79 | metadata = { 80 | "date": datetime.now().isoformat(), 81 | } 82 | 83 | if not content: 84 | raise ValueError("Content required") 85 | 86 | return types.GetPromptResult( 87 | messages=[ 88 | types.PromptMessage( 89 | role="user", 90 | content=types.TextContent( 91 | type="text", 92 | text=f"The namespace is {namespace if namespace else 'not specified'}. \n" 93 | "If the namespace is not specified, use pinecone-stats to find an appropriate namespace or use the default namespace.", 94 | ), 95 | ), 96 | types.PromptMessage( 97 | role="user", 98 | content=types.TextContent( 99 | type="text", 100 | text=f"Based on the content, generate metadata that can be relevant to the content and used for filtering. \n" 101 | "The metadata should be a dictionary with keys and values that are relevant to the content. \n" 102 | f"Append the metdata to {metadata} \n", 103 | ), 104 | ), 105 | types.PromptMessage( 106 | role="user", 107 | content=types.TextContent( 108 | type="text", 109 | text=f"Run the process-document tool with the content: {content} \n" 110 | "Include generated metadata in the document. \n" 111 | f"Store in the {namespace} if specified", 112 | ), 113 | ), 114 | ] 115 | ) 116 | 117 | 118 | def pinecone_query( 119 | arguments: dict | None, pinecone_client: PineconeClient 120 | ) -> list[types.TextContent]: 121 | """ 122 | Search Pinecone index and construct an answer based on relevant pinecone documents 123 | """ 124 | query = arguments.get("query") 125 | if not query: 126 | raise ValueError("Query required") 127 | 128 | return types.GetPromptResult( 129 | messages=[ 130 | types.PromptMessage( 131 | role="user", 132 | content=types.TextContent( 133 | type="text", 134 | text="First use pinecone-stats to get a list of namespaces that might contain relevant documents. Ignore if a namespace is specified in the query", 135 | ), 136 | ), 137 | types.PromptMessage( 138 | role="user", 139 | content=types.TextContent( 140 | type="text", 141 | text=f"Do a semantic search for the query: {query} with the chosen namespace", 142 | ), 143 | ), 144 | ] 145 | ) 146 | 147 | 148 | __all__ = [ 149 | "register_prompts", 150 | ] 151 | -------------------------------------------------------------------------------- /src/mcp_pinecone/server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Union 3 | from mcp.server.models import InitializationOptions 4 | import mcp.types as types 5 | from mcp.server import NotificationOptions, Server 6 | from pydantic import AnyUrl 7 | import mcp.server.stdio 8 | from .pinecone import PineconeClient 9 | from .tools import register_tools 10 | from .prompts import register_prompts 11 | import importlib.metadata 12 | 13 | logging.basicConfig(level=logging.INFO) 14 | logger = logging.getLogger("pinecone-mcp") 15 | 16 | pinecone_client = None 17 | server = Server("pinecone-mcp") 18 | 19 | 20 | @server.list_resources() 21 | async def handle_list_resources() -> list[types.Resource]: 22 | try: 23 | if pinecone_client is None: 24 | logger.error("Pinecone client is not initialized") 25 | return [] 26 | records = pinecone_client.list_records() 27 | 28 | resources = [] 29 | for record in records.get("vectors", []): 30 | # If metadata is None, use empty dict 31 | metadata = record.get("metadata") or {} 32 | description = ( 33 | metadata.get("text", "")[:100] + "..." if metadata.get("text") else "" 34 | ) 35 | resources.append( 36 | types.Resource( 37 | uri=f"pinecone://vectors/{record['id']}", 38 | name=metadata.get("title", f"Vector {record['id']}"), 39 | description=description, 40 | metadata=metadata, 41 | mimeType=metadata.get("content_type", "text/plain"), 42 | ) 43 | ) 44 | return resources 45 | except Exception as e: 46 | logger.error(f"Error listing resources: {e}") 47 | return [] 48 | 49 | 50 | @server.read_resource() 51 | async def handle_read_resource(uri: AnyUrl) -> Union[str, bytes]: 52 | if not str(uri).startswith("pinecone://vectors/"): 53 | raise ValueError(f"Unsupported URI scheme: {uri}") 54 | 55 | try: 56 | vector_id = str(uri).split("/")[-1] 57 | record = pinecone_client.fetch_records([vector_id]) 58 | 59 | if not record or "records" not in record or not record["records"]: 60 | raise ValueError(f"Vector not found: {vector_id}") 61 | 62 | vector_data = record["records"][0] 63 | metadata = vector_data.get("metadata", {}) 64 | content_type = metadata.get("content_type", "text/plain") 65 | 66 | if content_type.startswith("text/"): 67 | return format_text_content(vector_data) 68 | else: 69 | return format_binary_content(vector_data) 70 | except Exception as e: 71 | raise RuntimeError(f"Pinecone error: {str(e)}") 72 | 73 | 74 | def format_text_content(vector_data: dict) -> str: 75 | metadata = vector_data.get("metadata", {}) 76 | output = [] 77 | 78 | if "title" in metadata: 79 | output.append(f"Title: {metadata['title']}") 80 | output.append(f"ID: {vector_data.get('id')}") 81 | 82 | for key, value in metadata.items(): 83 | if key not in ["title", "text", "content_type"]: 84 | output.append(f"{key}: {value}") 85 | 86 | output.append("") 87 | 88 | if "text" in metadata: 89 | output.append(metadata["text"]) 90 | 91 | return "\n".join(output) 92 | 93 | 94 | def format_binary_content(vector_data: dict) -> bytes: 95 | content = vector_data.get("metadata", {}).get("content", b"") 96 | if isinstance(content, str): 97 | content = content.encode("utf-8") 98 | return content 99 | 100 | 101 | async def main(): 102 | logger.info("Starting Pinecone MCP server") 103 | 104 | global pinecone_client 105 | pinecone_client = PineconeClient() 106 | 107 | # Register tools and prompts 108 | register_tools(server, pinecone_client) 109 | register_prompts(server, pinecone_client) 110 | 111 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): 112 | await server.run( 113 | read_stream, 114 | write_stream, 115 | InitializationOptions( 116 | server_name="pinecone-mcp", 117 | server_version=importlib.metadata.version("mcp-pinecone"), 118 | capabilities=server.get_capabilities( 119 | notification_options=NotificationOptions(resources_changed=True), 120 | experimental_capabilities={}, 121 | ), 122 | ), 123 | ) 124 | -------------------------------------------------------------------------------- /src/mcp_pinecone/tools.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from typing import Dict, Any, TypedDict 4 | from enum import Enum 5 | from typing import Union, Sequence 6 | import mcp.types as types 7 | from mcp.server import Server 8 | from .pinecone import PineconeClient, PineconeRecord 9 | from .utils import MCPToolError 10 | from .chunking import create_chunker, Chunk 11 | 12 | 13 | logger = logging.getLogger("pinecone-mcp") 14 | 15 | 16 | class ToolName(str, Enum): 17 | SEMANTIC_SEARCH = "semantic-search" 18 | READ_DOCUMENT = "read-document" 19 | PROCESS_DOCUMENT = "process-document" 20 | LIST_DOCUMENTS = "list-documents" 21 | PINECONE_STATS = "pinecone-stats" 22 | 23 | 24 | ServerTools = [ 25 | types.Tool( 26 | name=ToolName.SEMANTIC_SEARCH, 27 | description="Search pinecone for documents", 28 | inputSchema={ 29 | "type": "object", 30 | "properties": { 31 | "query": {"type": "string"}, 32 | "top_k": {"type": "integer", "default": 10}, 33 | "namespace": { 34 | "type": "string", 35 | "description": "Optional namespace to search in", 36 | }, 37 | "category": {"type": "string"}, 38 | "tags": {"type": "array", "items": {"type": "string"}}, 39 | "date_range": { 40 | "type": "object", 41 | "properties": { 42 | "start": {"type": "string", "format": "date"}, 43 | "end": {"type": "string", "format": "date"}, 44 | }, 45 | }, 46 | }, 47 | "required": ["query"], 48 | }, 49 | ), 50 | types.Tool( 51 | name=ToolName.READ_DOCUMENT, 52 | description="Read a document from pinecone", 53 | inputSchema={ 54 | "type": "object", 55 | "properties": { 56 | "document_id": {"type": "string"}, 57 | "namespace": { 58 | "type": "string", 59 | "description": "Optional namespace to read from", 60 | }, 61 | }, 62 | "required": ["document_id"], 63 | }, 64 | ), 65 | types.Tool( 66 | name=ToolName.PROCESS_DOCUMENT, 67 | description="Process a document. This will optionally chunk, then embed, and upsert the document into pinecone.", 68 | inputSchema={ 69 | "type": "object", 70 | "properties": { 71 | "document_id": {"type": "string"}, 72 | "text": {"type": "string"}, 73 | "metadata": {"type": "object"}, 74 | "namespace": { 75 | "type": "string", 76 | "description": "Optional namespace to store the document in", 77 | }, 78 | }, 79 | "required": ["document_id", "text", "metadata"], 80 | }, 81 | ), 82 | types.Tool( 83 | name=ToolName.LIST_DOCUMENTS, 84 | description="List all documents in the knowledge base by namespace", 85 | inputSchema={ 86 | "type": "object", 87 | "properties": { 88 | "namespace": { 89 | "type": "string", 90 | "description": "Namespace to list documents in", 91 | } 92 | }, 93 | "required": ["namespace"], 94 | }, 95 | ), 96 | types.Tool( 97 | name=ToolName.PINECONE_STATS, 98 | description="Get stats about the Pinecone index specified in this server", 99 | inputSchema={ 100 | "type": "object", 101 | "properties": {}, 102 | "required": [], 103 | }, 104 | ), 105 | ] 106 | 107 | 108 | def register_tools(server: Server, pinecone_client: PineconeClient): 109 | @server.list_tools() 110 | async def handle_list_tools() -> list[types.Tool]: 111 | return ServerTools 112 | 113 | @server.call_tool() 114 | async def handle_call_tool( 115 | name: str, arguments: dict | None 116 | ) -> Sequence[Union[types.TextContent, types.ImageContent, types.EmbeddedResource]]: 117 | try: 118 | if name == ToolName.SEMANTIC_SEARCH: 119 | return semantic_search(arguments, pinecone_client) 120 | if name == ToolName.PINECONE_STATS: 121 | return pinecone_stats(pinecone_client) 122 | if name == ToolName.READ_DOCUMENT: 123 | return read_document(arguments, pinecone_client) 124 | if name == ToolName.PROCESS_DOCUMENT: 125 | return process_document(arguments, pinecone_client) 126 | if name == ToolName.LIST_DOCUMENTS: 127 | return list_documents(arguments, pinecone_client) 128 | 129 | except Exception as e: 130 | logger.error(f"Error calling tool {name}: {e}") 131 | raise 132 | 133 | 134 | def list_documents( 135 | arguments: dict | None, pinecone_client: PineconeClient 136 | ) -> list[types.TextContent]: 137 | """ 138 | List all documents in the knowledge base by namespace 139 | """ 140 | namespace = arguments.get("namespace") 141 | results = pinecone_client.list_records(namespace=namespace) 142 | return [types.TextContent(type="text", text=json.dumps(results))] 143 | 144 | 145 | def pinecone_stats(pinecone_client: PineconeClient) -> list[types.TextContent]: 146 | """ 147 | Get stats about the Pinecone index specified in this server 148 | """ 149 | stats = pinecone_client.stats() 150 | return [types.TextContent(type="text", text=json.dumps(stats))] 151 | 152 | 153 | def semantic_search( 154 | arguments: dict | None, pinecone_client: PineconeClient 155 | ) -> list[types.TextContent]: 156 | """ 157 | Read a document from the pinecone knowledge base 158 | """ 159 | query = arguments.get("query") 160 | top_k = arguments.get("top_k", 10) 161 | filters = arguments.get("filters", {}) 162 | namespace = arguments.get("namespace") 163 | 164 | results = pinecone_client.search_records( 165 | query=query, 166 | top_k=top_k, 167 | filter=filters, 168 | include_metadata=True, 169 | namespace=namespace, 170 | ) 171 | 172 | matches = results.get("matches", []) 173 | 174 | # Format results with rich context 175 | formatted_text = "Retrieved Contexts:\n\n" 176 | for i, match in enumerate(matches, 1): 177 | metadata = match.get("metadata", {}) 178 | formatted_text += f"Result {i} | Similarity: {match['score']:.3f} | Document ID: {match['id']}\n" 179 | formatted_text += f"{metadata.get('text', '').strip()}\n" 180 | formatted_text += "-" * 10 + "\n\n" 181 | 182 | return [types.TextContent(type="text", text=formatted_text)] 183 | 184 | 185 | def process_document( 186 | arguments: dict | None, pinecone_client: PineconeClient 187 | ) -> list[types.TextContent]: 188 | """ 189 | Process a document by chunking, embedding, and upserting it into the knowledge base. Returns the document ID. 190 | """ 191 | document_id = arguments.get("document_id") 192 | text = arguments.get("text") 193 | namespace = arguments.get("namespace") 194 | metadata = arguments.get("metadata", {}) 195 | 196 | chunker = create_chunker(chunk_type="smart") 197 | chunks = chunker.chunk_document(document_id, text, metadata) 198 | 199 | embed_result = embed_document(chunks, pinecone_client) 200 | 201 | embedded_chunks = embed_result.get("embedded_chunks", None) 202 | 203 | if embedded_chunks is None: 204 | raise MCPToolError("No embedded chunks found") 205 | 206 | upsert_documents(embedded_chunks, pinecone_client, namespace) 207 | 208 | return [ 209 | types.TextContent( 210 | type="text", 211 | text=f"Successfully processed document. The document ID is {document_id}", 212 | ) 213 | ] 214 | 215 | 216 | class EmbeddingResult(TypedDict): 217 | embedded_chunks: list[PineconeRecord] 218 | total_embedded: int 219 | 220 | 221 | def embed_document( 222 | chunks: list[Chunk], pinecone_client: PineconeClient 223 | ) -> EmbeddingResult: 224 | """ 225 | Embed a list of chunks. 226 | Uses the Pinecone client to generate embeddings with the inference API. 227 | """ 228 | embedded_chunks = [] 229 | for chunk in chunks: 230 | content = chunk.content 231 | chunk_id = chunk.id 232 | metadata = chunk.metadata 233 | 234 | if not content or not chunk_id: 235 | logger.warning(f"Skipping invalid chunk: {chunk}") 236 | continue 237 | 238 | embedding = pinecone_client.generate_embeddings(content) 239 | record = PineconeRecord( 240 | id=chunk_id, 241 | embedding=embedding, 242 | text=content, 243 | metadata=metadata, 244 | ) 245 | embedded_chunks.append(record) 246 | return EmbeddingResult( 247 | embedded_chunks=embedded_chunks, 248 | total_embedded=len(embedded_chunks), 249 | ) 250 | 251 | 252 | def read_document( 253 | arguments: dict | None, pinecone_client: PineconeClient 254 | ) -> list[types.TextContent]: 255 | """ 256 | Read a single Pinecone document by ID 257 | """ 258 | document_id = arguments.get("document_id") 259 | namespace = arguments.get("namespace") 260 | if not document_id: 261 | raise ValueError("document_id is required") 262 | 263 | # Fetch the record using your existing fetch_records method 264 | record = pinecone_client.fetch_records([document_id], namespace=namespace) 265 | 266 | # Get the vector data for this document 267 | vector = record.vectors.get(document_id) 268 | if not vector: 269 | raise ValueError(f"Document {document_id} not found") 270 | 271 | # Get metadata from the vector 272 | metadata = vector.metadata if hasattr(vector, "metadata") else {} 273 | 274 | # Format the document content 275 | formatted_content = [] 276 | formatted_content.append(f"Document ID: {document_id}") 277 | formatted_content.append("") # Empty line for spacing 278 | 279 | if metadata: 280 | formatted_content.append("Metadata:") 281 | for key, value in metadata.items(): 282 | formatted_content.append(f"{key}: {value}") 283 | 284 | return [types.TextContent(type="text", text="\n".join(formatted_content))] 285 | 286 | 287 | def upsert_documents( 288 | records: list[PineconeRecord], 289 | pinecone_client: PineconeClient, 290 | namespace: str | None = None, 291 | ) -> Dict[str, Any]: 292 | """ 293 | Upsert a list of Pinecone records into the knowledge base. 294 | """ 295 | result = pinecone_client.upsert_records(records, namespace=namespace) 296 | return result 297 | 298 | 299 | __all__ = [ 300 | "register_tools", 301 | ] 302 | -------------------------------------------------------------------------------- /src/mcp_pinecone/utils.py: -------------------------------------------------------------------------------- 1 | class MCPToolError(Exception): 2 | """Custom exception for MCP tool errors""" 3 | 4 | def __init__(self, code: int, message: str): 5 | self.code = code 6 | self.message = message 7 | super().__init__(message) 8 | 9 | 10 | def is_valid_vector_uri(uri: str) -> bool: 11 | """ 12 | Validate vector URI format 13 | 14 | Parameters: 15 | uri: The URI to validate. 16 | 17 | Returns: 18 | bool: True if the URI is valid, False otherwise.s 19 | """ 20 | try: 21 | if not uri.startswith("pinecone://vectors/"): 22 | return False 23 | vector_id = uri.split("/")[-1] 24 | return bool(vector_id.strip()) # Ensure non-empty ID 25 | except Exception: 26 | return False 27 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | resolution-markers = [ 4 | "python_full_version < '3.12.4'", 5 | "python_full_version >= '3.12.4'", 6 | ] 7 | 8 | [[package]] 9 | name = "annotated-types" 10 | version = "0.7.0" 11 | source = { registry = "https://pypi.org/simple" } 12 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 13 | wheels = [ 14 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 15 | ] 16 | 17 | [[package]] 18 | name = "anyio" 19 | version = "4.7.0" 20 | source = { registry = "https://pypi.org/simple" } 21 | dependencies = [ 22 | { name = "idna" }, 23 | { name = "sniffio" }, 24 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 25 | ] 26 | sdist = { url = "https://files.pythonhosted.org/packages/f6/40/318e58f669b1a9e00f5c4453910682e2d9dd594334539c7b7817dabb765f/anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", size = 177076 } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/a0/7a/4daaf3b6c08ad7ceffea4634ec206faeff697526421c20f07628c7372156/anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352", size = 93052 }, 29 | ] 30 | 31 | [[package]] 32 | name = "attrs" 33 | version = "24.3.0" 34 | source = { registry = "https://pypi.org/simple" } 35 | sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6260f8ccc11f0917360fc0da435c5c9c7504e3db174d5a12a1494887b045/attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", size = 805984 } 36 | wheels = [ 37 | { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, 38 | ] 39 | 40 | [[package]] 41 | name = "certifi" 42 | version = "2024.12.14" 43 | source = { registry = "https://pypi.org/simple" } 44 | sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } 45 | wheels = [ 46 | { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, 47 | ] 48 | 49 | [[package]] 50 | name = "charset-normalizer" 51 | version = "3.4.1" 52 | source = { registry = "https://pypi.org/simple" } 53 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } 54 | wheels = [ 55 | { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, 56 | { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, 57 | { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, 58 | { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, 59 | { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, 60 | { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, 61 | { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, 62 | { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, 63 | { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, 64 | { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, 65 | { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, 66 | { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, 67 | { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, 68 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, 69 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, 70 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, 71 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, 72 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, 73 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, 74 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, 75 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, 76 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, 77 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, 78 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, 79 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, 80 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, 81 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, 82 | ] 83 | 84 | [[package]] 85 | name = "colorama" 86 | version = "0.4.6" 87 | source = { registry = "https://pypi.org/simple" } 88 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 89 | wheels = [ 90 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 91 | ] 92 | 93 | [[package]] 94 | name = "h11" 95 | version = "0.14.0" 96 | source = { registry = "https://pypi.org/simple" } 97 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 98 | wheels = [ 99 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 100 | ] 101 | 102 | [[package]] 103 | name = "httpcore" 104 | version = "1.0.7" 105 | source = { registry = "https://pypi.org/simple" } 106 | dependencies = [ 107 | { name = "certifi" }, 108 | { name = "h11" }, 109 | ] 110 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } 111 | wheels = [ 112 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, 113 | ] 114 | 115 | [[package]] 116 | name = "httpx" 117 | version = "0.28.1" 118 | source = { registry = "https://pypi.org/simple" } 119 | dependencies = [ 120 | { name = "anyio" }, 121 | { name = "certifi" }, 122 | { name = "httpcore" }, 123 | { name = "idna" }, 124 | ] 125 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 126 | wheels = [ 127 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 128 | ] 129 | 130 | [[package]] 131 | name = "httpx-sse" 132 | version = "0.4.0" 133 | source = { registry = "https://pypi.org/simple" } 134 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 135 | wheels = [ 136 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 137 | ] 138 | 139 | [[package]] 140 | name = "idna" 141 | version = "3.10" 142 | source = { registry = "https://pypi.org/simple" } 143 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 144 | wheels = [ 145 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 146 | ] 147 | 148 | [[package]] 149 | name = "jsonschema" 150 | version = "4.23.0" 151 | source = { registry = "https://pypi.org/simple" } 152 | dependencies = [ 153 | { name = "attrs" }, 154 | { name = "jsonschema-specifications" }, 155 | { name = "referencing" }, 156 | { name = "rpds-py" }, 157 | ] 158 | sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } 159 | wheels = [ 160 | { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, 161 | ] 162 | 163 | [[package]] 164 | name = "jsonschema-specifications" 165 | version = "2024.10.1" 166 | source = { registry = "https://pypi.org/simple" } 167 | dependencies = [ 168 | { name = "referencing" }, 169 | ] 170 | sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } 171 | wheels = [ 172 | { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, 173 | ] 174 | 175 | [[package]] 176 | name = "mcp" 177 | version = "1.1.2" 178 | source = { registry = "https://pypi.org/simple" } 179 | dependencies = [ 180 | { name = "anyio" }, 181 | { name = "httpx" }, 182 | { name = "httpx-sse" }, 183 | { name = "pydantic" }, 184 | { name = "sse-starlette" }, 185 | { name = "starlette" }, 186 | ] 187 | sdist = { url = "https://files.pythonhosted.org/packages/9b/f3/5cf212e60681ea6da0dbb6e0d1bc0ab2dbf5eebc749b69663d46f114fea1/mcp-1.1.2.tar.gz", hash = "sha256:694aa9df7a8641b24953c935eb72c63136dc948981021525a0add199bdfee402", size = 57628 } 188 | wheels = [ 189 | { url = "https://files.pythonhosted.org/packages/df/40/9883eac3718b860d4006eba1920bfcb628f0a1fe37fac46a4f4e391edca6/mcp-1.1.2-py3-none-any.whl", hash = "sha256:a4d32d60fd80a1702440ba4751b847a8a88957a1f7b059880953143e9759965a", size = 36652 }, 190 | ] 191 | 192 | [[package]] 193 | name = "mcp-pinecone" 194 | version = "0.1.7" 195 | source = { editable = "." } 196 | dependencies = [ 197 | { name = "httpx" }, 198 | { name = "jsonschema" }, 199 | { name = "mcp" }, 200 | { name = "pinecone" }, 201 | { name = "python-dotenv" }, 202 | { name = "tiktoken" }, 203 | ] 204 | 205 | [package.metadata] 206 | requires-dist = [ 207 | { name = "httpx", specifier = ">=0.28.0" }, 208 | { name = "jsonschema", specifier = ">=4.23.0" }, 209 | { name = "mcp", specifier = ">=1.0.0" }, 210 | { name = "pinecone", specifier = ">=5.4.1" }, 211 | { name = "python-dotenv", specifier = ">=1.0.1" }, 212 | { name = "tiktoken", specifier = ">=0.8.0" }, 213 | ] 214 | 215 | [[package]] 216 | name = "pinecone" 217 | version = "5.4.2" 218 | source = { registry = "https://pypi.org/simple" } 219 | dependencies = [ 220 | { name = "certifi" }, 221 | { name = "pinecone-plugin-inference" }, 222 | { name = "pinecone-plugin-interface" }, 223 | { name = "python-dateutil" }, 224 | { name = "tqdm" }, 225 | { name = "typing-extensions" }, 226 | { name = "urllib3", marker = "python_full_version < '4.0'" }, 227 | ] 228 | sdist = { url = "https://files.pythonhosted.org/packages/df/4e/3376f99662f56e7462a4c444edc19e0cbb20676f03b8f70f56a964f34de4/pinecone-5.4.2.tar.gz", hash = "sha256:23e8aaa73b400bb11a3b626c4129284fb170f19025b82f65bd89cbb0dab2b873", size = 191780 } 229 | wheels = [ 230 | { url = "https://files.pythonhosted.org/packages/2f/a4/f7214bf02bb2edb29778e35fa6e73e2d188c403e6d9c2b6945f660a776b3/pinecone-5.4.2-py3-none-any.whl", hash = "sha256:1fad082c66a50a229b58cda0c3a1fa0083532dc9de8303015fe4071cb25c19a8", size = 427295 }, 231 | ] 232 | 233 | [[package]] 234 | name = "pinecone-plugin-inference" 235 | version = "3.1.0" 236 | source = { registry = "https://pypi.org/simple" } 237 | dependencies = [ 238 | { name = "pinecone-plugin-interface" }, 239 | ] 240 | sdist = { url = "https://files.pythonhosted.org/packages/3a/82/09f6fb3c9d3b005c5b110d323a98f848f57babb1394ebea9f72e26f68242/pinecone_plugin_inference-3.1.0.tar.gz", hash = "sha256:eff826178e1fe448577be2ff3d8dbb072befbbdc2d888e214624523a1c37cd8d", size = 49315 } 241 | wheels = [ 242 | { url = "https://files.pythonhosted.org/packages/89/45/4ae4e38439919584c2d34b6bef5d0ef8d068030871dd4da911d174840ee6/pinecone_plugin_inference-3.1.0-py3-none-any.whl", hash = "sha256:96e861527bd41e90d58b7e76abd4e713d9af28f63e76a51864dfb9cf7180e3df", size = 87477 }, 243 | ] 244 | 245 | [[package]] 246 | name = "pinecone-plugin-interface" 247 | version = "0.0.7" 248 | source = { registry = "https://pypi.org/simple" } 249 | sdist = { url = "https://files.pythonhosted.org/packages/f4/fb/e8a4063264953ead9e2b24d9b390152c60f042c951c47f4592e9996e57ff/pinecone_plugin_interface-0.0.7.tar.gz", hash = "sha256:b8e6675e41847333aa13923cc44daa3f85676d7157324682dc1640588a982846", size = 3370 } 250 | wheels = [ 251 | { url = "https://files.pythonhosted.org/packages/3b/1d/a21fdfcd6d022cb64cef5c2a29ee6691c6c103c4566b41646b080b7536a5/pinecone_plugin_interface-0.0.7-py3-none-any.whl", hash = "sha256:875857ad9c9fc8bbc074dbe780d187a2afd21f5bfe0f3b08601924a61ef1bba8", size = 6249 }, 252 | ] 253 | 254 | [[package]] 255 | name = "pydantic" 256 | version = "2.10.4" 257 | source = { registry = "https://pypi.org/simple" } 258 | dependencies = [ 259 | { name = "annotated-types" }, 260 | { name = "pydantic-core" }, 261 | { name = "typing-extensions" }, 262 | ] 263 | sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 } 264 | wheels = [ 265 | { url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 }, 266 | ] 267 | 268 | [[package]] 269 | name = "pydantic-core" 270 | version = "2.27.2" 271 | source = { registry = "https://pypi.org/simple" } 272 | dependencies = [ 273 | { name = "typing-extensions" }, 274 | ] 275 | sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } 276 | wheels = [ 277 | { 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 }, 278 | { 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 }, 279 | { 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 }, 280 | { 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 }, 281 | { 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 }, 282 | { 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 }, 283 | { 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 }, 284 | { 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 }, 285 | { 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 }, 286 | { 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 }, 287 | { 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 }, 288 | { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, 289 | { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, 290 | { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, 291 | { 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 }, 292 | { 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 }, 293 | { 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 }, 294 | { 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 }, 295 | { 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 }, 296 | { 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 }, 297 | { 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 }, 298 | { 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 }, 299 | { 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 }, 300 | { 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 }, 301 | { 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 }, 302 | { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, 303 | { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, 304 | { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, 305 | ] 306 | 307 | [[package]] 308 | name = "python-dateutil" 309 | version = "2.9.0.post0" 310 | source = { registry = "https://pypi.org/simple" } 311 | dependencies = [ 312 | { name = "six" }, 313 | ] 314 | sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } 315 | wheels = [ 316 | { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, 317 | ] 318 | 319 | [[package]] 320 | name = "python-dotenv" 321 | version = "1.0.1" 322 | source = { registry = "https://pypi.org/simple" } 323 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } 324 | wheels = [ 325 | { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, 326 | ] 327 | 328 | [[package]] 329 | name = "referencing" 330 | version = "0.35.1" 331 | source = { registry = "https://pypi.org/simple" } 332 | dependencies = [ 333 | { name = "attrs" }, 334 | { name = "rpds-py" }, 335 | ] 336 | sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } 337 | wheels = [ 338 | { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, 339 | ] 340 | 341 | [[package]] 342 | name = "regex" 343 | version = "2024.11.6" 344 | source = { registry = "https://pypi.org/simple" } 345 | sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } 346 | wheels = [ 347 | { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, 348 | { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, 349 | { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, 350 | { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, 351 | { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, 352 | { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, 353 | { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, 354 | { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, 355 | { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, 356 | { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, 357 | { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, 358 | { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, 359 | { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, 360 | { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, 361 | { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, 362 | { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, 363 | { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, 364 | { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, 365 | { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, 366 | { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, 367 | { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, 368 | { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, 369 | { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, 370 | { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, 371 | { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, 372 | { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, 373 | { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, 374 | { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, 375 | { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, 376 | { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, 377 | ] 378 | 379 | [[package]] 380 | name = "requests" 381 | version = "2.32.3" 382 | source = { registry = "https://pypi.org/simple" } 383 | dependencies = [ 384 | { name = "certifi" }, 385 | { name = "charset-normalizer" }, 386 | { name = "idna" }, 387 | { name = "urllib3" }, 388 | ] 389 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 390 | wheels = [ 391 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 392 | ] 393 | 394 | [[package]] 395 | name = "rpds-py" 396 | version = "0.22.3" 397 | source = { registry = "https://pypi.org/simple" } 398 | sdist = { url = "https://files.pythonhosted.org/packages/01/80/cce854d0921ff2f0a9fa831ba3ad3c65cee3a46711addf39a2af52df2cfd/rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", size = 26771 } 399 | wheels = [ 400 | { url = "https://files.pythonhosted.org/packages/75/47/3383ee3bd787a2a5e65a9b9edc37ccf8505c0a00170e3a5e6ea5fbcd97f7/rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", size = 352334 }, 401 | { url = "https://files.pythonhosted.org/packages/40/14/aa6400fa8158b90a5a250a77f2077c0d0cd8a76fce31d9f2b289f04c6dec/rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", size = 342111 }, 402 | { url = "https://files.pythonhosted.org/packages/7d/06/395a13bfaa8a28b302fb433fb285a67ce0ea2004959a027aea8f9c52bad4/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", size = 384286 }, 403 | { url = "https://files.pythonhosted.org/packages/43/52/d8eeaffab047e6b7b7ef7f00d5ead074a07973968ffa2d5820fa131d7852/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e", size = 391739 }, 404 | { url = "https://files.pythonhosted.org/packages/83/31/52dc4bde85c60b63719610ed6f6d61877effdb5113a72007679b786377b8/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", size = 427306 }, 405 | { url = "https://files.pythonhosted.org/packages/70/d5/1bab8e389c2261dba1764e9e793ed6830a63f830fdbec581a242c7c46bda/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", size = 442717 }, 406 | { url = "https://files.pythonhosted.org/packages/82/a1/a45f3e30835b553379b3a56ea6c4eb622cf11e72008229af840e4596a8ea/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", size = 385721 }, 407 | { url = "https://files.pythonhosted.org/packages/a6/27/780c942de3120bdd4d0e69583f9c96e179dfff082f6ecbb46b8d6488841f/rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", size = 415824 }, 408 | { url = "https://files.pythonhosted.org/packages/94/0b/aa0542ca88ad20ea719b06520f925bae348ea5c1fdf201b7e7202d20871d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", size = 561227 }, 409 | { url = "https://files.pythonhosted.org/packages/0d/92/3ed77d215f82c8f844d7f98929d56cc321bb0bcfaf8f166559b8ec56e5f1/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", size = 587424 }, 410 | { url = "https://files.pythonhosted.org/packages/09/42/cacaeb047a22cab6241f107644f230e2935d4efecf6488859a7dd82fc47d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", size = 555953 }, 411 | { url = "https://files.pythonhosted.org/packages/e6/52/c921dc6d5f5d45b212a456c1f5b17df1a471127e8037eb0972379e39dff4/rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", size = 221339 }, 412 | { url = "https://files.pythonhosted.org/packages/f2/c7/f82b5be1e8456600395366f86104d1bd8d0faed3802ad511ef6d60c30d98/rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", size = 235786 }, 413 | { url = "https://files.pythonhosted.org/packages/d0/bf/36d5cc1f2c609ae6e8bf0fc35949355ca9d8790eceb66e6385680c951e60/rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", size = 351657 }, 414 | { url = "https://files.pythonhosted.org/packages/24/2a/f1e0fa124e300c26ea9382e59b2d582cba71cedd340f32d1447f4f29fa4e/rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", size = 341829 }, 415 | { url = "https://files.pythonhosted.org/packages/cf/c2/0da1231dd16953845bed60d1a586fcd6b15ceaeb965f4d35cdc71f70f606/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", size = 384220 }, 416 | { url = "https://files.pythonhosted.org/packages/c7/73/a4407f4e3a00a9d4b68c532bf2d873d6b562854a8eaff8faa6133b3588ec/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", size = 391009 }, 417 | { url = "https://files.pythonhosted.org/packages/a9/c3/04b7353477ab360fe2563f5f0b176d2105982f97cd9ae80a9c5a18f1ae0f/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", size = 426989 }, 418 | { url = "https://files.pythonhosted.org/packages/8d/e6/e4b85b722bcf11398e17d59c0f6049d19cd606d35363221951e6d625fcb0/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", size = 441544 }, 419 | { url = "https://files.pythonhosted.org/packages/27/fc/403e65e56f65fff25f2973216974976d3f0a5c3f30e53758589b6dc9b79b/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", size = 385179 }, 420 | { url = "https://files.pythonhosted.org/packages/57/9b/2be9ff9700d664d51fd96b33d6595791c496d2778cb0b2a634f048437a55/rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", size = 415103 }, 421 | { url = "https://files.pythonhosted.org/packages/bb/a5/03c2ad8ca10994fcf22dd2150dd1d653bc974fa82d9a590494c84c10c641/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", size = 560916 }, 422 | { url = "https://files.pythonhosted.org/packages/ba/2e/be4fdfc8b5b576e588782b56978c5b702c5a2307024120d8aeec1ab818f0/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", size = 587062 }, 423 | { url = "https://files.pythonhosted.org/packages/67/e0/2034c221937709bf9c542603d25ad43a68b4b0a9a0c0b06a742f2756eb66/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", size = 555734 }, 424 | { url = "https://files.pythonhosted.org/packages/ea/ce/240bae07b5401a22482b58e18cfbabaa392409b2797da60223cca10d7367/rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", size = 220663 }, 425 | { url = "https://files.pythonhosted.org/packages/cb/f0/d330d08f51126330467edae2fa4efa5cec8923c87551a79299380fdea30d/rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", size = 235503 }, 426 | { url = "https://files.pythonhosted.org/packages/f7/c4/dbe1cc03df013bf2feb5ad00615038050e7859f381e96fb5b7b4572cd814/rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", size = 347698 }, 427 | { url = "https://files.pythonhosted.org/packages/a4/3a/684f66dd6b0f37499cad24cd1c0e523541fd768576fa5ce2d0a8799c3cba/rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", size = 337330 }, 428 | { url = "https://files.pythonhosted.org/packages/82/eb/e022c08c2ce2e8f7683baa313476492c0e2c1ca97227fe8a75d9f0181e95/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", size = 380022 }, 429 | { url = "https://files.pythonhosted.org/packages/e4/21/5a80e653e4c86aeb28eb4fea4add1f72e1787a3299687a9187105c3ee966/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", size = 390754 }, 430 | { url = "https://files.pythonhosted.org/packages/37/a4/d320a04ae90f72d080b3d74597074e62be0a8ecad7d7321312dfe2dc5a6a/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", size = 423840 }, 431 | { url = "https://files.pythonhosted.org/packages/87/70/674dc47d93db30a6624279284e5631be4c3a12a0340e8e4f349153546728/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", size = 438970 }, 432 | { url = "https://files.pythonhosted.org/packages/3f/64/9500f4d66601d55cadd21e90784cfd5d5f4560e129d72e4339823129171c/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", size = 383146 }, 433 | { url = "https://files.pythonhosted.org/packages/4d/45/630327addb1d17173adcf4af01336fd0ee030c04798027dfcb50106001e0/rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", size = 408294 }, 434 | { url = "https://files.pythonhosted.org/packages/5f/ef/8efb3373cee54ea9d9980b772e5690a0c9e9214045a4e7fa35046e399fee/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", size = 556345 }, 435 | { url = "https://files.pythonhosted.org/packages/54/01/151d3b9ef4925fc8f15bfb131086c12ec3c3d6dd4a4f7589c335bf8e85ba/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", size = 582292 }, 436 | { url = "https://files.pythonhosted.org/packages/30/89/35fc7a6cdf3477d441c7aca5e9bbf5a14e0f25152aed7f63f4e0b141045d/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", size = 553855 }, 437 | { url = "https://files.pythonhosted.org/packages/8f/e0/830c02b2457c4bd20a8c5bb394d31d81f57fbefce2dbdd2e31feff4f7003/rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", size = 219100 }, 438 | { url = "https://files.pythonhosted.org/packages/f8/30/7ac943f69855c2db77407ae363484b915d861702dbba1aa82d68d57f42be/rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", size = 233794 }, 439 | ] 440 | 441 | [[package]] 442 | name = "six" 443 | version = "1.17.0" 444 | source = { registry = "https://pypi.org/simple" } 445 | sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } 446 | wheels = [ 447 | { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, 448 | ] 449 | 450 | [[package]] 451 | name = "sniffio" 452 | version = "1.3.1" 453 | source = { registry = "https://pypi.org/simple" } 454 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 455 | wheels = [ 456 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 457 | ] 458 | 459 | [[package]] 460 | name = "sse-starlette" 461 | version = "2.2.1" 462 | source = { registry = "https://pypi.org/simple" } 463 | dependencies = [ 464 | { name = "anyio" }, 465 | { name = "starlette" }, 466 | ] 467 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } 468 | wheels = [ 469 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, 470 | ] 471 | 472 | [[package]] 473 | name = "starlette" 474 | version = "0.45.1" 475 | source = { registry = "https://pypi.org/simple" } 476 | dependencies = [ 477 | { name = "anyio" }, 478 | ] 479 | sdist = { url = "https://files.pythonhosted.org/packages/c1/be/b398217eb35b356d2d9bb84ec67071ea2842e02950fcf38b33df9d5b24ba/starlette-0.45.1.tar.gz", hash = "sha256:a8ae1fa3b1ab7ca83a4abd77871921a13fb5aeaf4874436fb96c29dfcd4ecfa3", size = 2573953 } 480 | wheels = [ 481 | { url = "https://files.pythonhosted.org/packages/6b/2c/a50484b035ee0e13ebb7a42391e391befbfc1b6a9ad5503e83badd182ada/starlette-0.45.1-py3-none-any.whl", hash = "sha256:5656c0524f586e9148d9a3c1dd5257fb42a99892fb0dc6877dd76ef4d184aac3", size = 71488 }, 482 | ] 483 | 484 | [[package]] 485 | name = "tiktoken" 486 | version = "0.8.0" 487 | source = { registry = "https://pypi.org/simple" } 488 | dependencies = [ 489 | { name = "regex" }, 490 | { name = "requests" }, 491 | ] 492 | sdist = { url = "https://files.pythonhosted.org/packages/37/02/576ff3a6639e755c4f70997b2d315f56d6d71e0d046f4fb64cb81a3fb099/tiktoken-0.8.0.tar.gz", hash = "sha256:9ccbb2740f24542534369c5635cfd9b2b3c2490754a78ac8831d99f89f94eeb2", size = 35107 } 493 | wheels = [ 494 | { url = "https://files.pythonhosted.org/packages/c1/22/34b2e136a6f4af186b6640cbfd6f93400783c9ef6cd550d9eab80628d9de/tiktoken-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:881839cfeae051b3628d9823b2e56b5cc93a9e2efb435f4cf15f17dc45f21586", size = 1039357 }, 495 | { url = "https://files.pythonhosted.org/packages/04/d2/c793cf49c20f5855fd6ce05d080c0537d7418f22c58e71f392d5e8c8dbf7/tiktoken-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe9399bdc3f29d428f16a2f86c3c8ec20be3eac5f53693ce4980371c3245729b", size = 982616 }, 496 | { url = "https://files.pythonhosted.org/packages/b3/a1/79846e5ef911cd5d75c844de3fa496a10c91b4b5f550aad695c5df153d72/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a58deb7075d5b69237a3ff4bb51a726670419db6ea62bdcd8bd80c78497d7ab", size = 1144011 }, 497 | { url = "https://files.pythonhosted.org/packages/26/32/e0e3a859136e95c85a572e4806dc58bf1ddf651108ae8b97d5f3ebe1a244/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2908c0d043a7d03ebd80347266b0e58440bdef5564f84f4d29fb235b5df3b04", size = 1175432 }, 498 | { url = "https://files.pythonhosted.org/packages/c7/89/926b66e9025b97e9fbabeaa59048a736fe3c3e4530a204109571104f921c/tiktoken-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:294440d21a2a51e12d4238e68a5972095534fe9878be57d905c476017bff99fc", size = 1236576 }, 499 | { url = "https://files.pythonhosted.org/packages/45/e2/39d4aa02a52bba73b2cd21ba4533c84425ff8786cc63c511d68c8897376e/tiktoken-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d8f3192733ac4d77977432947d563d7e1b310b96497acd3c196c9bddb36ed9db", size = 883824 }, 500 | { url = "https://files.pythonhosted.org/packages/e3/38/802e79ba0ee5fcbf240cd624143f57744e5d411d2e9d9ad2db70d8395986/tiktoken-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:02be1666096aff7da6cbd7cdaa8e7917bfed3467cd64b38b1f112e96d3b06a24", size = 1039648 }, 501 | { url = "https://files.pythonhosted.org/packages/b1/da/24cdbfc302c98663fbea66f5866f7fa1048405c7564ab88483aea97c3b1a/tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94ff53c5c74b535b2cbf431d907fc13c678bbd009ee633a2aca269a04389f9a", size = 982763 }, 502 | { url = "https://files.pythonhosted.org/packages/e4/f0/0ecf79a279dfa41fc97d00adccf976ecc2556d3c08ef3e25e45eb31f665b/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b231f5e8982c245ee3065cd84a4712d64692348bc609d84467c57b4b72dcbc5", size = 1144417 }, 503 | { url = "https://files.pythonhosted.org/packages/ab/d3/155d2d4514f3471a25dc1d6d20549ef254e2aa9bb5b1060809b1d3b03d3a/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4177faa809bd55f699e88c96d9bb4635d22e3f59d635ba6fd9ffedf7150b9953", size = 1175108 }, 504 | { url = "https://files.pythonhosted.org/packages/19/eb/5989e16821ee8300ef8ee13c16effc20dfc26c777d05fbb6825e3c037b81/tiktoken-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5376b6f8dc4753cd81ead935c5f518fa0fbe7e133d9e25f648d8c4dabdd4bad7", size = 1236520 }, 505 | { url = "https://files.pythonhosted.org/packages/40/59/14b20465f1d1cb89cfbc96ec27e5617b2d41c79da12b5e04e96d689be2a7/tiktoken-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:18228d624807d66c87acd8f25fc135665617cab220671eb65b50f5d70fa51f69", size = 883849 }, 506 | ] 507 | 508 | [[package]] 509 | name = "tqdm" 510 | version = "4.67.1" 511 | source = { registry = "https://pypi.org/simple" } 512 | dependencies = [ 513 | { name = "colorama", marker = "platform_system == 'Windows'" }, 514 | ] 515 | sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } 516 | wheels = [ 517 | { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, 518 | ] 519 | 520 | [[package]] 521 | name = "typing-extensions" 522 | version = "4.12.2" 523 | source = { registry = "https://pypi.org/simple" } 524 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 525 | wheels = [ 526 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 527 | ] 528 | 529 | [[package]] 530 | name = "urllib3" 531 | version = "2.3.0" 532 | source = { registry = "https://pypi.org/simple" } 533 | sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } 534 | wheels = [ 535 | { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, 536 | ] 537 | --------------------------------------------------------------------------------