From cce0f55f513d3d982ed42a5c69cb2e8a182725ff Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Tue, 16 Dec 2025 10:26:10 +0530 Subject: [PATCH 01/13] 1. Create oci-support-mcp-server 2. Create models.py and server.py 3. MCP tool function for list incidents --- src/oci-support-mcp-server/.python-version | 1 + src/oci-support-mcp-server/LICENSE.txt | 35 + src/oci-support-mcp-server/README.md | 0 src/oci-support-mcp-server/oracle/__init__.py | 5 + .../oracle/oci_support_mcp_server/__init__.py | 8 + .../oracle/oci_support_mcp_server/models.py | 155 ++ .../oracle/oci_support_mcp_server/server.py | 195 +++ src/oci-support-mcp-server/pyproject.toml | 41 + src/oci-support-mcp-server/uv.lock | 1418 +++++++++++++++++ 9 files changed, 1858 insertions(+) create mode 100644 src/oci-support-mcp-server/.python-version create mode 100644 src/oci-support-mcp-server/LICENSE.txt create mode 100644 src/oci-support-mcp-server/README.md create mode 100644 src/oci-support-mcp-server/oracle/__init__.py create mode 100644 src/oci-support-mcp-server/oracle/oci_support_mcp_server/__init__.py create mode 100644 src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py create mode 100644 src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py create mode 100644 src/oci-support-mcp-server/pyproject.toml create mode 100644 src/oci-support-mcp-server/uv.lock diff --git a/src/oci-support-mcp-server/.python-version b/src/oci-support-mcp-server/.python-version new file mode 100644 index 00000000..24ee5b1b --- /dev/null +++ b/src/oci-support-mcp-server/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/src/oci-support-mcp-server/LICENSE.txt b/src/oci-support-mcp-server/LICENSE.txt new file mode 100644 index 00000000..8dc7c070 --- /dev/null +++ b/src/oci-support-mcp-server/LICENSE.txt @@ -0,0 +1,35 @@ +Copyright (c) 2025 Oracle and/or its affiliates. + +The Universal Permissive License (UPL), Version 1.0 + +Subject to the condition set forth below, permission is hereby granted to any +person obtaining a copy of this software, associated documentation and/or data +(collectively the "Software"), free of charge and under any and all copyright +rights in the Software, and any and all patent rights owned or freely +licensable by each licensor hereunder covering either (i) the unmodified +Software as contributed to or provided by such licensor, or (ii) the Larger +Works (as defined below), to deal in both + +(a) the Software, and +(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +one is included with the Software (each a "Larger Work" to which the Software +is contributed by such licensors), + +without restriction, including without limitation the rights to copy, create +derivative works of, display, perform, and distribute the Software and make, +use, sell, offer for sale, import, export, have made, and have sold the +Software and the Larger Work(s), and to sublicense the foregoing rights on +either these or other terms. + +This license is subject to the following condition: +The above copyright notice and either this complete permission notice or at +a minimum a reference to the UPL must be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/oci-support-mcp-server/README.md b/src/oci-support-mcp-server/README.md new file mode 100644 index 00000000..e69de29b diff --git a/src/oci-support-mcp-server/oracle/__init__.py b/src/oci-support-mcp-server/oracle/__init__.py new file mode 100644 index 00000000..d9dff098 --- /dev/null +++ b/src/oci-support-mcp-server/oracle/__init__.py @@ -0,0 +1,5 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/__init__.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/__init__.py new file mode 100644 index 00000000..5237ada7 --- /dev/null +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/__init__.py @@ -0,0 +1,8 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +__project__ = "oracle.oci-support-mcp-server" +__version__ = "1.0.0" diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py new file mode 100644 index 00000000..72446217 --- /dev/null +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -0,0 +1,155 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from datetime import datetime +from typing import Any, Dict, List, Optional +from pydantic import BaseModel, Field + +# --- OCI Support CIMS Pydantic Models --- + +class Contact(BaseModel): + email: Optional[str] = Field(None) + name: Optional[str] = Field(None) + phone: Optional[str] = Field(None) + timezone: Optional[str] = Field(None) + country: Optional[str] = Field(None) + + +class Category(BaseModel): + name: Optional[str] = Field(None) + label: Optional[str] = Field(None) + + +class SubCategory(BaseModel): + name: Optional[str] = Field(None) + label: Optional[str] = Field(None) + + +class IncidentType(BaseModel): + name: Optional[str] = Field(None) + label: Optional[str] = Field(None) + category: Optional[Category] = Field(None) + sub_category: Optional[SubCategory] = Field(None) + + +class Resource(BaseModel): + item: Optional[str] = Field(None) + region: Optional[str] = Field(None) + availability_domain: Optional[str] = Field(None) + compartment_id: Optional[str] = Field(None) + + +class Ticket(BaseModel): + key: Optional[str] = Field(None) + summary: Optional[str] = Field(None) + status: Optional[str] = Field(None) + time_created: Optional[datetime] = Field(None) + time_updated: Optional[datetime] = Field(None) + + +class IncidentSummary(BaseModel): + key: Optional[str] = Field(None) + compartment_id: Optional[str] = Field(None) + ticket_number: Optional[str] = Field(None) + incident_type: Optional[IncidentType] = Field(None) + severity: Optional[str] = Field(None) + resource: Optional[Resource] = Field(None) + status: Optional[str] = Field(None) + time_created: Optional[datetime] = Field(None) + time_updated: Optional[datetime] = Field(None) + referrer: Optional[str] = Field(None) + tenancy_id: Optional[str] = Field(None) + contact_list: Optional[List[Contact]] = Field(None) + lifecycle_state: Optional[str] = Field(None) + ticket: Optional[str] = Field(None) + +# --- Mapping Utilities --- + +def map_contact(oci_contact) -> Contact | None: + if not oci_contact: + return None + return Contact( + email=getattr(oci_contact, "email", None), + name=getattr(oci_contact, "name", None), + phone=getattr(oci_contact, "phone", None), + timezone=getattr(oci_contact, "timezone", None), + country=getattr(oci_contact, "country", None), + ) + +def map_category(oci_category) -> Category | None: + if not oci_category: + return None + return Category( + name=getattr(oci_category, "name", None), + label=getattr(oci_category, "label", None), + ) + +def map_sub_category(oci_sub_category) -> SubCategory | None: + if not oci_sub_category: + return None + return SubCategory( + name=getattr(oci_sub_category, "name", None), + label=getattr(oci_sub_category, "label", None), + ) + +def map_incident_type(oci_incident_type) -> IncidentType | None: + if not oci_incident_type: + return None + return IncidentType( + name=getattr(oci_incident_type, "name", None), + label=getattr(oci_incident_type, "label", None), + category=map_category(getattr(oci_incident_type, "category", None)), + sub_category=map_sub_category(getattr(oci_incident_type, "sub_category", None)), + ) + +def map_resource(oci_resource) -> Resource | None: + if not oci_resource: + return None + return Resource( + item=getattr(oci_resource, "item", None), + region=getattr(oci_resource, "region", None), + availability_domain=getattr(oci_resource, "availability_domain", None), + compartment_id=getattr(oci_resource, "compartment_id", None), + ) + +def map_ticket(oci_ticket) -> Optional[str]: + if not oci_ticket: + return None + if isinstance(oci_ticket, str): + return oci_ticket + if isinstance(oci_ticket, dict): + # Try to get ticket['key'] first; else fallback to something unique or string repr + return oci_ticket.get("key") or oci_ticket.get("summary") or str(oci_ticket) + # Try attribute access (object with .key or .summary), else fallback to str(obj) + return getattr(oci_ticket, "key", None) or getattr(oci_ticket, "summary", None) or str(oci_ticket) + +def map_incident_summary(oci_incident_summary) -> IncidentSummary | None: + if not oci_incident_summary: + return None + + def get(field, default=None): + if isinstance(oci_incident_summary, dict): + return oci_incident_summary.get(field, default) + return getattr(oci_incident_summary, field, default) + + return IncidentSummary( + key=get("key"), + compartment_id=get("compartment_id"), + ticket_number=get("ticket_number"), + incident_type=map_incident_type(get("incident_type")), + severity=get("severity"), + resource=map_resource(get("resource")), + status=get("status"), + time_created=get("time_created"), + time_updated=get("time_updated"), + referrer=get("referrer"), + tenancy_id=get("tenancy_id"), + contact_list=[ + map_contact(c) for c in (get("contact_list") or []) + ], + lifecycle_state=get("lifecycle_state"), + ticket=map_ticket(get("ticket")), + ) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py new file mode 100644 index 00000000..ceb1e68e --- /dev/null +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py @@ -0,0 +1,195 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +import os +from logging import Logger +from typing import Annotated, List, Optional + +import oci +from fastmcp import FastMCP +from oracle.oci_support_mcp_server.models import ( + IncidentSummary, + map_incident_summary, +) +from pydantic import Field + +__project__ = "oracle.oci_support_mcp_server" +__version__ = "0.1.0" + +logger = Logger(__name__, level="INFO") + +mcp = FastMCP(name=__project__) + + +def get_cims_client(): + """ + Instantiate and return an OCI CIMS IncidentClient with API-key (fingerprint) based authentication only. + """ + config = oci.config.from_file( + profile_name=os.environ.get("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE) + ) + user_agent_name = __project__.split("oracle.", 1)[1].split("_mcp_server", 1)[0] + config["additional_user_agent"] = f"{user_agent_name}/{__version__}" + return oci.cims.IncidentClient(config) + + +@mcp.tool( + description="List support incidents for the tenancy using the OCI Support API (CIMS). Returns mapped IncidentSummary models." +) +def list_incidents( + csi: str = Field( + ..., + description="The Oracle Cloud Identifier (CSI) associated with the support account used to interact with Oracle Support.", + min_length=1, + ), + compartment_id: str = Field( + ..., + description="The OCID of the tenancy. Use the tenancy OCID here.", + min_length=1, + ), + ocid: str = Field( + ..., + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account. Required for OCI users.", + min_length=1, + ), + limit: Optional[int] = Field( + None, + description="The maximum number of items to return in a paginated call.", + ge=1 + ), + page: Optional[str] = Field( + None, + description="The pagination token for retrieving the next batch of results." + ), + problem_type: Optional[str] = Field( + None, + description="A filter to return only resources that match the specified problem type. Accepts values such as 'LIMIT', 'TECH', or 'ACCOUNT'." + ), + sort_by: Optional[str] = Field( + None, + description="The field by which to sort results. Values: 'dateUpdated', 'severity', 'resourceType', 'status'." + ), + sort_order: Optional[str] = Field( + None, + description="The sort order to use. Either 'ASC' (ascending) or 'DESC' (descending)." + ), + severity: Optional[str] = Field( + None, + description="A filter to return only incidents matching the specified severity." + ), + status: Optional[str] = Field( + None, + description="A filter to return only incidents matching the specified status." + ), + list_incident_resource_type: Optional[str] = Field( + None, + description="A filter to return only incidents related to the specified resource type." + ), + opc_request_id: Optional[str] = Field( + None, + description="The OPC request ID for tracing from the client. Optional." + ) +) -> List[IncidentSummary]: + """ + Lists the incidents for a tenancy from the OCI CIMS (Support) API, mapped to IncidentSummary Pydantic models. + """ + logger.info("Calling OCI CIMS IncidentClient.list_incidents") + try: + client = get_cims_client() + has_next_page = True + next_page = page + incidents: List[IncidentSummary] = [] + total_limit = limit if limit and limit > 0 else None + call_limit = min(limit, 1000) if limit else 1000 # CIMS API may cap at 1000 + + while has_next_page and (total_limit is None or len(incidents) < total_limit): + kwargs = { + "csi": csi, + "compartment_id": compartment_id, + "ocid": ocid, + "limit": call_limit, + "page": next_page + } + if problem_type: + kwargs["problem_type"] = problem_type + if sort_by: + kwargs["sort_by"] = sort_by + if sort_order: + kwargs["sort_order"] = sort_order + if severity: + kwargs["severity"] = severity + if status: + kwargs["status"] = status + if list_incident_resource_type: + kwargs["list_incident_resource_type"] = list_incident_resource_type + if opc_request_id: + kwargs["opc_request_id"] = opc_request_id + + response = client.list_incidents(**kwargs) + results = getattr(response, "data", []) + # 'items' property if present (SDK style), else treat as a simple list + items = getattr(results, "items", results) + # DEBUG: Write first raw item ticket to a file for inspection + # import json + # if items and len(items) > 0: + # raw_ticket = None + # first_item = items[0] + # if isinstance(first_item, dict): + # raw_ticket = first_item.get("ticket") + # elif hasattr(first_item, "ticket"): + # raw_ticket = getattr(first_item, "ticket") + # try: + # with open("/tmp/mcp_incident_ticket_debug.json", "w") as f: + # json.dump(raw_ticket, f, default=str, indent=2) + # except Exception as debug_exc: + # logger.error(f"Failed to write ticket debug info: {debug_exc}") + mapped = [map_incident_summary(i) for i in items] + # Sanity/sanitize the ticket field for every mapped incident + for inc in mapped: + if hasattr(inc, "ticket") and not (inc.ticket is None or isinstance(inc.ticket, str)): + t = inc.ticket + if isinstance(t, dict): + inc.ticket = t.get("key") or t.get("summary") or str(t) + else: + inc.ticket = str(t) + incidents.extend(mapped) + has_next_page = getattr(response, "has_next_page", False) + next_page = getattr(response, "next_page", None) + # Stop if we've hit user-supplied max + if total_limit is not None and len(incidents) >= total_limit: + incidents = incidents[:total_limit] + break + + logger.info(f"Returning {len(incidents)} IncidentSummary records") + # Output as plain dicts, forcibly ensuring ticket is a string for serialization + output = [] + for inc in incidents: + val = inc.model_dump() if hasattr(inc, "model_dump") else dict(inc) + ticket_val = val.get("ticket") + if ticket_val is not None and not isinstance(ticket_val, str): + if isinstance(ticket_val, dict): + val["ticket"] = ticket_val.get("key") or ticket_val.get("summary") or str(ticket_val) + else: + val["ticket"] = str(ticket_val) + output.append(val) + return output + + except Exception as e: + logger.error(f"Error in list_incidents tool: {str(e)}") + raise e + + +def main(): + host = os.getenv("ORACLE_MCP_HOST") + port = os.getenv("ORACLE_MCP_PORT") + if host and port: + mcp.run(transport="http", host=host, port=int(port)) + else: + mcp.run() + + +if __name__ == "__main__": + main() diff --git a/src/oci-support-mcp-server/pyproject.toml b/src/oci-support-mcp-server/pyproject.toml new file mode 100644 index 00000000..996bc327 --- /dev/null +++ b/src/oci-support-mcp-server/pyproject.toml @@ -0,0 +1,41 @@ +[project] +name = "oracle.oci-support-mcp-server" +version = "1.0.0" +description = "OCI Support Service MCP server" +readme = "README.md" +requires-python = ">=3.13" +license = "UPL-1.0" +license-files = ["LICENSE.txt"] +authors = [ + {name = "Oracle MCP", email = "237432095+oracle-mcp@users.noreply.github.com"}, +] +dependencies = [ + "fastmcp==2.13.0", + "oci==2.160.0", + "pydantic==2.12.3", +] + +classifiers = [ + "License :: OSI Approved :: Universal Permissive License (UPL)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.13", +] + +[project.scripts] +"oracle.oci-support-mcp-server" = "oracle.oci_support_mcp_server.server:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["oracle"] +include = ["oracle"] + +[dependency-groups] +dev = [ + "pytest>=8.4.2", + "pytest-asyncio>=1.2.0", + "pytest-cov>=7.0.0", +] diff --git a/src/oci-support-mcp-server/uv.lock b/src/oci-support-mcp-server/uv.lock new file mode 100644 index 00000000..be282048 --- /dev/null +++ b/src/oci-support-mcp-server/uv.lock @@ -0,0 +1,1418 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "authlib" +version = "1.6.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/9b/b1661026ff24bc641b76b78c5222d614776b0c085bcfdac9bd15a1cb4b35/authlib-1.6.6.tar.gz", hash = "sha256:45770e8e056d0f283451d9996fbb59b70d45722b45d854d58f32878d0a40c38e", size = 164894, upload-time = "2025-12-12T08:01:41.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/51/321e821856452f7386c4e9df866f196720b1ad0c5ea1623ea7399969ae3b/authlib-1.6.6-py2.py3-none-any.whl", hash = "sha256:7d9e9bc535c13974313a87f53e8430eb6ea3d1cf6ae4f6efcd793f2e949143fd", size = 244005, upload-time = "2025-12-12T08:01:40.209Z" }, +] + +[[package]] +name = "beartype" +version = "0.22.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/1d/794ae2acaa67c8b216d91d5919da2606c2bb14086849ffde7f5555f3a3a5/beartype-0.22.8.tar.gz", hash = "sha256:b19b21c9359722ee3f7cc433f063b3e13997b27ae8226551ea5062e621f61165", size = 1602262, upload-time = "2025-12-03T05:11:10.766Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2a/fbcbf5a025d3e71ddafad7efd43e34ec4362f4d523c3c471b457148fb211/beartype-0.22.8-py3-none-any.whl", hash = "sha256:b832882d04e41a4097bab9f63e6992bc6de58c414ee84cba9b45b67314f5ab2e", size = 1331895, upload-time = "2025-12-03T05:11:08.373Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/44/ca1675be2a83aeee1886ab745b28cda92093066590233cc501890eb8417a/cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6", size = 31571, upload-time = "2025-11-13T17:42:51.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "circuitbreaker" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/ac/de7a92c4ed39cba31fe5ad9203b76a25ca67c530797f6bb420fff5f65ccb/circuitbreaker-2.1.3.tar.gz", hash = "sha256:1a4baee510f7bea3c91b194dcce7c07805fe96c4423ed5594b75af438531d084", size = 10787, upload-time = "2025-03-31T08:12:08.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/34/15f08edd4628f65217de1fc3c1a27c82e46fe357d60c217fc9881e12ebcc/circuitbreaker-2.1.3-py3-none-any.whl", hash = "sha256:87ba6a3ed03fdc7032bc175561c2b04d52ade9d5faf94ca2b035fbdc5e6b1dd1", size = 7737, upload-time = "2025-03-31T08:12:07.802Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905, upload-time = "2025-12-08T13:14:38.055Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/cc/bce226595eb3bf7d13ccffe154c3c487a22222d87ff018525ab4dd2e9542/coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf", size = 218297, upload-time = "2025-12-08T13:13:10.977Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9f/73c4d34600aae03447dff3d7ad1d0ac649856bfb87d1ca7d681cfc913f9e/coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a", size = 218673, upload-time = "2025-12-08T13:13:12.562Z" }, + { url = "https://files.pythonhosted.org/packages/63/ab/8fa097db361a1e8586535ae5073559e6229596b3489ec3ef2f5b38df8cb2/coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74", size = 249652, upload-time = "2025-12-08T13:13:13.909Z" }, + { url = "https://files.pythonhosted.org/packages/90/3a/9bfd4de2ff191feb37ef9465855ca56a6f2f30a3bca172e474130731ac3d/coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6", size = 252251, upload-time = "2025-12-08T13:13:15.553Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/b5d8105f016e1b5874af0d7c67542da780ccd4a5f2244a433d3e20ceb1ad/coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b", size = 253492, upload-time = "2025-12-08T13:13:16.849Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b8/0fad449981803cc47a4694768b99823fb23632150743f9c83af329bb6090/coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232", size = 249850, upload-time = "2025-12-08T13:13:18.142Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e9/8d68337c3125014d918cf4327d5257553a710a2995a6a6de2ac77e5aa429/coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971", size = 251633, upload-time = "2025-12-08T13:13:19.56Z" }, + { url = "https://files.pythonhosted.org/packages/55/14/d4112ab26b3a1bc4b3c1295d8452dcf399ed25be4cf649002fb3e64b2d93/coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d", size = 249586, upload-time = "2025-12-08T13:13:20.883Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a9/22b0000186db663b0d82f86c2f1028099ae9ac202491685051e2a11a5218/coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137", size = 249412, upload-time = "2025-12-08T13:13:22.22Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2e/42d8e0d9e7527fba439acdc6ed24a2b97613b1dc85849b1dd935c2cffef0/coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511", size = 251191, upload-time = "2025-12-08T13:13:23.899Z" }, + { url = "https://files.pythonhosted.org/packages/a4/af/8c7af92b1377fd8860536aadd58745119252aaaa71a5213e5a8e8007a9f5/coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1", size = 220829, upload-time = "2025-12-08T13:13:25.182Z" }, + { url = "https://files.pythonhosted.org/packages/58/f9/725e8bf16f343d33cbe076c75dc8370262e194ff10072c0608b8e5cf33a3/coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a", size = 221640, upload-time = "2025-12-08T13:13:26.836Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ff/e98311000aa6933cc79274e2b6b94a2fe0fe3434fca778eba82003675496/coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6", size = 220269, upload-time = "2025-12-08T13:13:28.116Z" }, + { url = "https://files.pythonhosted.org/packages/cf/cf/bbaa2e1275b300343ea865f7d424cc0a2e2a1df6925a070b2b2d5d765330/coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a", size = 218990, upload-time = "2025-12-08T13:13:29.463Z" }, + { url = "https://files.pythonhosted.org/packages/21/1d/82f0b3323b3d149d7672e7744c116e9c170f4957e0c42572f0366dbb4477/coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8", size = 219340, upload-time = "2025-12-08T13:13:31.524Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/fe3fd4702a3832a255f4d43013eacb0ef5fc155a5960ea9269d8696db28b/coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053", size = 260638, upload-time = "2025-12-08T13:13:32.965Z" }, + { url = "https://files.pythonhosted.org/packages/ad/01/63186cb000307f2b4da463f72af9b85d380236965574c78e7e27680a2593/coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071", size = 262705, upload-time = "2025-12-08T13:13:34.378Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a1/c0dacef0cc865f2455d59eed3548573ce47ed603205ffd0735d1d78b5906/coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e", size = 265125, upload-time = "2025-12-08T13:13:35.73Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/82b99223628b61300bd382c205795533bed021505eab6dd86e11fb5d7925/coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493", size = 259844, upload-time = "2025-12-08T13:13:37.69Z" }, + { url = "https://files.pythonhosted.org/packages/cf/2c/89b0291ae4e6cd59ef042708e1c438e2290f8c31959a20055d8768349ee2/coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0", size = 262700, upload-time = "2025-12-08T13:13:39.525Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f9/a5f992efae1996245e796bae34ceb942b05db275e4b34222a9a40b9fbd3b/coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e", size = 260321, upload-time = "2025-12-08T13:13:41.172Z" }, + { url = "https://files.pythonhosted.org/packages/4c/89/a29f5d98c64fedbe32e2ac3c227fbf78edc01cc7572eee17d61024d89889/coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c", size = 259222, upload-time = "2025-12-08T13:13:43.282Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c3/940fe447aae302a6701ee51e53af7e08b86ff6eed7631e5740c157ee22b9/coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e", size = 261411, upload-time = "2025-12-08T13:13:44.72Z" }, + { url = "https://files.pythonhosted.org/packages/eb/31/12a4aec689cb942a89129587860ed4d0fd522d5fda81237147fde554b8ae/coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46", size = 221505, upload-time = "2025-12-08T13:13:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/65/8c/3b5fe3259d863572d2b0827642c50c3855d26b3aefe80bdc9eba1f0af3b0/coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39", size = 222569, upload-time = "2025-12-08T13:13:47.79Z" }, + { url = "https://files.pythonhosted.org/packages/b0/39/f71fa8316a96ac72fc3908839df651e8eccee650001a17f2c78cdb355624/coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e", size = 220841, upload-time = "2025-12-08T13:13:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4b/9b54bedda55421449811dcd5263a2798a63f48896c24dfb92b0f1b0845bd/coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256", size = 218343, upload-time = "2025-12-08T13:13:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/59/df/c3a1f34d4bba2e592c8979f924da4d3d4598b0df2392fbddb7761258e3dc/coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a", size = 218672, upload-time = "2025-12-08T13:13:52.284Z" }, + { url = "https://files.pythonhosted.org/packages/07/62/eec0659e47857698645ff4e6ad02e30186eb8afd65214fd43f02a76537cb/coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9", size = 249715, upload-time = "2025-12-08T13:13:53.791Z" }, + { url = "https://files.pythonhosted.org/packages/23/2d/3c7ff8b2e0e634c1f58d095f071f52ed3c23ff25be524b0ccae8b71f99f8/coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19", size = 252225, upload-time = "2025-12-08T13:13:55.274Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ac/fb03b469d20e9c9a81093575003f959cf91a4a517b783aab090e4538764b/coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be", size = 253559, upload-time = "2025-12-08T13:13:57.161Z" }, + { url = "https://files.pythonhosted.org/packages/29/62/14afa9e792383c66cc0a3b872a06ded6e4ed1079c7d35de274f11d27064e/coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb", size = 249724, upload-time = "2025-12-08T13:13:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/31/b7/333f3dab2939070613696ab3ee91738950f0467778c6e5a5052e840646b7/coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8", size = 251582, upload-time = "2025-12-08T13:14:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/81/cb/69162bda9381f39b2287265d7e29ee770f7c27c19f470164350a38318764/coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b", size = 249538, upload-time = "2025-12-08T13:14:02.556Z" }, + { url = "https://files.pythonhosted.org/packages/e0/76/350387b56a30f4970abe32b90b2a434f87d29f8b7d4ae40d2e8a85aacfb3/coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9", size = 249349, upload-time = "2025-12-08T13:14:04.015Z" }, + { url = "https://files.pythonhosted.org/packages/86/0d/7f6c42b8d59f4c7e43ea3059f573c0dcfed98ba46eb43c68c69e52ae095c/coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927", size = 251011, upload-time = "2025-12-08T13:14:05.505Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f1/4bb2dff379721bb0b5c649d5c5eaf438462cad824acf32eb1b7ca0c7078e/coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f", size = 221091, upload-time = "2025-12-08T13:14:07.127Z" }, + { url = "https://files.pythonhosted.org/packages/ba/44/c239da52f373ce379c194b0ee3bcc121020e397242b85f99e0afc8615066/coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc", size = 221904, upload-time = "2025-12-08T13:14:08.542Z" }, + { url = "https://files.pythonhosted.org/packages/89/1f/b9f04016d2a29c2e4a0307baefefad1a4ec5724946a2b3e482690486cade/coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b", size = 220480, upload-time = "2025-12-08T13:14:10.958Z" }, + { url = "https://files.pythonhosted.org/packages/16/d4/364a1439766c8e8647860584171c36010ca3226e6e45b1753b1b249c5161/coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28", size = 219074, upload-time = "2025-12-08T13:14:13.345Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f4/71ba8be63351e099911051b2089662c03d5671437a0ec2171823c8e03bec/coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe", size = 219342, upload-time = "2025-12-08T13:14:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/5e/25/127d8ed03d7711a387d96f132589057213e3aef7475afdaa303412463f22/coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657", size = 260713, upload-time = "2025-12-08T13:14:16.907Z" }, + { url = "https://files.pythonhosted.org/packages/fd/db/559fbb6def07d25b2243663b46ba9eb5a3c6586c0c6f4e62980a68f0ee1c/coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff", size = 262825, upload-time = "2025-12-08T13:14:18.68Z" }, + { url = "https://files.pythonhosted.org/packages/37/99/6ee5bf7eff884766edb43bd8736b5e1c5144d0fe47498c3779326fe75a35/coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3", size = 265233, upload-time = "2025-12-08T13:14:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/d8/90/92f18fe0356ea69e1f98f688ed80cec39f44e9f09a1f26a1bbf017cc67f2/coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b", size = 259779, upload-time = "2025-12-08T13:14:22.367Z" }, + { url = "https://files.pythonhosted.org/packages/90/5d/b312a8b45b37a42ea7d27d7d3ff98ade3a6c892dd48d1d503e773503373f/coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d", size = 262700, upload-time = "2025-12-08T13:14:24.309Z" }, + { url = "https://files.pythonhosted.org/packages/63/f8/b1d0de5c39351eb71c366f872376d09386640840a2e09b0d03973d791e20/coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e", size = 260302, upload-time = "2025-12-08T13:14:26.068Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7c/d42f4435bc40c55558b3109a39e2d456cddcec37434f62a1f1230991667a/coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940", size = 259136, upload-time = "2025-12-08T13:14:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d3/23413241dc04d47cfe19b9a65b32a2edd67ecd0b817400c2843ebc58c847/coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2", size = 261467, upload-time = "2025-12-08T13:14:29.09Z" }, + { url = "https://files.pythonhosted.org/packages/13/e6/6e063174500eee216b96272c0d1847bf215926786f85c2bd024cf4d02d2f/coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7", size = 221875, upload-time = "2025-12-08T13:14:31.106Z" }, + { url = "https://files.pythonhosted.org/packages/3b/46/f4fb293e4cbe3620e3ac2a3e8fd566ed33affb5861a9b20e3dd6c1896cbc/coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc", size = 222982, upload-time = "2025-12-08T13:14:33.1Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/5b3b9018215ed9733fbd1ae3b2ed75c5de62c3b55377a52cae732e1b7805/coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a", size = 221016, upload-time = "2025-12-08T13:14:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068, upload-time = "2025-12-08T13:14:36.236Z" }, +] + +[[package]] +name = "cryptography" +version = "44.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281, upload-time = "2025-05-02T19:34:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305, upload-time = "2025-05-02T19:34:53.042Z" }, + { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040, upload-time = "2025-05-02T19:34:54.675Z" }, + { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411, upload-time = "2025-05-02T19:34:56.61Z" }, + { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263, upload-time = "2025-05-02T19:34:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198, upload-time = "2025-05-02T19:35:00.988Z" }, + { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502, upload-time = "2025-05-02T19:35:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173, upload-time = "2025-05-02T19:35:05.018Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713, upload-time = "2025-05-02T19:35:07.187Z" }, + { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064, upload-time = "2025-05-02T19:35:08.879Z" }, + { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887, upload-time = "2025-05-02T19:35:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737, upload-time = "2025-05-02T19:35:12.12Z" }, + { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501, upload-time = "2025-05-02T19:35:13.775Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307, upload-time = "2025-05-02T19:35:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876, upload-time = "2025-05-02T19:35:18.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127, upload-time = "2025-05-02T19:35:19.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164, upload-time = "2025-05-02T19:35:21.449Z" }, + { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081, upload-time = "2025-05-02T19:35:23.187Z" }, + { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716, upload-time = "2025-05-02T19:35:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398, upload-time = "2025-05-02T19:35:27.678Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900, upload-time = "2025-05-02T19:35:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067, upload-time = "2025-05-02T19:35:31.547Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467, upload-time = "2025-05-02T19:35:33.805Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375, upload-time = "2025-05-02T19:35:35.369Z" }, +] + +[[package]] +name = "cyclopts" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "docstring-parser" }, + { name = "rich" }, + { name = "rich-rst" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/0f/fe026df2ab8301e30a2b0bd425ff1462ad858fd4f991c1ac0389c2059c24/cyclopts-4.3.0.tar.gz", hash = "sha256:e95179cd0a959ce250ecfb2f0262a5996a92c1f9467bccad2f3d829e6833cef5", size = 151411, upload-time = "2025-11-25T02:59:33.572Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/e8/77a231ae531cf38765b75ddf27dae28bb5f70b41d8bb4f15ce1650e93f57/cyclopts-4.3.0-py3-none-any.whl", hash = "sha256:91a30b69faf128ada7cfeaefd7d9649dc222e8b2a8697f1fc99e4ee7b7ca44f3", size = 187184, upload-time = "2025-11-25T02:59:32.21Z" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "docstring-parser" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/02/111134bfeb6e6c7ac4c74594e39a59f6c0195dc4846afbeac3cba60f1927/docutils-0.22.3.tar.gz", hash = "sha256:21486ae730e4ca9f622677b1412b879af1791efcfba517e4c6f60be543fc8cdd", size = 2290153, upload-time = "2025-11-06T02:35:55.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl", hash = "sha256:bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb", size = 633032, upload-time = "2025-11-06T02:35:52.391Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "fastmcp" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "authlib" }, + { name = "cyclopts" }, + { name = "exceptiongroup" }, + { name = "httpx" }, + { name = "mcp" }, + { name = "openapi-core" }, + { name = "openapi-pydantic" }, + { name = "platformdirs" }, + { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, + { name = "pydantic", extra = ["email"] }, + { name = "pyperclip" }, + { name = "python-dotenv" }, + { name = "rich" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/3b/c30af894db2c3ec439d0e4168ba7ce705474cabdd0a599033ad9a19ad977/fastmcp-2.13.0.tar.gz", hash = "sha256:57f7b7503363e1babc0d1a13af18252b80366a409e1de85f1256cce66a4bee35", size = 7767346, upload-time = "2025-10-25T12:54:10.957Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/7f/09942135f506953fc61bb81b9e5eaf50a8eea923b83d9135bd959168ef2d/fastmcp-2.13.0-py3-none-any.whl", hash = "sha256:bdff1399d3b7ebb79286edfd43eb660182432514a5ab8e4cbfb45f1d841d2aa0", size = 367134, upload-time = "2025-10-25T12:54:09.284Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912, upload-time = "2024-08-20T03:39:27.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825, upload-time = "2024-08-20T03:39:25.966Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-path" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathable" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159, upload-time = "2025-01-24T14:33:16.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8", size = 14810, upload-time = "2025-01-24T14:33:14.652Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" }, + { url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" }, + { url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" }, + { url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" }, + { url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" }, + { url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" }, + { url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" }, + { url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" }, + { url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3a/277857b51ae419a1574557c0b12e0d06bf327b758ba94cafc664cb1e2f66/lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3", size = 26582, upload-time = "2025-08-22T13:49:49.366Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/c5e0fa43535bb9c87880e0ba037cdb1c50e01850b0831e80eb4f4762f270/lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a", size = 71059, upload-time = "2025-08-22T13:49:50.488Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/7dcad19c685963c652624702f1a968ff10220b16bfcc442257038216bf55/lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a", size = 71034, upload-time = "2025-08-22T13:49:54.224Z" }, + { url = "https://files.pythonhosted.org/packages/12/ac/34cbfb433a10e28c7fd830f91c5a348462ba748413cbb950c7f259e67aa7/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95", size = 69529, upload-time = "2025-08-22T13:49:55.29Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6a/11ad7e349307c3ca4c0175db7a77d60ce42a41c60bcb11800aabd6a8acb8/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5", size = 70391, upload-time = "2025-08-22T13:49:56.35Z" }, + { url = "https://files.pythonhosted.org/packages/59/97/9b410ed8fbc6e79c1ee8b13f8777a80137d4bc189caf2c6202358e66192c/lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f", size = 26988, upload-time = "2025-08-22T13:49:57.302Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mcp" +version = "1.23.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a4/d06a303f45997e266f2c228081abe299bbcba216cb806128e2e49095d25f/mcp-1.23.3.tar.gz", hash = "sha256:b3b0da2cc949950ce1259c7bfc1b081905a51916fcd7c8182125b85e70825201", size = 600697, upload-time = "2025-12-09T16:04:37.351Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/c6/13c1a26b47b3f3a3b480783001ada4268917c9f42d78a079c336da2e75e5/mcp-1.23.3-py3-none-any.whl", hash = "sha256:32768af4b46a1b4f7df34e2bfdf5c6011e7b63d7f1b0e321d0fdef4cd6082031", size = 231570, upload-time = "2025-12-09T16:04:35.56Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + +[[package]] +name = "oci" +version = "2.160.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "circuitbreaker" }, + { name = "cryptography" }, + { name = "pyopenssl" }, + { name = "python-dateutil" }, + { name = "pytz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/7b/c9d7fc28f11c25c7875db3584eab5d52ccb2d7df553d07ac47f19a14d075/oci-2.160.0.tar.gz", hash = "sha256:f8e3410204c1405b40247179550cf74f5145a8e17025c4f2a92f2b9ffdc7d26b", size = 15601606, upload-time = "2025-09-09T04:17:43.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/51/752375a4e0d2de371c2788414157eda337417010d2ef7383cd7140388f1e/oci-2.160.0-py3-none-any.whl", hash = "sha256:3dba1ec671ebea23f255fabf836cb0fd08aea0913a8df85610fccaa5a4344ee9", size = 31715365, upload-time = "2025-09-09T04:17:34.998Z" }, +] + +[[package]] +name = "openapi-core" +version = "0.19.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "isodate" }, + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "more-itertools" }, + { name = "openapi-schema-validator" }, + { name = "openapi-spec-validator" }, + { name = "parse" }, + { name = "typing-extensions" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/35/1acaa5f2fcc6e54eded34a2ec74b479439c4e469fc4e8d0e803fda0234db/openapi_core-0.19.5.tar.gz", hash = "sha256:421e753da56c391704454e66afe4803a290108590ac8fa6f4a4487f4ec11f2d3", size = 103264, upload-time = "2025-03-20T20:17:28.193Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/6f/83ead0e2e30a90445ee4fc0135f43741aebc30cca5b43f20968b603e30b6/openapi_core-0.19.5-py3-none-any.whl", hash = "sha256:ef7210e83a59394f46ce282639d8d26ad6fc8094aa904c9c16eb1bac8908911f", size = 106595, upload-time = "2025-03-20T20:17:26.77Z" }, +] + +[[package]] +name = "openapi-pydantic" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, +] + +[[package]] +name = "openapi-schema-validator" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-specifications" }, + { name = "rfc3339-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550, upload-time = "2025-01-10T18:08:22.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755, upload-time = "2025-01-10T18:08:19.758Z" }, +] + +[[package]] +name = "openapi-spec-validator" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "lazy-object-proxy" }, + { name = "openapi-schema-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855, upload-time = "2025-06-07T14:48:56.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" }, +] + +[[package]] +name = "oracle-oci-support-mcp-server" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "fastmcp" }, + { name = "oci" }, + { name = "pydantic" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastmcp", specifier = "==2.13.0" }, + { name = "oci", specifier = "==2.160.0" }, + { name = "pydantic", specifier = "==2.12.3" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.4.2" }, + { name = "pytest-asyncio", specifier = ">=1.2.0" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "parse" +version = "1.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" }, +] + +[[package]] +name = "pathable" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124, upload-time = "2025-01-10T18:43:13.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" }, +] + +[[package]] +name = "pathvalidate" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "py-key-value-aio" +version = "0.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "py-key-value-shared" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/35/65310a4818acec0f87a46e5565e341c5a96fc062a9a03495ad28828ff4d7/py_key_value_aio-0.2.8.tar.gz", hash = "sha256:c0cfbb0bd4e962a3fa1a9fa6db9ba9df812899bd9312fa6368aaea7b26008b36", size = 32853, upload-time = "2025-10-24T13:31:04.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/5a/e56747d87a97ad2aff0f3700d77f186f0704c90c2da03bfed9e113dae284/py_key_value_aio-0.2.8-py3-none-any.whl", hash = "sha256:561565547ce8162128fd2bd0b9d70ce04a5f4586da8500cce79a54dfac78c46a", size = 69200, upload-time = "2025-10-24T13:31:03.81Z" }, +] + +[package.optional-dependencies] +disk = [ + { name = "diskcache" }, + { name = "pathvalidate" }, +] +keyring = [ + { name = "keyring" }, +] +memory = [ + { name = "cachetools" }, +] + +[[package]] +name = "py-key-value-shared" +version = "0.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/79/05a1f9280cfa0709479319cbfd2b1c5beb23d5034624f548c83fb65b0b61/py_key_value_shared-0.2.8.tar.gz", hash = "sha256:703b4d3c61af124f0d528ba85995c3c8d78f8bd3d2b217377bd3278598070cc1", size = 8216, upload-time = "2025-10-24T13:31:03.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/7a/1726ceaa3343874f322dd83c9ec376ad81f533df8422b8b1e1233a59f8ce/py_key_value_shared-0.2.8-py3-none-any.whl", hash = "sha256:aff1bbfd46d065b2d67897d298642e80e5349eae588c6d11b48452b46b8d46ba", size = 14586, upload-time = "2025-10-24T13:31:02.838Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pyopenssl" +version = "24.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/d4/1067b82c4fc674d6f6e9e8d26b3dff978da46d351ca3bac171544693e085/pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36", size = 178944, upload-time = "2024-11-27T20:43:12.755Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/22/40f9162e943f86f0fc927ebc648078be87def360d9d8db346619fb97df2b/pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a", size = 56111, upload-time = "2024-11-27T20:43:21.112Z" }, +] + +[[package]] +name = "pyperclip" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { 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, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + +[[package]] +name = "rich-rst" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/3c/fa6517610dc641262b77cc7bf994ecd17465812c1b0585fe33e11be758ab/sse_starlette-3.0.3.tar.gz", hash = "sha256:88cfb08747e16200ea990c8ca876b03910a23b547ab3bd764c0d8eb81019b971", size = 21943, upload-time = "2025-10-30T18:44:20.117Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/a0/984525d19ca5c8a6c33911a0c164b11490dd0f90ff7fd689f704f84e9a11/sse_starlette-3.0.3-py3-none-any.whl", hash = "sha256:af5bf5a6f3933df1d9c7f8539633dc8444ca6a97ab2e2a7cd3b6e431ac03a431", size = 11765, upload-time = "2025-10-30T18:44:18.834Z" }, +] + +[[package]] +name = "starlette" +version = "0.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7175d764718d5183caf8d0867a4f0190d5d4a45cea49/werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4", size = 806453, upload-time = "2024-11-01T16:40:45.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" }, +] From 24e3cc11f3cf488bb9bd2267ffbe7087720e28ef Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Mon, 5 Jan 2026 14:15:33 +0530 Subject: [PATCH 02/13] 1. Created Incident model 2. MCP tool function for get incident --- .../oracle/oci_support_mcp_server/models.py | 99 ++++++++++++++++++- .../oracle/oci_support_mcp_server/server.py | 84 +++++++++++++--- 2 files changed, 168 insertions(+), 15 deletions(-) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py index 72446217..c85073c6 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -50,6 +50,44 @@ class Ticket(BaseModel): time_updated: Optional[datetime] = Field(None) +class Impact(BaseModel): + type: Optional[str] = Field(None) + description: Optional[str] = Field(None) + +class User(BaseModel): + name: Optional[str] = Field(None) + email: Optional[str] = Field(None) + contact_number: Optional[str] = Field(None) + country: Optional[str] = Field(None) + +class Context(BaseModel): + context_type: Optional[str] = Field(None) + description: Optional[str] = Field(None) + additional_details: Optional[dict] = Field(None) + +class Incident(BaseModel): + key: Optional[str] = Field(None) + compartment_id: Optional[str] = Field(None) + ticket_number: Optional[str] = Field(None) + incident_type: Optional[IncidentType] = Field(None) + severity: Optional[str] = Field(None) + status: Optional[str] = Field(None) + lifecycle_state: Optional[str] = Field(None) + resource: Optional[Resource] = Field(None) + resources: Optional[List[Resource]] = Field(None) + time_created: Optional[datetime] = Field(None) + time_updated: Optional[datetime] = Field(None) + referrer: Optional[str] = Field(None) + tenancy_id: Optional[str] = Field(None) + contact_list: Optional[List[Contact]] = Field(None) + ticket: Optional[str] = Field(None) + description: Optional[str] = Field(None) + impact: Optional[Impact] = Field(None) + context: Optional[Context] = Field(None) + user: Optional[User] = Field(None) + department: Optional[str] = Field(None) + problem_type: Optional[str] = Field(None) + class IncidentSummary(BaseModel): key: Optional[str] = Field(None) compartment_id: Optional[str] = Field(None) @@ -65,9 +103,68 @@ class IncidentSummary(BaseModel): contact_list: Optional[List[Contact]] = Field(None) lifecycle_state: Optional[str] = Field(None) ticket: Optional[str] = Field(None) - # --- Mapping Utilities --- +def map_impact(oci_impact) -> Impact | None: + if not oci_impact: + return None + return Impact( + type=getattr(oci_impact, "type", None), + description=getattr(oci_impact, "description", None), + ) + +def map_user(oci_user) -> User | None: + if not oci_user: + return None + return User( + name=getattr(oci_user, "name", None), + email=getattr(oci_user, "email", None), + contact_number=getattr(oci_user, "contact_number", None), + country=getattr(oci_user, "country", None), + ) + +def map_context(oci_context) -> Context | None: + if not oci_context: + return None + return Context( + context_type=getattr(oci_context, "context_type", None), + description=getattr(oci_context, "description", None), + additional_details=getattr(oci_context, "additional_details", None), + ) + +def map_incident(oci_incident) -> Incident | None: + if not oci_incident: + return None + + def get(field, default=None): + if isinstance(oci_incident, dict): + return oci_incident.get(field, default) + return getattr(oci_incident, field, default) + + return Incident( + key=get("key"), + compartment_id=get("compartment_id"), + ticket_number=get("ticket_number"), + incident_type=map_incident_type(get("incident_type")), + severity=get("severity"), + status=get("status"), + lifecycle_state=get("lifecycle_state"), + resource=map_resource(get("resource")), + resources=[map_resource(r) for r in (get("resources") or [])] if get("resources") else None, + time_created=get("time_created"), + time_updated=get("time_updated"), + referrer=get("referrer"), + tenancy_id=get("tenancy_id"), + contact_list=[map_contact(c) for c in (get("contact_list") or [])] if get("contact_list") else None, + ticket=map_ticket(get("ticket")), + description=get("description"), + impact=map_impact(get("impact")), + context=map_context(get("context")), + user=map_user(get("user")), + department=get("department"), + problem_type=get("problem_type"), + ) + def map_contact(oci_contact) -> Contact | None: if not oci_contact: return None diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py index ceb1e68e..1974956e 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py @@ -13,6 +13,8 @@ from oracle.oci_support_mcp_server.models import ( IncidentSummary, map_incident_summary, + Incident, + map_incident, ) from pydantic import Field @@ -132,20 +134,6 @@ def list_incidents( results = getattr(response, "data", []) # 'items' property if present (SDK style), else treat as a simple list items = getattr(results, "items", results) - # DEBUG: Write first raw item ticket to a file for inspection - # import json - # if items and len(items) > 0: - # raw_ticket = None - # first_item = items[0] - # if isinstance(first_item, dict): - # raw_ticket = first_item.get("ticket") - # elif hasattr(first_item, "ticket"): - # raw_ticket = getattr(first_item, "ticket") - # try: - # with open("/tmp/mcp_incident_ticket_debug.json", "w") as f: - # json.dump(raw_ticket, f, default=str, indent=2) - # except Exception as debug_exc: - # logger.error(f"Failed to write ticket debug info: {debug_exc}") mapped = [map_incident_summary(i) for i in items] # Sanity/sanitize the ticket field for every mapped incident for inc in mapped: @@ -182,6 +170,74 @@ def list_incidents( raise e +@mcp.tool( + description="Get a support incident from OCI CIMS by key. Returns mapped Incident Pydantic model." +) +def get_incident( + csi: str = Field( + ..., + description="The Oracle Cloud Identifier (CSI) associated with the support account used to interact with Oracle Support.", + min_length=1, + ), + compartment_id: str = Field( + ..., + description="The OCID of the tenancy. Use the tenancy OCID here.", + min_length=1, + ), + ocid: str = Field( + ..., + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account. Required for OCI users.", + min_length=1, + ), + incident_key: str = Field( + ..., + description="The unique key or ticket number identifying the incident.", + min_length=1, + ), + problemtype: Optional[str] = Field( + None, + description="A filter to return only resources that match the specified problem type. Accepts values such as 'LIMIT', 'TECH', or 'ACCOUNT'." + ), + opc_request_id: Optional[str] = Field( + None, + description="The OPC request ID for tracing from the client. Optional." + ) +) -> Incident: + """ + Retrieves a specific support incident from OCI CIMS using the incident key, mapped to the Incident Pydantic model. + Optionally filter by problem_type (e.g., 'LIMIT', 'TECH', or 'ACCOUNT'). + """ + logger.info(f"Calling OCI CIMS IncidentClient.get_incident for key {incident_key}") + try: + client = get_cims_client() + kwargs = { + "csi": csi, + "compartment_id": compartment_id, + "ocid": ocid, + "incident_key": incident_key, + } + if problemtype: + kwargs["problemtype"] = problemtype + if opc_request_id: + kwargs["opc_request_id"] = opc_request_id + + response = client.get_incident(**kwargs) + incident_data = getattr(response, "data", None) + mapped = map_incident(incident_data) + if mapped is None: + return None + val = mapped.model_dump() if hasattr(mapped, "model_dump") else dict(mapped) + ticket_val = val.get("ticket") + if ticket_val is not None and not isinstance(ticket_val, str): + if isinstance(ticket_val, dict): + val["ticket"] = ticket_val.get("key") or ticket_val.get("summary") or str(ticket_val) + else: + val["ticket"] = str(ticket_val) + return val + except Exception as e: + logger.error(f"Error in get_incident tool: {str(e)}") + raise e + def main(): host = os.getenv("ORACLE_MCP_HOST") port = os.getenv("ORACLE_MCP_PORT") From 4852d9de15a4c4c5415973fc72e2b813e3804ca0 Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Mon, 5 Jan 2026 20:06:54 +0530 Subject: [PATCH 03/13] Update models.py with pydantic models for IncidentSummary --- .../oracle/oci_support_mcp_server/models.py | 247 ++++++++++++++---- 1 file changed, 198 insertions(+), 49 deletions(-) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py index c85073c6..085a80b0 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -11,21 +11,28 @@ # --- OCI Support CIMS Pydantic Models --- class Contact(BaseModel): + contact_name: Optional[str] = Field(None) + contact_email: Optional[str] = Field(None) email: Optional[str] = Field(None) - name: Optional[str] = Field(None) - phone: Optional[str] = Field(None) - timezone: Optional[str] = Field(None) - country: Optional[str] = Field(None) + contact_phone: Optional[str] = Field(None) + contact_type: Optional[str] = Field(None) +class ContactList(BaseModel): + contact_list: List[Contact] -class Category(BaseModel): - name: Optional[str] = Field(None) + +class IssueType(BaseModel): + issue_type_key: Optional[str] = Field(None) label: Optional[str] = Field(None) + name: Optional[str] = Field(None) +class Category(BaseModel): + category_key: Optional[str] = Field(None) + name: Optional[str] = Field(None) class SubCategory(BaseModel): + sub_category_key: Optional[str] = Field(None) name: Optional[str] = Field(None) - label: Optional[str] = Field(None) class IncidentType(BaseModel): @@ -35,19 +42,29 @@ class IncidentType(BaseModel): sub_category: Optional[SubCategory] = Field(None) +class Item(BaseModel): + item_key: Optional[str] = Field(None) + name: Optional[str] = Field(None) + type: Optional[str] = Field(None) + category: Optional[Category] = Field(None) + sub_category: Optional[SubCategory] = Field(None) + issue_type: Optional[IssueType] = Field(None) + class Resource(BaseModel): - item: Optional[str] = Field(None) + item: Optional[Item] = Field(None) region: Optional[str] = Field(None) - availability_domain: Optional[str] = Field(None) - compartment_id: Optional[str] = Field(None) class Ticket(BaseModel): - key: Optional[str] = Field(None) - summary: Optional[str] = Field(None) - status: Optional[str] = Field(None) - time_created: Optional[datetime] = Field(None) - time_updated: Optional[datetime] = Field(None) + ticket_number: Optional[str] = Field(None) + severity: Optional[str] = Field(None) + resource_list: Optional[List[Resource]] = Field(None) + title: Optional[str] = Field(None) + description: Optional[str] = Field(None) + time_created: Optional[int] = Field(None) + time_updated: Optional[int] = Field(None) + lifecycle_state: Optional[str] = Field(None) + lifecycle_details: Optional[str] = Field(None) class Impact(BaseModel): @@ -88,21 +105,61 @@ class Incident(BaseModel): department: Optional[str] = Field(None) problem_type: Optional[str] = Field(None) +class TenancyInformation(BaseModel): + customer_support_key: Optional[str] = Field(None) + tenancy_id: Optional[str] = Field(None) + +class ServiceCategory(BaseModel): + key: Optional[str] = Field(None) + name: Optional[str] = Field(None) + label: Optional[str] = Field(None) + description: Optional[str] = Field(None) + issue_type_list: Optional[List[IssueType]] = Field(None) + supported_subscriptions: Optional[List[str]] = Field(None) + scope: Optional[str] = Field(None) + unit: Optional[str] = Field(None) + limit_id: Optional[str] = Field(None) + +class SubComponents(BaseModel): + sub_category: Optional[Dict[str, str]] = Field(None) + schema: Optional[str] = Field(None) + +class SubCategories(BaseModel): + service_category: Optional[Dict[str, str]] = Field(None) + schema: Optional[str] = Field(None) + has_sub_category: Optional[str] = Field(None) + sub_categories: Optional[List[SubComponents]] = Field(None) + +class Services(BaseModel): + service: Optional[Dict[str, str]] = Field(None) + schema: Optional[str] = Field(None) + service_categories: Optional[List[SubCategories]] = Field(None) + +class IncidentResourceType(BaseModel): + resource_type_key: Optional[str] = Field(None) + name: Optional[str] = Field(None) + label: Optional[str] = Field(None) + description: Optional[str] = Field(None) + is_subscriptions_supported: Optional[bool] = Field(None) + service_category_list: Optional[List[ServiceCategory]] = Field(None) + service: Optional[Dict[str, str]] = Field(None) + services: Optional[List[Services]] = Field(None) + class IncidentSummary(BaseModel): key: Optional[str] = Field(None) compartment_id: Optional[str] = Field(None) - ticket_number: Optional[str] = Field(None) - incident_type: Optional[IncidentType] = Field(None) - severity: Optional[str] = Field(None) - resource: Optional[Resource] = Field(None) - status: Optional[str] = Field(None) - time_created: Optional[datetime] = Field(None) - time_updated: Optional[datetime] = Field(None) - referrer: Optional[str] = Field(None) - tenancy_id: Optional[str] = Field(None) - contact_list: Optional[List[Contact]] = Field(None) - lifecycle_state: Optional[str] = Field(None) - ticket: Optional[str] = Field(None) + contact_list: Optional[ContactList] = Field(None) + tenancy_information: Optional[TenancyInformation] = Field(None) + ticket: Optional[Ticket] = Field(None) + incident_type: Optional[IncidentResourceType] = Field(None) + migrated_sr_number: Optional[str] = Field(None) + user_group_id: Optional[str] = Field(None) + user_group_name: Optional[str] = Field(None) + primary_contact_party_id: Optional[str] = Field(None) + primary_contact_party_name: Optional[str] = Field(None) + is_write_permitted: Optional[bool] = Field(None) + warn_message: Optional[str] = Field(None) + problem_type: Optional[str] = Field(None) # --- Mapping Utilities --- def map_impact(oci_impact) -> Impact | None: @@ -168,28 +225,50 @@ def get(field, default=None): def map_contact(oci_contact) -> Contact | None: if not oci_contact: return None + def get(field, default=None): + if isinstance(oci_contact, dict): + return oci_contact.get(field, default) + return getattr(oci_contact, field, default) return Contact( - email=getattr(oci_contact, "email", None), - name=getattr(oci_contact, "name", None), - phone=getattr(oci_contact, "phone", None), - timezone=getattr(oci_contact, "timezone", None), - country=getattr(oci_contact, "country", None), + contact_name=get("contact_name"), + contact_email=get("contact_email"), + email=get("email"), + contact_phone=get("contact_phone"), + contact_type=get("contact_type"), ) +def map_contact_list(oci_contact_list) -> ContactList | None: + if not oci_contact_list: + return None + def get(field, default=None): + if isinstance(oci_contact_list, dict): + return oci_contact_list.get(field, default) + return getattr(oci_contact_list, field, default) + cl = get("contact_list") + return ContactList(contact_list=[map_contact(c) for c in (cl or [])]) + def map_category(oci_category) -> Category | None: if not oci_category: return None + def get(field, default=None): + if isinstance(oci_category, dict): + return oci_category.get(field, default) + return getattr(oci_category, field, default) return Category( - name=getattr(oci_category, "name", None), - label=getattr(oci_category, "label", None), + category_key=get("category_key"), + name=get("name"), ) def map_sub_category(oci_sub_category) -> SubCategory | None: if not oci_sub_category: return None + def get(field, default=None): + if isinstance(oci_sub_category, dict): + return oci_sub_category.get(field, default) + return getattr(oci_sub_category, field, default) return SubCategory( - name=getattr(oci_sub_category, "name", None), - label=getattr(oci_sub_category, "label", None), + sub_category_key=get("sub_category_key"), + name=get("name"), ) def map_incident_type(oci_incident_type) -> IncidentType | None: @@ -223,6 +302,78 @@ def map_ticket(oci_ticket) -> Optional[str]: # Try attribute access (object with .key or .summary), else fallback to str(obj) return getattr(oci_ticket, "key", None) or getattr(oci_ticket, "summary", None) or str(oci_ticket) +def map_tenancy_information(oci_ti) -> TenancyInformation | None: + if not oci_ti: + return None + def get(field, default=None): + if isinstance(oci_ti, dict): + return oci_ti.get(field, default) + return getattr(oci_ti, field, default) + return TenancyInformation( + customer_support_key=get("customer_support_key"), + tenancy_id=get("tenancy_id") + ) + +def map_issue_type(oci_issue_type) -> IssueType | None: + if not oci_issue_type: + return None + def get(field, default=None): + if isinstance(oci_issue_type, dict): + return oci_issue_type.get(field, default) + return getattr(oci_issue_type, field, default) + return IssueType( + issue_type_key=get("issue_type_key"), + label=get("label"), + name=get("name"), + ) + +def map_item(oci_item) -> Item | None: + if not oci_item: + return None + def get(field, default=None): + if isinstance(oci_item, dict): + return oci_item.get(field, default) + return getattr(oci_item, field, default) + return Item( + item_key=get("item_key"), + name=get("name"), + type=get("type"), + category=map_category(get("category")), + sub_category=map_sub_category(get("sub_category")), + issue_type=map_issue_type(get("issue_type")), + ) + +def map_resource(oci_resource) -> Resource | None: + if not oci_resource: + return None + def get(field, default=None): + if isinstance(oci_resource, dict): + return oci_resource.get(field, default) + return getattr(oci_resource, field, default) + return Resource( + item=map_item(get("item")), + region=get("region"), + ) + +def map_ticket(oci_ticket) -> Ticket | None: + if not oci_ticket: + return None + def get(field, default=None): + if isinstance(oci_ticket, dict): + return oci_ticket.get(field, default) + return getattr(oci_ticket, field, default) + return Ticket( + ticket_number=get("ticket_number"), + severity=get("severity"), + resource_list=[map_resource(r) for r in (get("resource_list") or [])], + title=get("title"), + description=get("description"), + time_created=get("time_created"), + time_updated=get("time_updated"), + lifecycle_state=get("lifecycle_state"), + lifecycle_details=get("lifecycle_details"), + ) + def map_incident_summary(oci_incident_summary) -> IncidentSummary | None: if not oci_incident_summary: return None @@ -235,18 +386,16 @@ def get(field, default=None): return IncidentSummary( key=get("key"), compartment_id=get("compartment_id"), - ticket_number=get("ticket_number"), - incident_type=map_incident_type(get("incident_type")), - severity=get("severity"), - resource=map_resource(get("resource")), - status=get("status"), - time_created=get("time_created"), - time_updated=get("time_updated"), - referrer=get("referrer"), - tenancy_id=get("tenancy_id"), - contact_list=[ - map_contact(c) for c in (get("contact_list") or []) - ], - lifecycle_state=get("lifecycle_state"), + contact_list=map_contact_list(get("contact_list")), + tenancy_information=map_tenancy_information(get("tenancy_information")), ticket=map_ticket(get("ticket")), + incident_type=get("incident_type"), # for now, adjust if explicit mapping is required + migrated_sr_number=get("migrated_sr_number"), + user_group_id=get("user_group_id"), + user_group_name=get("user_group_name"), + primary_contact_party_id=get("primary_contact_party_id"), + primary_contact_party_name=get("primary_contact_party_name"), + is_write_permitted=get("is_write_permitted"), + warn_message=get("warn_message"), + problem_type=get("problem_type"), ) From 59f8fe49b3cba07ddcdaa334098835b184adb96b Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Tue, 6 Jan 2026 12:18:36 +0530 Subject: [PATCH 04/13] list_incidents request and response update --- .../oracle/oci_support_mcp_server/models.py | 32 +++- .../oracle/oci_support_mcp_server/server.py | 180 ++++++++++-------- 2 files changed, 128 insertions(+), 84 deletions(-) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py index 085a80b0..756f0674 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -58,7 +58,7 @@ class Resource(BaseModel): class Ticket(BaseModel): ticket_number: Optional[str] = Field(None) severity: Optional[str] = Field(None) - resource_list: Optional[List[Resource]] = Field(None) + resource_list: Optional[List[dict]] = Field(None) # dict instead of Resource for serialization safety title: Optional[str] = Field(None) description: Optional[str] = Field(None) time_created: Optional[int] = Field(None) @@ -334,13 +334,22 @@ def get(field, default=None): if isinstance(oci_item, dict): return oci_item.get(field, default) return getattr(oci_item, field, default) + mapped_category = map_category(get("category")) + if mapped_category is not None and hasattr(mapped_category, "model_dump"): + mapped_category = mapped_category.model_dump(exclude_none=True) + mapped_sub_category = map_sub_category(get("sub_category")) + if mapped_sub_category is not None and hasattr(mapped_sub_category, "model_dump"): + mapped_sub_category = mapped_sub_category.model_dump(exclude_none=True) + mapped_issue_type = map_issue_type(get("issue_type")) + if mapped_issue_type is not None and hasattr(mapped_issue_type, "model_dump"): + mapped_issue_type = mapped_issue_type.model_dump(exclude_none=True) return Item( item_key=get("item_key"), name=get("name"), type=get("type"), - category=map_category(get("category")), - sub_category=map_sub_category(get("sub_category")), - issue_type=map_issue_type(get("issue_type")), + category=mapped_category, + sub_category=mapped_sub_category, + issue_type=mapped_issue_type, ) def map_resource(oci_resource) -> Resource | None: @@ -350,8 +359,11 @@ def get(field, default=None): if isinstance(oci_resource, dict): return oci_resource.get(field, default) return getattr(oci_resource, field, default) + mapped_item = map_item(get("item")) + if mapped_item is not None and hasattr(mapped_item, "model_dump"): + mapped_item = mapped_item.model_dump(exclude_none=True) return Resource( - item=map_item(get("item")), + item=mapped_item, region=get("region"), ) @@ -362,10 +374,18 @@ def get(field, default=None): if isinstance(oci_ticket, dict): return oci_ticket.get(field, default) return getattr(oci_ticket, field, default) + resource_list = [] + for r in get("resource_list") or []: + mapped = map_resource(r) + if mapped is not None: + # Ensure mapped is dict for serialization + if hasattr(mapped, "model_dump"): + mapped = mapped.model_dump(exclude_none=True) + resource_list.append(mapped) return Ticket( ticket_number=get("ticket_number"), severity=get("severity"), - resource_list=[map_resource(r) for r in (get("resource_list") or [])], + resource_list=resource_list if resource_list else None, title=get("title"), description=get("description"), time_created=get("time_created"), diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py index 1974956e..df734400 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py @@ -38,133 +38,157 @@ def get_cims_client(): return oci.cims.IncidentClient(config) + @mcp.tool( description="List support incidents for the tenancy using the OCI Support API (CIMS). Returns mapped IncidentSummary models." ) def list_incidents( - csi: str = Field( - ..., - description="The Oracle Cloud Identifier (CSI) associated with the support account used to interact with Oracle Support.", - min_length=1, - ), compartment_id: str = Field( ..., - description="The OCID of the tenancy. Use the tenancy OCID here.", - min_length=1, + description="The OCID of the tenancy." ), - ocid: str = Field( - ..., - description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account. Required for OCI users.", - min_length=1, + csi: Optional[str] = Field( + None, + description="The Customer Support Identifier (CSI) number associated with the support account. Optional for all request types." + ), + ocid: Optional[str] = Field( + None, + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account. Required for OCI users, optional for Multicloud users." ), limit: Optional[int] = Field( None, - description="The maximum number of items to return in a paginated call.", - ge=1 + description="For list pagination. The maximum number of results per page, or items to return in a paginated List call." + ), + sort_by: Optional[str] = Field( + None, + description="The key to use to sort the returned items. Allowed values: 'dateUpdated', 'severity'." + ), + sort_order: Optional[str] = Field( + None, + description="The order to sort the results in. Allowed values: 'ASC', 'DESC'." + ), + lifecycle_state: Optional[str] = Field( + None, + description="The current state of the ticket. Allowed values: 'ACTIVE', 'CLOSED'." ), page: Optional[str] = Field( None, - description="The pagination token for retrieving the next batch of results." + description="For list pagination. The value of the 'opc-next-page' response header from the previous List call." + ), + opc_request_id: Optional[str] = Field( + None, + description="Unique Oracle-assigned identifier for the request. If you need to contact Oracle about a particular request, provide this ID." + ), + homeregion: Optional[str] = Field( + None, + description="The region of the tenancy." ), problem_type: Optional[str] = Field( None, description="A filter to return only resources that match the specified problem type. Accepts values such as 'LIMIT', 'TECH', or 'ACCOUNT'." ), - sort_by: Optional[str] = Field( + bearertokentype: Optional[str] = Field( None, - description="The field by which to sort results. Values: 'dateUpdated', 'severity', 'resourceType', 'status'." + description="Token type that determines which cloud provider the request comes from." ), - sort_order: Optional[str] = Field( + bearertoken: Optional[str] = Field( None, - description="The sort order to use. Either 'ASC' (ascending) or 'DESC' (descending)." + description="Token provided by multi cloud provider, which helps to validate the email." ), - severity: Optional[str] = Field( + idtoken: Optional[str] = Field( None, - description="A filter to return only incidents matching the specified severity." + description="IdToken provided by multi cloud provider, which helps to validate the email." ), - status: Optional[str] = Field( + domainid: Optional[str] = Field( None, - description="A filter to return only incidents matching the specified status." + description="The OCID of identity domain. DomainID is mandatory if the user is part of Non Default Identity domain." ), - list_incident_resource_type: Optional[str] = Field( + allow_control_chars: Optional[bool] = Field( None, - description="A filter to return only incidents related to the specified resource type." + description="Set to True to allow control characters in the response object." ), - opc_request_id: Optional[str] = Field( + retry_strategy: Optional[str] = Field( None, - description="The OPC request ID for tracing from the client. Optional." + description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default. (Advanced/experimental.)" ) ) -> List[IncidentSummary]: """ - Lists the incidents for a tenancy from the OCI CIMS (Support) API, mapped to IncidentSummary Pydantic models. + Lists OCI support incidents using the OCI CIMS IncidentClient. Returns a list of mapped IncidentSummary models. + All available filter, pagination, and context parameters are supported as per Oracle SDK documentation. """ logger.info("Calling OCI CIMS IncidentClient.list_incidents") try: client = get_cims_client() - has_next_page = True - next_page = page - incidents: List[IncidentSummary] = [] - total_limit = limit if limit and limit > 0 else None - call_limit = min(limit, 1000) if limit else 1000 # CIMS API may cap at 1000 + results = [] + remaining = limit if limit is not None else None + next_page_token = page - while has_next_page and (total_limit is None or len(incidents) < total_limit): + while True: kwargs = { - "csi": csi, "compartment_id": compartment_id, - "ocid": ocid, - "limit": call_limit, - "page": next_page } - if problem_type: - kwargs["problem_type"] = problem_type - if sort_by: + if csi is not None: + kwargs["csi"] = csi + if ocid is not None: + kwargs["ocid"] = ocid + if remaining is not None: + kwargs["limit"] = remaining + if sort_by is not None: kwargs["sort_by"] = sort_by - if sort_order: + if sort_order is not None: kwargs["sort_order"] = sort_order - if severity: - kwargs["severity"] = severity - if status: - kwargs["status"] = status - if list_incident_resource_type: - kwargs["list_incident_resource_type"] = list_incident_resource_type - if opc_request_id: + if lifecycle_state is not None: + kwargs["lifecycle_state"] = lifecycle_state + if next_page_token: + kwargs["page"] = next_page_token + if opc_request_id is not None: kwargs["opc_request_id"] = opc_request_id + if homeregion is not None: + kwargs["homeregion"] = homeregion + if problem_type is not None: + kwargs["problem_type"] = problem_type + if bearertokentype is not None: + kwargs["bearertokentype"] = bearertokentype + if bearertoken is not None: + kwargs["bearertoken"] = bearertoken + if idtoken is not None: + kwargs["idtoken"] = idtoken + if domainid is not None: + kwargs["domainid"] = domainid + if allow_control_chars is not None: + kwargs["allow_control_chars"] = allow_control_chars + + if retry_strategy is not None: + import oci.retry + if retry_strategy == "default": + kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY + elif retry_strategy == "none": + kwargs["retry_strategy"] = oci.retry.NoneRetryStrategy response = client.list_incidents(**kwargs) - results = getattr(response, "data", []) - # 'items' property if present (SDK style), else treat as a simple list - items = getattr(results, "items", results) - mapped = [map_incident_summary(i) for i in items] - # Sanity/sanitize the ticket field for every mapped incident - for inc in mapped: - if hasattr(inc, "ticket") and not (inc.ticket is None or isinstance(inc.ticket, str)): - t = inc.ticket - if isinstance(t, dict): - inc.ticket = t.get("key") or t.get("summary") or str(t) - else: - inc.ticket = str(t) - incidents.extend(mapped) + data = getattr(response, "data", []) or [] + mapped = [map_incident_summary(x) for x in data if x is not None] + # Convert to dicts for serialization, using mode="json" to recursively dump submodels + mapped_dicts = [m.model_dump(exclude_none=True, mode="json") for m in mapped if m is not None] + results.extend(mapped_dicts) + has_next_page = getattr(response, "has_next_page", False) - next_page = getattr(response, "next_page", None) - # Stop if we've hit user-supplied max - if total_limit is not None and len(incidents) >= total_limit: - incidents = incidents[:total_limit] + next_page_token = getattr(response, "next_page", None) + + # If we respected a limit, trim the results and break if reached + if remaining is not None and len(results) >= limit: + results = results[:limit] + break + if not has_next_page or not next_page_token: break - logger.info(f"Returning {len(incidents)} IncidentSummary records") - # Output as plain dicts, forcibly ensuring ticket is a string for serialization - output = [] - for inc in incidents: - val = inc.model_dump() if hasattr(inc, "model_dump") else dict(inc) - ticket_val = val.get("ticket") - if ticket_val is not None and not isinstance(ticket_val, str): - if isinstance(ticket_val, dict): - val["ticket"] = ticket_val.get("key") or ticket_val.get("summary") or str(ticket_val) - else: - val["ticket"] = str(ticket_val) - output.append(val) - return output + if remaining is not None: + remaining = limit - len(results) + if remaining <= 0: + break + logger.info(f"Found {len(results)} incident summaries") + return results except Exception as e: logger.error(f"Error in list_incidents tool: {str(e)}") raise e From d3774e215ac5e4f7d6d1a3274def7b9833cb8740 Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Tue, 6 Jan 2026 12:59:31 +0530 Subject: [PATCH 05/13] Models.py and server.py update for GetIncident operation --- .../oracle/oci_support_mcp_server/models.py | 111 +++++++----------- .../oracle/oci_support_mcp_server/server.py | 104 ++++++++++------ 2 files changed, 116 insertions(+), 99 deletions(-) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py index 756f0674..c66a6120 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -67,10 +67,6 @@ class Ticket(BaseModel): lifecycle_details: Optional[str] = Field(None) -class Impact(BaseModel): - type: Optional[str] = Field(None) - description: Optional[str] = Field(None) - class User(BaseModel): name: Optional[str] = Field(None) email: Optional[str] = Field(None) @@ -82,28 +78,6 @@ class Context(BaseModel): description: Optional[str] = Field(None) additional_details: Optional[dict] = Field(None) -class Incident(BaseModel): - key: Optional[str] = Field(None) - compartment_id: Optional[str] = Field(None) - ticket_number: Optional[str] = Field(None) - incident_type: Optional[IncidentType] = Field(None) - severity: Optional[str] = Field(None) - status: Optional[str] = Field(None) - lifecycle_state: Optional[str] = Field(None) - resource: Optional[Resource] = Field(None) - resources: Optional[List[Resource]] = Field(None) - time_created: Optional[datetime] = Field(None) - time_updated: Optional[datetime] = Field(None) - referrer: Optional[str] = Field(None) - tenancy_id: Optional[str] = Field(None) - contact_list: Optional[List[Contact]] = Field(None) - ticket: Optional[str] = Field(None) - description: Optional[str] = Field(None) - impact: Optional[Impact] = Field(None) - context: Optional[Context] = Field(None) - user: Optional[User] = Field(None) - department: Optional[str] = Field(None) - problem_type: Optional[str] = Field(None) class TenancyInformation(BaseModel): customer_support_key: Optional[str] = Field(None) @@ -160,15 +134,26 @@ class IncidentSummary(BaseModel): is_write_permitted: Optional[bool] = Field(None) warn_message: Optional[str] = Field(None) problem_type: Optional[str] = Field(None) + +class Incident(BaseModel): + key: Optional[str] = Field(None) + compartment_id: Optional[str] = Field(None) + contact_list: Optional[ContactList] = Field(None) + tenancy_information: Optional[TenancyInformation] = Field(None) + ticket: Optional[Ticket] = Field(None) + incident_type: Optional[IncidentType] = Field(None) + migrated_sr_number: Optional[str] = Field(None) + user_group_id: Optional[str] = Field(None) + user_group_name: Optional[str] = Field(None) + primary_contact_party_id: Optional[str] = Field(None) + primary_contact_party_name: Optional[str] = Field(None) + is_write_permitted: Optional[bool] = Field(None) + warn_message: Optional[str] = Field(None) + problem_type: Optional[str] = Field(None) + referrer: Optional[str] = Field(None) + # --- Mapping Utilities --- -def map_impact(oci_impact) -> Impact | None: - if not oci_impact: - return None - return Impact( - type=getattr(oci_impact, "type", None), - description=getattr(oci_impact, "description", None), - ) def map_user(oci_user) -> User | None: if not oci_user: @@ -189,39 +174,6 @@ def map_context(oci_context) -> Context | None: additional_details=getattr(oci_context, "additional_details", None), ) -def map_incident(oci_incident) -> Incident | None: - if not oci_incident: - return None - - def get(field, default=None): - if isinstance(oci_incident, dict): - return oci_incident.get(field, default) - return getattr(oci_incident, field, default) - - return Incident( - key=get("key"), - compartment_id=get("compartment_id"), - ticket_number=get("ticket_number"), - incident_type=map_incident_type(get("incident_type")), - severity=get("severity"), - status=get("status"), - lifecycle_state=get("lifecycle_state"), - resource=map_resource(get("resource")), - resources=[map_resource(r) for r in (get("resources") or [])] if get("resources") else None, - time_created=get("time_created"), - time_updated=get("time_updated"), - referrer=get("referrer"), - tenancy_id=get("tenancy_id"), - contact_list=[map_contact(c) for c in (get("contact_list") or [])] if get("contact_list") else None, - ticket=map_ticket(get("ticket")), - description=get("description"), - impact=map_impact(get("impact")), - context=map_context(get("context")), - user=map_user(get("user")), - department=get("department"), - problem_type=get("problem_type"), - ) - def map_contact(oci_contact) -> Contact | None: if not oci_contact: return None @@ -419,3 +371,30 @@ def get(field, default=None): warn_message=get("warn_message"), problem_type=get("problem_type"), ) + +def map_incident(oci_incident) -> Incident | None: + if not oci_incident: + return None + + def get(field, default=None): + if isinstance(oci_incident, dict): + return oci_incident.get(field, default) + return getattr(oci_incident, field, default) + + return Incident( + key=get("key"), + compartment_id=get("compartment_id"), + contact_list=map_contact_list(get("contact_list")), + tenancy_information=map_tenancy_information(get("tenancy_information")), + ticket=map_ticket(get("ticket")), + incident_type=map_incident_type(get("incident_type")), + migrated_sr_number=get("migrated_sr_number"), + user_group_id=get("user_group_id"), + user_group_name=get("user_group_name"), + primary_contact_party_id=get("primary_contact_party_id"), + primary_contact_party_name=get("primary_contact_party_name"), + is_write_permitted=get("is_write_permitted"), + warn_message=get("warn_message"), + problem_type=get("problem_type"), + referrer=get("referrer"), + ) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py index df734400..66b2401d 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py @@ -195,73 +195,111 @@ def list_incidents( @mcp.tool( - description="Get a support incident from OCI CIMS by key. Returns mapped Incident Pydantic model." + description="Get the specified support incident from OCI CIMS. Returns mapped Incident Pydantic model." ) def get_incident( - csi: str = Field( + incident_key: str = Field( ..., - description="The Oracle Cloud Identifier (CSI) associated with the support account used to interact with Oracle Support.", - min_length=1, + description="Unique identifier for the support request." ), compartment_id: str = Field( ..., - description="The OCID of the tenancy. Use the tenancy OCID here.", - min_length=1, + description="The OCID of the tenancy." ), - ocid: str = Field( - ..., - description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account. Required for OCI users.", - min_length=1, + opc_request_id: Optional[str] = Field( + None, + description="Unique Oracle-assigned identifier for the request. Provide this if needing to contact Oracle about a particular request." ), - incident_key: str = Field( - ..., - description="The unique key or ticket number identifying the incident.", - min_length=1, + csi: Optional[str] = Field( + None, + description="The Customer Support Identifier (CSI) number associated with the support account." + ), + ocid: Optional[str] = Field( + None, + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated OCI account. Required for OCI users, optional for Multicloud." + ), + homeregion: Optional[str] = Field( + None, + description="The region of the tenancy." ), problemtype: Optional[str] = Field( None, description="A filter to return only resources that match the specified problem type. Accepts values such as 'LIMIT', 'TECH', or 'ACCOUNT'." ), - opc_request_id: Optional[str] = Field( + bearertokentype: Optional[str] = Field( None, - description="The OPC request ID for tracing from the client. Optional." + description="Token type that determines which cloud provider the request comes from." + ), + bearertoken: Optional[str] = Field( + None, + description="Token provided by multi cloud provider, which helps to validate the email." + ), + idtoken: Optional[str] = Field( + None, + description="IdToken provided by multi cloud provider, which helps to validate the email." + ), + domainid: Optional[str] = Field( + None, + description="The OCID of identity domain. DomainID is mandatory if the user is part of Non Default Identity domain." + ), + allow_control_chars: Optional[bool] = Field( + None, + description="Set to True to allow control characters in the response object." + ), + retry_strategy: Optional[str] = Field( + None, + description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default. (Advanced/experimental.)" ) ) -> Incident: """ - Retrieves a specific support incident from OCI CIMS using the incident key, mapped to the Incident Pydantic model. - Optionally filter by problem_type (e.g., 'LIMIT', 'TECH', or 'ACCOUNT'). + Gets a support request from OCI CIMS by incident_key. Returns mapped Incident Pydantic model. """ logger.info(f"Calling OCI CIMS IncidentClient.get_incident for key {incident_key}") try: client = get_cims_client() kwargs = { - "csi": csi, - "compartment_id": compartment_id, - "ocid": ocid, "incident_key": incident_key, + "compartment_id": compartment_id, } - if problemtype: - kwargs["problemtype"] = problemtype - if opc_request_id: + if opc_request_id is not None: kwargs["opc_request_id"] = opc_request_id + if csi is not None: + kwargs["csi"] = csi + if ocid is not None: + kwargs["ocid"] = ocid + if homeregion is not None: + kwargs["homeregion"] = homeregion + if problemtype is not None: + kwargs["problemtype"] = problemtype + if bearertokentype is not None: + kwargs["bearertokentype"] = bearertokentype + if bearertoken is not None: + kwargs["bearertoken"] = bearertoken + if idtoken is not None: + kwargs["idtoken"] = idtoken + if domainid is not None: + kwargs["domainid"] = domainid + if allow_control_chars is not None: + kwargs["allow_control_chars"] = allow_control_chars + + if retry_strategy is not None: + import oci.retry + if retry_strategy == "default": + kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY + elif retry_strategy == "none": + kwargs["retry_strategy"] = oci.retry.NoneRetryStrategy response = client.get_incident(**kwargs) incident_data = getattr(response, "data", None) mapped = map_incident(incident_data) if mapped is None: - return None - val = mapped.model_dump() if hasattr(mapped, "model_dump") else dict(mapped) - ticket_val = val.get("ticket") - if ticket_val is not None and not isinstance(ticket_val, str): - if isinstance(ticket_val, dict): - val["ticket"] = ticket_val.get("key") or ticket_val.get("summary") or str(ticket_val) - else: - val["ticket"] = str(ticket_val) - return val + return Incident() + return mapped except Exception as e: logger.error(f"Error in get_incident tool: {str(e)}") raise e + def main(): host = os.getenv("ORACLE_MCP_HOST") port = os.getenv("ORACLE_MCP_PORT") From c9ccd9b3a7c5493e9be0e4c8297896a235e0e35f Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Wed, 7 Jan 2026 15:11:59 +0530 Subject: [PATCH 06/13] 1. Created Pydantic models for CreateIncident operation 2. Created MCP tool function for create incident operation --- .../oracle/oci_support_mcp_server/models.py | 235 +++++++++++++++++- .../oracle/oci_support_mcp_server/server.py | 90 +++++++ 2 files changed, 324 insertions(+), 1 deletion(-) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py index c66a6120..e9e387b4 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -152,9 +152,135 @@ class Incident(BaseModel): problem_type: Optional[str] = Field(None) referrer: Optional[str] = Field(None) -# --- Mapping Utilities --- +class ContextualData(BaseModel): + client_id: Optional[str] = Field(None) + schema_name: Optional[str] = Field(None) + schema_version: Optional[str] = Field(None) + payload: Optional[str] = Field(None) + +class CreateCategoryDetails(BaseModel): + category_key: Optional[str] = Field(None) + +class CreateSubCategoryDetails(BaseModel): + sub_category_key: Optional[str] = Field(None) + +class CreateIssueTypeDetails(BaseModel): + issue_type_key: Optional[str] = Field(None) + +class CreateItemDetails(BaseModel): + type: Optional[str] = Field(None) + category: Optional[CreateCategoryDetails] = Field(None) + sub_category: Optional[CreateSubCategoryDetails] = Field(None) + issue_type: Optional[CreateIssueTypeDetails] = Field(None) + name: Optional[str] = Field(None) + +class CreateResourceDetails(BaseModel): + item: Optional[CreateItemDetails] = Field(None) + region: Optional[str] = Field(None) + +class CreateTicketDetails(BaseModel): + severity: Optional[str] = Field(None) + resource_list: Optional[List[CreateResourceDetails]] = Field(None) + title: Optional[str] = Field(None) + description: Optional[str] = Field(None) + contextual_data: Optional[ContextualData] = Field(None) +class CreateIncident(BaseModel): + compartment_id: Optional[str] = Field(None) + ticket: Optional[CreateTicketDetails] = Field(None) + csi: Optional[str] = Field(None) + user_group_id: Optional[str] = Field(None) + problem_type: Optional[str] = Field(None) + contacts: Optional[List[Contact]] = Field(None) + referrer: Optional[str] = Field(None) + + +# --- Conversion to OCI SDK Model Utilities --- + +def to_oci_create_category_details(p): + if p is None: + return None + import oci.cims.models + return oci.cims.models.CreateCategoryDetails(category_key=p.category_key) + +def to_oci_create_sub_category_details(p): + if p is None: + return None + import oci.cims.models + return oci.cims.models.CreateSubCategoryDetails(sub_category_key=p.sub_category_key) + +def to_oci_create_issue_type_details(p): + if p is None: + return None + import oci.cims.models + return oci.cims.models.CreateIssueTypeDetails(issue_type_key=p.issue_type_key) + +def to_oci_create_item_details(p): + if p is None: + return None + import oci.cims.models + return oci.cims.models.CreateItemDetails( + type=p.type, + category=to_oci_create_category_details(p.category) if hasattr(p, "category") else None, + sub_category=to_oci_create_sub_category_details(p.sub_category) if hasattr(p, "sub_category") else None, + issue_type=to_oci_create_issue_type_details(p.issue_type) if hasattr(p, "issue_type") else None, + name=p.name + ) + +def to_oci_create_resource_details(p): + if p is None: + return None + import oci.cims.models + return oci.cims.models.CreateResourceDetails( + item=to_oci_create_item_details(p.item) if hasattr(p, "item") else None, + region=p.region + ) + +def to_oci_contextual_data(p): + if p is None: + return None + import oci.cims.models + return oci.cims.models.ContextualData( + client_id=p.client_id, + schema_name=p.schema_name, + schema_version=p.schema_version, + payload=p.payload + ) + +def to_oci_create_ticket_details(p): + if p is None: + return None + import oci.cims.models + resource_list = [ + to_oci_create_resource_details(r) + for r in (p.resource_list or []) + ] if p.resource_list else None + contextual_data = to_oci_contextual_data(p.contextual_data) if p.contextual_data else None + return oci.cims.models.CreateTicketDetails( + severity=p.severity, + resource_list=resource_list, + title=p.title, + description=p.description, + contextual_data=contextual_data + ) + +def to_oci_create_incident(p): + if p is None: + return None + import oci.cims.models + ticket = to_oci_create_ticket_details(p.ticket) if p.ticket else None + return oci.cims.models.CreateIncident( + compartment_id=p.compartment_id, + ticket=ticket, + csi=p.csi, + user_group_id=p.user_group_id, + problem_type=p.problem_type, + contacts=[c.model_dump(exclude_none=True) for c in (p.contacts or [])] if getattr(p, "contacts", None) else None, + referrer=p.referrer + ) + +# --- Mapping Utilities --- def map_user(oci_user) -> User | None: if not oci_user: return None @@ -371,6 +497,113 @@ def get(field, default=None): warn_message=get("warn_message"), problem_type=get("problem_type"), ) +def map_contextual_data(oci_ctx) -> ContextualData | None: + if not oci_ctx: + return None + def get(field, default=None): + if isinstance(oci_ctx, dict): + return oci_ctx.get(field, default) + return getattr(oci_ctx, field, default) + return ContextualData( + client_id=get("client_id"), + schema_name=get("schema_name"), + schema_version=get("schema_version"), + payload=get("payload"), + ) + +def map_create_category_details(oci_cat) -> CreateCategoryDetails | None: + if not oci_cat: + return None + def get(field, default=None): + if isinstance(oci_cat, dict): + return oci_cat.get(field, default) + return getattr(oci_cat, field, default) + return CreateCategoryDetails( + category_key=get("category_key"), + ) + +def map_create_sub_category_details(oci_subcat) -> CreateSubCategoryDetails | None: + if not oci_subcat: + return None + def get(field, default=None): + if isinstance(oci_subcat, dict): + return oci_subcat.get(field, default) + return getattr(oci_subcat, field, default) + return CreateSubCategoryDetails( + sub_category_key=get("sub_category_key"), + ) + +def map_create_issue_type_details(oci_iss) -> CreateIssueTypeDetails | None: + if not oci_iss: + return None + def get(field, default=None): + if isinstance(oci_iss, dict): + return oci_iss.get(field, default) + return getattr(oci_iss, field, default) + return CreateIssueTypeDetails( + issue_type_key=get("issue_type_key"), + ) + +def map_create_item_details(oci_item) -> CreateItemDetails | None: + if not oci_item: + return None + def get(field, default=None): + if isinstance(oci_item, dict): + return oci_item.get(field, default) + return getattr(oci_item, field, default) + return CreateItemDetails( + type=get("type"), + category=map_create_category_details(get("category")), + sub_category=map_create_sub_category_details(get("sub_category")), + issue_type=map_create_issue_type_details(get("issue_type")), + name=get("name"), + ) + +def map_create_resource_details(oci_crd) -> CreateResourceDetails | None: + if not oci_crd: + return None + def get(field, default=None): + if isinstance(oci_crd, dict): + return oci_crd.get(field, default) + return getattr(oci_crd, field, default) + return CreateResourceDetails( + item=map_create_item_details(get("item")), + region=get("region"), + ) + +def map_create_ticket_details(oci_ticket) -> CreateTicketDetails | None: + if not oci_ticket: + return None + def get(field, default=None): + if isinstance(oci_ticket, dict): + return oci_ticket.get(field, default) + return getattr(oci_ticket, field, default) + return CreateTicketDetails( + severity=get("severity"), + resource_list=[ + map_create_resource_details(r) for r in (get("resource_list") or []) + ] if get("resource_list") else None, + title=get("title"), + description=get("description"), + contextual_data=map_contextual_data(get("contextual_data")), + ) + +def map_create_incident(oci_incident) -> CreateIncident | None: + if not oci_incident: + return None + def get(field, default=None): + if isinstance(oci_incident, dict): + return oci_incident.get(field, default) + return getattr(oci_incident, field, default) + return CreateIncident( + compartment_id=get("compartment_id"), + ticket=map_create_ticket_details(get("ticket")), + csi=get("csi"), + user_group_id=get("user_group_id"), + problem_type=get("problem_type"), + contacts=[map_contact(c) for c in get("contacts") or []] if get("contacts") else None, + referrer=get("referrer"), + ) def map_incident(oci_incident) -> Incident | None: if not oci_incident: diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py index 66b2401d..a46cf373 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py @@ -15,6 +15,7 @@ map_incident_summary, Incident, map_incident, + CreateIncident, ) from pydantic import Field @@ -300,6 +301,95 @@ def get_incident( raise e +@mcp.tool( + description="Create a support incident (OCI CIMS) with given incident details. Returns mapped Incident Pydantic model." +) +def create_incident( + create_incident_details: CreateIncident = Field( + ..., + description="Incident information to create request. Pydantic model matching oci.cims.models.CreateIncident." + ), + opc_request_id: Optional[str] = Field( + None, + description="Unique Oracle-assigned identifier for the request." + ), + ocid: Optional[str] = Field( + None, + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account. Mandatory for OCI users, optional for Multicloud users." + ), + homeregion: Optional[str] = Field( + None, + description="The region of the tenancy." + ), + bearertokentype: Optional[str] = Field( + None, + description="Token type that determines which cloud provider the request comes from." + ), + bearertoken: Optional[str] = Field( + None, + description="Token provided by multi cloud provider, which helps to validate the email." + ), + idtoken: Optional[str] = Field( + None, + description="IdToken provided by multi cloud provider, which helps to validate the email." + ), + domainid: Optional[str] = Field( + None, + description="The OCID of identity domain. Mandatory if the user is part of Non Default Identity domain." + ), + allow_control_chars: Optional[bool] = Field( + None, + description="Set to True to allow control characters in the response object." + ), + retry_strategy: Optional[str] = Field( + None, + description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default. (Advanced/experimental.)" + ) +) -> Incident: + """ + Create a support incident in OCI CIMS. Receives a CreateIncident Pydantic model, converts to OCI SDK model, and maps the Incident SDK response to API Pydantic. + """ + logger.info("Calling OCI CIMS IncidentClient.create_incident") + try: + client = get_cims_client() + # Use recursive converter for SDK CreateIncident object + from oracle.oci_support_mcp_server.models import to_oci_create_incident + sdk_create_incident = to_oci_create_incident(create_incident_details) + kwargs = {"create_incident_details": sdk_create_incident} + if opc_request_id is not None: + kwargs["opc_request_id"] = opc_request_id + if ocid is not None: + kwargs["ocid"] = ocid + if homeregion is not None: + kwargs["homeregion"] = homeregion + if bearertokentype is not None: + kwargs["bearertokentype"] = bearertokentype + if bearertoken is not None: + kwargs["bearertoken"] = bearertoken + if idtoken is not None: + kwargs["idtoken"] = idtoken + if domainid is not None: + kwargs["domainid"] = domainid + if allow_control_chars is not None: + kwargs["allow_control_chars"] = allow_control_chars + + if retry_strategy is not None: + import oci.retry + if retry_strategy == "default": + kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY + elif retry_strategy == "none": + kwargs["retry_strategy"] = oci.retry.NoneRetryStrategy + + response = client.create_incident(**kwargs) + incident_data = getattr(response, "data", None) + mapped = map_incident(incident_data) + if mapped is None: + return Incident() + return mapped + except Exception as e: + logger.error(f"Error in create_incident tool: {str(e)}") + raise e + def main(): host = os.getenv("ORACLE_MCP_HOST") port = os.getenv("ORACLE_MCP_PORT") From d1008b56158c48422ceaba52dd8d7d34336a2406 Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Wed, 7 Jan 2026 16:00:15 +0530 Subject: [PATCH 07/13] 1. Map functions for IncidentResourceType 2. MCP Tool function for List IncidentResourceType API --- .../oracle/oci_support_mcp_server/models.py | 76 ++++++++++++ .../oracle/oci_support_mcp_server/server.py | 113 ++++++++++++++++++ 2 files changed, 189 insertions(+) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py index e9e387b4..48a27bc6 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -631,3 +631,79 @@ def get(field, default=None): problem_type=get("problem_type"), referrer=get("referrer"), ) + +def map_incident_resource_type(oci_resource_type) -> IncidentResourceType | None: + if not oci_resource_type: + return None + def get(field, default=None): + if isinstance(oci_resource_type, dict): + return oci_resource_type.get(field, default) + return getattr(oci_resource_type, field, default) + return IncidentResourceType( + resource_type_key=get("resource_type_key"), + name=get("name"), + label=get("label"), + description=get("description"), + is_subscriptions_supported=get("is_subscriptions_supported"), + service_category_list=[map_service_category(sc) for sc in (get("service_category_list") or [])] if get("service_category_list") else None, + service=get("service"), + services=[map_services(s) for s in (get("services") or [])] if get("services") else None, + ) + +def map_service_category(oci_service_category) -> ServiceCategory | None: + if not oci_service_category: + return None + def get(field, default=None): + if isinstance(oci_service_category, dict): + return oci_service_category.get(field, default) + return getattr(oci_service_category, field, default) + return ServiceCategory( + key=get("key"), + name=get("name"), + label=get("label"), + description=get("description"), + issue_type_list=[map_issue_type(it) for it in (get("issue_type_list") or [])] if get("issue_type_list") else None, + supported_subscriptions=get("supported_subscriptions"), + scope=get("scope"), + unit=get("unit"), + limit_id=get("limit_id"), + ) + +def map_services(oci_services) -> Services | None: + if not oci_services: + return None + def get(field, default=None): + if isinstance(oci_services, dict): + return oci_services.get(field, default) + return getattr(oci_services, field, default) + return Services( + service=get("service"), + schema=get("schema"), + service_categories=[map_sub_categories(sc) for sc in (get("service_categories") or [])] if get("service_categories") else None + ) + +def map_sub_categories(oci_sub_categories) -> SubCategories | None: + if not oci_sub_categories: + return None + def get(field, default=None): + if isinstance(oci_sub_categories, dict): + return oci_sub_categories.get(field, default) + return getattr(oci_sub_categories, field, default) + return SubCategories( + service_category=get("service_category"), + schema=get("schema"), + has_sub_category=get("has_sub_category"), + sub_categories=[map_sub_components(sc) for sc in (get("sub_categories") or [])] if get("sub_categories") else None + ) + +def map_sub_components(oci_sub_components) -> SubComponents | None: + if not oci_sub_components: + return None + def get(field, default=None): + if isinstance(oci_sub_components, dict): + return oci_sub_components.get(field, default) + return getattr(oci_sub_components, field, default) + return SubComponents( + sub_category=get("sub_category"), + schema=get("schema"), + ) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py index a46cf373..9f2fc568 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py @@ -16,6 +16,8 @@ Incident, map_incident, CreateIncident, + IncidentResourceType, + map_incident_resource_type, ) from pydantic import Field @@ -399,5 +401,116 @@ def main(): mcp.run() +@mcp.tool( + description="Lists available incident resource types (products/services/service-categories) for OCI support requests using the OCI CIMS API. Returns mapped IncidentResourceType models." +) +def list_incident_resource_types( + problem_type: str = Field( + ..., + description="The kind of support request." + ), + compartment_id: str = Field( + ..., + description="The OCID of the tenancy." + ), + opc_request_id: Optional[str] = Field( + None, + description="Unique Oracle-assigned identifier for the request. If you need to contact Oracle about a particular request, provide this ID." + ), + limit: Optional[int] = Field( + None, + description="For list pagination. The maximum number of results per page, or items to return in a paginated List call." + ), + page: Optional[str] = Field( + None, + description="For list pagination. The value of the 'opc-next-page' response header from the previous List call." + ), + sort_by: Optional[str] = Field( + None, + description="The key to use to sort the returned items. Allowed values: 'dateUpdated', 'severity'." + ), + sort_order: Optional[str] = Field( + None, + description="The order to sort the results in. Allowed values: 'ASC', 'DESC'." + ), + name: Optional[str] = Field( + None, + description="The user-friendly name of the support request type." + ), + csi: Optional[str] = Field( + None, + description="The Customer Support Identifier (CSI) number associated with the support account." + ), + ocid: Optional[str] = Field( + None, + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account." + ), + homeregion: Optional[str] = Field( + None, + description="The region of the tenancy." + ), + domainid: Optional[str] = Field( + None, + description="The OCID of identity domain. DomainID is mandatory if the user is part of Non Default Identity domain." + ), + allow_control_chars: Optional[bool] = Field( + None, + description="Set to True to allow control characters in the response object." + ), + retry_strategy: Optional[str] = Field( + None, + description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default." + ) +) -> List[IncidentResourceType]: + """ + Lists available incident resource types (products/services/service-categories) for OCI support requests. + Returns a list of mapped IncidentResourceType Pydantic models. + """ + logger.info("Calling OCI CIMS IncidentClient.list_incident_resource_types") + try: + client = get_cims_client() + kwargs = { + "problem_type": problem_type, + "compartment_id": compartment_id, + } + if opc_request_id is not None: + kwargs["opc_request_id"] = opc_request_id + if limit is not None: + kwargs["limit"] = limit + if page is not None: + kwargs["page"] = page + if sort_by is not None: + kwargs["sort_by"] = sort_by + if sort_order is not None: + kwargs["sort_order"] = sort_order + if name is not None: + kwargs["name"] = name + if csi is not None: + kwargs["csi"] = csi + if ocid is not None: + kwargs["ocid"] = ocid + if homeregion is not None: + kwargs["homeregion"] = homeregion + if domainid is not None: + kwargs["domainid"] = domainid + if allow_control_chars is not None: + kwargs["allow_control_chars"] = allow_control_chars + if retry_strategy is not None: + import oci.retry + if retry_strategy == "default": + kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY + elif retry_strategy == "none": + kwargs["retry_strategy"] = oci.retry.NoneRetryStrategy + + response = client.list_incident_resource_types(**kwargs) + data = getattr(response, "data", []) or [] + mapped = [map_incident_resource_type(x) for x in data if x is not None] + logger.info(f"Found {len(mapped)} incident resource types") + return mapped + except Exception as e: + logger.error(f"Error in list_incident_resource_types tool: {str(e)}") + raise e + + if __name__ == "__main__": main() From cd9ddcf801b34a976adfeb62d25da01489ef6359 Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Thu, 8 Jan 2026 13:49:08 +0530 Subject: [PATCH 08/13] 1.Created Pydantic model for validationResponse 2. Created MCP tool function for validate api --- .../oracle/oci_support_mcp_server/models.py | 34 ++++++ .../oracle/oci_support_mcp_server/server.py | 115 +++++++++++++++--- 2 files changed, 133 insertions(+), 16 deletions(-) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py index 48a27bc6..1fe41f8a 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -194,6 +194,14 @@ class CreateIncident(BaseModel): contacts: Optional[List[Contact]] = Field(None) referrer: Optional[str] = Field(None) +class CmosUserGroupInfo(BaseModel): + user_group_id: Optional[str] = Field(None) + user_group_name: Optional[str] = Field(None) + +class ValidationResponse(BaseModel): + is_valid_user: Optional[bool] = Field(None) + write_permitted_user_group_infos: Optional[List[CmosUserGroupInfo]] = Field(None) + # --- Conversion to OCI SDK Model Utilities --- @@ -707,3 +715,29 @@ def get(field, default=None): sub_category=get("sub_category"), schema=get("schema"), ) + +def map_cmos_user_group_info(oci_user_group_info) -> CmosUserGroupInfo | None: + if not oci_user_group_info: + return None + def get(field, default=None): + if isinstance(oci_user_group_info, dict): + return oci_user_group_info.get(field, default) + return getattr(oci_user_group_info, field, default) + return CmosUserGroupInfo( + user_group_id=get("user_group_id"), + user_group_name=get("user_group_name"), + ) + +def map_validation_response(oci_validation_response) -> ValidationResponse | None: + if not oci_validation_response: + return None + def get(field, default=None): + if isinstance(oci_validation_response, dict): + return oci_validation_response.get(field, default) + return getattr(oci_validation_response, field, default) + user_groups = get("write_permitted_user_group_infos") + mapped_groups = [map_cmos_user_group_info(g) for g in (user_groups or [])] if user_groups else None + return ValidationResponse( + is_valid_user=get("is_valid_user"), + write_permitted_user_group_infos=mapped_groups + ) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py index 9f2fc568..92743468 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py @@ -5,11 +5,11 @@ """ import os -from logging import Logger -from typing import Annotated, List, Optional - import oci +from logging import Logger +from typing import List, Optional from fastmcp import FastMCP +from pydantic import Field from oracle.oci_support_mcp_server.models import ( IncidentSummary, map_incident_summary, @@ -18,14 +18,14 @@ CreateIncident, IncidentResourceType, map_incident_resource_type, + ValidationResponse, + map_validation_response, + ) -from pydantic import Field __project__ = "oracle.oci_support_mcp_server" __version__ = "0.1.0" - logger = Logger(__name__, level="INFO") - mcp = FastMCP(name=__project__) @@ -392,15 +392,6 @@ def create_incident( logger.error(f"Error in create_incident tool: {str(e)}") raise e -def main(): - host = os.getenv("ORACLE_MCP_HOST") - port = os.getenv("ORACLE_MCP_PORT") - if host and port: - mcp.run(transport="http", host=host, port=int(port)) - else: - mcp.run() - - @mcp.tool( description="Lists available incident resource types (products/services/service-categories) for OCI support requests using the OCI CIMS API. Returns mapped IncidentResourceType models." ) @@ -512,5 +503,97 @@ def list_incident_resource_types( raise e +@mcp.tool( + description="Check whether the requested user is valid with OCI Support API (CIMS). Returns mapped ValidationResponse model." +) +def validate_user( + csi: Optional[str] = Field( + None, description="The Customer Support Identifier (CSI) number associated with the support account." + ), + opc_request_id: Optional[str] = Field( + None, description="Unique Oracle-assigned identifier for the request. If you need to contact Oracle about a particular request, please provide the request ID." + ), + problem_type: Optional[str] = Field( + None, description="The kind of support request. Allowed: LIMIT, LEGACY_LIMIT, TECH, ACCOUNT, TAXONOMY" + ), + ocid: Optional[str] = Field( + None, description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account." + ), + homeregion: Optional[str] = Field( + None, description="The region of the tenancy." + ), + bearertokentype: Optional[str] = Field( + None, description="Token type that determines which cloud provider the request comes from." + ), + bearertoken: Optional[str] = Field( + None, description="Token provided by multi cloud provider, which helps to validate the email." + ), + idtoken: Optional[str] = Field( + None, description="IdToken provided by multi cloud provider, which helps to validate the email." + ), + domainid: Optional[str] = Field( + None, description="The OCID of identity domain. DomainID is mandatory if the user is part of Non Default Identity domain." + ), + allow_control_chars: Optional[bool] = Field( + None, description="Set to True to allow control characters in the response object." + ), + retry_strategy: Optional[str] = Field( + None, description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default." + ) +) -> ValidationResponse: + """ + Checks whether the requested user is valid via OCI CIMS. Returns mapped ValidationResponse Pydantic model. + """ + logger.info("Calling OCI CIMS IncidentClient.validate_user") + try: + client = get_cims_client() + kwargs = {} + if csi is not None: + kwargs["csi"] = csi + if opc_request_id is not None: + kwargs["opc_request_id"] = opc_request_id + if problem_type is not None: + kwargs["problem_type"] = problem_type + if ocid is not None: + kwargs["ocid"] = ocid + if homeregion is not None: + kwargs["homeregion"] = homeregion + if bearertokentype is not None: + kwargs["bearertokentype"] = bearertokentype + if bearertoken is not None: + kwargs["bearertoken"] = bearertoken + if idtoken is not None: + kwargs["idtoken"] = idtoken + if domainid is not None: + kwargs["domainid"] = domainid + if allow_control_chars is not None: + kwargs["allow_control_chars"] = allow_control_chars + if retry_strategy is not None: + import oci.retry + if retry_strategy == "default": + kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY + elif retry_strategy == "none": + kwargs["retry_strategy"] = oci.retry.NoneRetryStrategy + + response = client.validate_user(**kwargs) + data = getattr(response, "data", None) + mapped = map_validation_response(data) + if mapped is None: + return ValidationResponse() + return mapped + except Exception as e: + logger.error(f"Error in validate_user tool: {str(e)}") + raise e + +def main(): + host = os.getenv("ORACLE_MCP_HOST") + port = os.getenv("ORACLE_MCP_PORT") + if host and port: + mcp.run(transport="http", host=host, port=int(port)) + else: + mcp.run() + + + if __name__ == "__main__": - main() + main() \ No newline at end of file From e7cbf62025a4204b758e5c60bfbbd1f6d8e773dd Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Thu, 8 Jan 2026 18:08:33 +0530 Subject: [PATCH 09/13] Readme file for oci-support-mcp-server --- src/oci-support-mcp-server/README.md | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/oci-support-mcp-server/README.md b/src/oci-support-mcp-server/README.md index e69de29b..2e7b6ab8 100644 --- a/src/oci-support-mcp-server/README.md +++ b/src/oci-support-mcp-server/README.md @@ -0,0 +1,78 @@ +# OCI Support MCP Server + +## Overview + +This MCP server provides tools to interact with Oracle Cloud Infrastructure (OCI) Support resources via the CIMS API. The server enables listing and managing support tickets, validating users making it easy to automate and monitor support operations. + +## Running the server + +### STDIO transport mode (for local testing) + +```sh +uv oracle.oci-support-mcp-server +``` + +### HTTP streaming transport mode + +```sh +ORACLE_MCP_HOST= ORACLE_MCP_PORT= uv oracle.oci-support-mcp-server +``` + +## Tools + +| Tool Name | Description | +| --- | --- | +| list_incidents | List support incidents for the tenancy using Oracle CIMS. | +| get_incident | Get details of a specific support incident. | +| create_incident | Create a new support incident with all required details. | +| list_incident_resource_types | List available incident resource types (products, services, service categories) for OCI support requests. | +| validate_user | Validate if a user (by OCID/CSI and credentials) is permitted for OCI support operations. | + +⚠️ **NOTE**: All actions are performed with the permissions of the configured OCI CLI profile. We advise least-privilege IAM setup, secure credential management, safe network practices, secure logging, and warn against exposing secrets. + +## Authentication + +> **Important:** +> The OCI Support MCP Server does NOT support `oci session authenticate` or token-based authentication. +> Instead, it authenticates exclusively using the OCI config file with a private key and fingerprint. + +You must provide credentials in the standard OCI CLI config file (see [OCI docs](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm)). + +**Example environment configuration for Cline or compatible clients:** + +```json +{ + "mcpServers": { + "oracle-oci-support-mcp-server": { + "type": "stdio", + "command": "uv", + "args": [ + "oracle.oci-support-mcp-server" + ], + "env": { + "OCI_CONFIG_PROFILE": "", // OCI CLI profile with private key & fingerprint + "OCI_CONFIG_FILE": "/.oci/config" // Path to the OCI CLI config file (typically ~/.oci/config) + } + } + } +} +``` +- `OCI_CONFIG_PROFILE` — This is the name of your OCI config CLI profile that includes the correct private key and fingerprint. +- `OCI_CONFIG_FILE` — Path to the OCI CLI config file (default: `~/.oci/config`). + +⚠️ **NOTE:** Session-based or ephemeral authentication (e.g., using `oci session authenticate`) is **not supported** for the Support MCP Server. Only config-file-based authentication is supported. + +## Third-Party APIs + +Developers distributing a binary implementation of this project are responsible for obtaining and providing all required licenses and copyright notices for third-party code to ensure compliance with their respective open source licenses. + +## Disclaimer + +Users are responsible for their local environment and credential safety. Use of this software may impact OCI support configuration if misused. Different LLM/model selections and prompt configurations can yield different results and performance. + +## License + +Copyright (c) 2025 Oracle and/or its affiliates. + +Released under the Universal Permissive License v1.0 as shown at +. From fa94fd65e08452ae663bacdb40d491d073fc91c6 Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Fri, 9 Jan 2026 11:36:18 +0530 Subject: [PATCH 10/13] Formatting the files --- .../oracle/oci_support_mcp_server/models.py | 282 ++++++++++++++---- .../oracle/oci_support_mcp_server/server.py | 273 +++++++++-------- 2 files changed, 372 insertions(+), 183 deletions(-) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py index 1fe41f8a..cca23611 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/models.py @@ -4,12 +4,14 @@ https://oss.oracle.com/licenses/upl. """ -from datetime import datetime -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional + +import oci.cims.models from pydantic import BaseModel, Field # --- OCI Support CIMS Pydantic Models --- + class Contact(BaseModel): contact_name: Optional[str] = Field(None) contact_email: Optional[str] = Field(None) @@ -17,6 +19,7 @@ class Contact(BaseModel): contact_phone: Optional[str] = Field(None) contact_type: Optional[str] = Field(None) + class ContactList(BaseModel): contact_list: List[Contact] @@ -26,10 +29,12 @@ class IssueType(BaseModel): label: Optional[str] = Field(None) name: Optional[str] = Field(None) + class Category(BaseModel): category_key: Optional[str] = Field(None) name: Optional[str] = Field(None) + class SubCategory(BaseModel): sub_category_key: Optional[str] = Field(None) name: Optional[str] = Field(None) @@ -50,6 +55,7 @@ class Item(BaseModel): sub_category: Optional[SubCategory] = Field(None) issue_type: Optional[IssueType] = Field(None) + class Resource(BaseModel): item: Optional[Item] = Field(None) region: Optional[str] = Field(None) @@ -58,7 +64,9 @@ class Resource(BaseModel): class Ticket(BaseModel): ticket_number: Optional[str] = Field(None) severity: Optional[str] = Field(None) - resource_list: Optional[List[dict]] = Field(None) # dict instead of Resource for serialization safety + resource_list: Optional[List[dict]] = Field( + None + ) # dict instead of Resource for serialization safety title: Optional[str] = Field(None) description: Optional[str] = Field(None) time_created: Optional[int] = Field(None) @@ -73,6 +81,7 @@ class User(BaseModel): contact_number: Optional[str] = Field(None) country: Optional[str] = Field(None) + class Context(BaseModel): context_type: Optional[str] = Field(None) description: Optional[str] = Field(None) @@ -83,6 +92,7 @@ class TenancyInformation(BaseModel): customer_support_key: Optional[str] = Field(None) tenancy_id: Optional[str] = Field(None) + class ServiceCategory(BaseModel): key: Optional[str] = Field(None) name: Optional[str] = Field(None) @@ -94,21 +104,25 @@ class ServiceCategory(BaseModel): unit: Optional[str] = Field(None) limit_id: Optional[str] = Field(None) + class SubComponents(BaseModel): sub_category: Optional[Dict[str, str]] = Field(None) schema: Optional[str] = Field(None) + class SubCategories(BaseModel): service_category: Optional[Dict[str, str]] = Field(None) schema: Optional[str] = Field(None) has_sub_category: Optional[str] = Field(None) sub_categories: Optional[List[SubComponents]] = Field(None) + class Services(BaseModel): service: Optional[Dict[str, str]] = Field(None) schema: Optional[str] = Field(None) service_categories: Optional[List[SubCategories]] = Field(None) + class IncidentResourceType(BaseModel): resource_type_key: Optional[str] = Field(None) name: Optional[str] = Field(None) @@ -119,6 +133,7 @@ class IncidentResourceType(BaseModel): service: Optional[Dict[str, str]] = Field(None) services: Optional[List[Services]] = Field(None) + class IncidentSummary(BaseModel): key: Optional[str] = Field(None) compartment_id: Optional[str] = Field(None) @@ -135,6 +150,7 @@ class IncidentSummary(BaseModel): warn_message: Optional[str] = Field(None) problem_type: Optional[str] = Field(None) + class Incident(BaseModel): key: Optional[str] = Field(None) compartment_id: Optional[str] = Field(None) @@ -152,21 +168,26 @@ class Incident(BaseModel): problem_type: Optional[str] = Field(None) referrer: Optional[str] = Field(None) + class ContextualData(BaseModel): client_id: Optional[str] = Field(None) schema_name: Optional[str] = Field(None) schema_version: Optional[str] = Field(None) payload: Optional[str] = Field(None) + class CreateCategoryDetails(BaseModel): category_key: Optional[str] = Field(None) + class CreateSubCategoryDetails(BaseModel): sub_category_key: Optional[str] = Field(None) + class CreateIssueTypeDetails(BaseModel): issue_type_key: Optional[str] = Field(None) + class CreateItemDetails(BaseModel): type: Optional[str] = Field(None) category: Optional[CreateCategoryDetails] = Field(None) @@ -174,10 +195,12 @@ class CreateItemDetails(BaseModel): issue_type: Optional[CreateIssueTypeDetails] = Field(None) name: Optional[str] = Field(None) + class CreateResourceDetails(BaseModel): item: Optional[CreateItemDetails] = Field(None) region: Optional[str] = Field(None) + class CreateTicketDetails(BaseModel): severity: Optional[str] = Field(None) resource_list: Optional[List[CreateResourceDetails]] = Field(None) @@ -185,6 +208,7 @@ class CreateTicketDetails(BaseModel): description: Optional[str] = Field(None) contextual_data: Optional[ContextualData] = Field(None) + class CreateIncident(BaseModel): compartment_id: Optional[str] = Field(None) ticket: Optional[CreateTicketDetails] = Field(None) @@ -194,89 +218,112 @@ class CreateIncident(BaseModel): contacts: Optional[List[Contact]] = Field(None) referrer: Optional[str] = Field(None) + class CmosUserGroupInfo(BaseModel): user_group_id: Optional[str] = Field(None) user_group_name: Optional[str] = Field(None) + class ValidationResponse(BaseModel): is_valid_user: Optional[bool] = Field(None) write_permitted_user_group_infos: Optional[List[CmosUserGroupInfo]] = Field(None) - # --- Conversion to OCI SDK Model Utilities --- + def to_oci_create_category_details(p): if p is None: return None - import oci.cims.models + return oci.cims.models.CreateCategoryDetails(category_key=p.category_key) + def to_oci_create_sub_category_details(p): if p is None: return None - import oci.cims.models + return oci.cims.models.CreateSubCategoryDetails(sub_category_key=p.sub_category_key) + def to_oci_create_issue_type_details(p): if p is None: return None - import oci.cims.models + return oci.cims.models.CreateIssueTypeDetails(issue_type_key=p.issue_type_key) + def to_oci_create_item_details(p): if p is None: return None - import oci.cims.models + return oci.cims.models.CreateItemDetails( type=p.type, - category=to_oci_create_category_details(p.category) if hasattr(p, "category") else None, - sub_category=to_oci_create_sub_category_details(p.sub_category) if hasattr(p, "sub_category") else None, - issue_type=to_oci_create_issue_type_details(p.issue_type) if hasattr(p, "issue_type") else None, - name=p.name + category=( + to_oci_create_category_details(p.category) + if hasattr(p, "category") + else None + ), + sub_category=( + to_oci_create_sub_category_details(p.sub_category) + if hasattr(p, "sub_category") + else None + ), + issue_type=( + to_oci_create_issue_type_details(p.issue_type) + if hasattr(p, "issue_type") + else None + ), + name=p.name, ) + def to_oci_create_resource_details(p): if p is None: return None - import oci.cims.models return oci.cims.models.CreateResourceDetails( item=to_oci_create_item_details(p.item) if hasattr(p, "item") else None, - region=p.region + region=p.region, ) + def to_oci_contextual_data(p): if p is None: return None - import oci.cims.models + return oci.cims.models.ContextualData( client_id=p.client_id, schema_name=p.schema_name, schema_version=p.schema_version, - payload=p.payload + payload=p.payload, ) + def to_oci_create_ticket_details(p): if p is None: return None - import oci.cims.models - resource_list = [ - to_oci_create_resource_details(r) - for r in (p.resource_list or []) - ] if p.resource_list else None - contextual_data = to_oci_contextual_data(p.contextual_data) if p.contextual_data else None + + resource_list = ( + [to_oci_create_resource_details(r) for r in (p.resource_list or [])] + if p.resource_list + else None + ) + contextual_data = ( + to_oci_contextual_data(p.contextual_data) if p.contextual_data else None + ) return oci.cims.models.CreateTicketDetails( severity=p.severity, resource_list=resource_list, title=p.title, description=p.description, - contextual_data=contextual_data + contextual_data=contextual_data, ) + def to_oci_create_incident(p): if p is None: return None - import oci.cims.models + ticket = to_oci_create_ticket_details(p.ticket) if p.ticket else None return oci.cims.models.CreateIncident( compartment_id=p.compartment_id, @@ -284,10 +331,15 @@ def to_oci_create_incident(p): csi=p.csi, user_group_id=p.user_group_id, problem_type=p.problem_type, - contacts=[c.model_dump(exclude_none=True) for c in (p.contacts or [])] if getattr(p, "contacts", None) else None, - referrer=p.referrer + contacts=( + [c.model_dump(exclude_none=True) for c in (p.contacts or [])] + if getattr(p, "contacts", None) + else None + ), + referrer=p.referrer, ) + # --- Mapping Utilities --- def map_user(oci_user) -> User | None: if not oci_user: @@ -299,6 +351,7 @@ def map_user(oci_user) -> User | None: country=getattr(oci_user, "country", None), ) + def map_context(oci_context) -> Context | None: if not oci_context: return None @@ -308,13 +361,16 @@ def map_context(oci_context) -> Context | None: additional_details=getattr(oci_context, "additional_details", None), ) + def map_contact(oci_contact) -> Contact | None: if not oci_contact: return None + def get(field, default=None): if isinstance(oci_contact, dict): return oci_contact.get(field, default) return getattr(oci_contact, field, default) + return Contact( contact_name=get("contact_name"), contact_email=get("contact_email"), @@ -323,40 +379,50 @@ def get(field, default=None): contact_type=get("contact_type"), ) + def map_contact_list(oci_contact_list) -> ContactList | None: if not oci_contact_list: return None + def get(field, default=None): if isinstance(oci_contact_list, dict): return oci_contact_list.get(field, default) return getattr(oci_contact_list, field, default) + cl = get("contact_list") return ContactList(contact_list=[map_contact(c) for c in (cl or [])]) + def map_category(oci_category) -> Category | None: if not oci_category: return None + def get(field, default=None): if isinstance(oci_category, dict): return oci_category.get(field, default) return getattr(oci_category, field, default) + return Category( category_key=get("category_key"), name=get("name"), ) + def map_sub_category(oci_sub_category) -> SubCategory | None: if not oci_sub_category: return None + def get(field, default=None): if isinstance(oci_sub_category, dict): return oci_sub_category.get(field, default) return getattr(oci_sub_category, field, default) + return SubCategory( sub_category_key=get("sub_category_key"), name=get("name"), ) + def map_incident_type(oci_incident_type) -> IncidentType | None: if not oci_incident_type: return None @@ -367,59 +433,73 @@ def map_incident_type(oci_incident_type) -> IncidentType | None: sub_category=map_sub_category(getattr(oci_incident_type, "sub_category", None)), ) -def map_resource(oci_resource) -> Resource | None: - if not oci_resource: - return None - return Resource( - item=getattr(oci_resource, "item", None), - region=getattr(oci_resource, "region", None), - availability_domain=getattr(oci_resource, "availability_domain", None), - compartment_id=getattr(oci_resource, "compartment_id", None), - ) -def map_ticket(oci_ticket) -> Optional[str]: - if not oci_ticket: - return None - if isinstance(oci_ticket, str): - return oci_ticket - if isinstance(oci_ticket, dict): - # Try to get ticket['key'] first; else fallback to something unique or string repr - return oci_ticket.get("key") or oci_ticket.get("summary") or str(oci_ticket) - # Try attribute access (object with .key or .summary), else fallback to str(obj) - return getattr(oci_ticket, "key", None) or getattr(oci_ticket, "summary", None) or str(oci_ticket) +# def map_resource(oci_resource) -> Resource | None: +# if not oci_resource: +# return None +# return Resource( +# item=getattr(oci_resource, "item", None), +# region=getattr(oci_resource, "region", None), +# availability_domain=getattr(oci_resource, "availability_domain", None), +# compartment_id=getattr(oci_resource, "compartment_id", None), +# ) + + +# def map_ticket(oci_ticket) -> Optional[str]: +# if not oci_ticket: +# return None +# if isinstance(oci_ticket, str): +# return oci_ticket +# if isinstance(oci_ticket, dict): +# # Try to get ticket['key'] first; else fallback to something unique or string repr +# return oci_ticket.get("key") or oci_ticket.get("summary") or str(oci_ticket) +# # Try attribute access (object with .key or .summary), else fallback to str(obj) +# return ( +# getattr(oci_ticket, "key", None) +# or getattr(oci_ticket, "summary", None) +# or str(oci_ticket) +# ) + def map_tenancy_information(oci_ti) -> TenancyInformation | None: if not oci_ti: return None + def get(field, default=None): if isinstance(oci_ti, dict): return oci_ti.get(field, default) return getattr(oci_ti, field, default) + return TenancyInformation( - customer_support_key=get("customer_support_key"), - tenancy_id=get("tenancy_id") + customer_support_key=get("customer_support_key"), tenancy_id=get("tenancy_id") ) + def map_issue_type(oci_issue_type) -> IssueType | None: if not oci_issue_type: return None + def get(field, default=None): if isinstance(oci_issue_type, dict): return oci_issue_type.get(field, default) return getattr(oci_issue_type, field, default) + return IssueType( issue_type_key=get("issue_type_key"), label=get("label"), name=get("name"), ) + def map_item(oci_item) -> Item | None: if not oci_item: return None + def get(field, default=None): if isinstance(oci_item, dict): return oci_item.get(field, default) return getattr(oci_item, field, default) + mapped_category = map_category(get("category")) if mapped_category is not None and hasattr(mapped_category, "model_dump"): mapped_category = mapped_category.model_dump(exclude_none=True) @@ -438,13 +518,16 @@ def get(field, default=None): issue_type=mapped_issue_type, ) + def map_resource(oci_resource) -> Resource | None: if not oci_resource: return None + def get(field, default=None): if isinstance(oci_resource, dict): return oci_resource.get(field, default) return getattr(oci_resource, field, default) + mapped_item = map_item(get("item")) if mapped_item is not None and hasattr(mapped_item, "model_dump"): mapped_item = mapped_item.model_dump(exclude_none=True) @@ -453,13 +536,16 @@ def get(field, default=None): region=get("region"), ) + def map_ticket(oci_ticket) -> Ticket | None: if not oci_ticket: return None + def get(field, default=None): if isinstance(oci_ticket, dict): return oci_ticket.get(field, default) return getattr(oci_ticket, field, default) + resource_list = [] for r in get("resource_list") or []: mapped = map_resource(r) @@ -480,6 +566,7 @@ def get(field, default=None): lifecycle_details=get("lifecycle_details"), ) + def map_incident_summary(oci_incident_summary) -> IncidentSummary | None: if not oci_incident_summary: return None @@ -495,7 +582,9 @@ def get(field, default=None): contact_list=map_contact_list(get("contact_list")), tenancy_information=map_tenancy_information(get("tenancy_information")), ticket=map_ticket(get("ticket")), - incident_type=get("incident_type"), # for now, adjust if explicit mapping is required + incident_type=get( + "incident_type" + ), # for now, adjust if explicit mapping is required migrated_sr_number=get("migrated_sr_number"), user_group_id=get("user_group_id"), user_group_name=get("user_group_name"), @@ -505,13 +594,17 @@ def get(field, default=None): warn_message=get("warn_message"), problem_type=get("problem_type"), ) + + def map_contextual_data(oci_ctx) -> ContextualData | None: if not oci_ctx: return None + def get(field, default=None): if isinstance(oci_ctx, dict): return oci_ctx.get(field, default) return getattr(oci_ctx, field, default) + return ContextualData( client_id=get("client_id"), schema_name=get("schema_name"), @@ -519,46 +612,58 @@ def get(field, default=None): payload=get("payload"), ) + def map_create_category_details(oci_cat) -> CreateCategoryDetails | None: if not oci_cat: return None + def get(field, default=None): if isinstance(oci_cat, dict): return oci_cat.get(field, default) return getattr(oci_cat, field, default) + return CreateCategoryDetails( category_key=get("category_key"), ) + def map_create_sub_category_details(oci_subcat) -> CreateSubCategoryDetails | None: if not oci_subcat: return None + def get(field, default=None): if isinstance(oci_subcat, dict): return oci_subcat.get(field, default) return getattr(oci_subcat, field, default) + return CreateSubCategoryDetails( sub_category_key=get("sub_category_key"), ) + def map_create_issue_type_details(oci_iss) -> CreateIssueTypeDetails | None: if not oci_iss: return None + def get(field, default=None): if isinstance(oci_iss, dict): return oci_iss.get(field, default) return getattr(oci_iss, field, default) + return CreateIssueTypeDetails( issue_type_key=get("issue_type_key"), ) + def map_create_item_details(oci_item) -> CreateItemDetails | None: if not oci_item: return None + def get(field, default=None): if isinstance(oci_item, dict): return oci_item.get(field, default) return getattr(oci_item, field, default) + return CreateItemDetails( type=get("type"), category=map_create_category_details(get("category")), @@ -567,52 +672,66 @@ def get(field, default=None): name=get("name"), ) + def map_create_resource_details(oci_crd) -> CreateResourceDetails | None: if not oci_crd: return None + def get(field, default=None): if isinstance(oci_crd, dict): return oci_crd.get(field, default) return getattr(oci_crd, field, default) + return CreateResourceDetails( item=map_create_item_details(get("item")), region=get("region"), ) + def map_create_ticket_details(oci_ticket) -> CreateTicketDetails | None: if not oci_ticket: return None + def get(field, default=None): if isinstance(oci_ticket, dict): return oci_ticket.get(field, default) return getattr(oci_ticket, field, default) + return CreateTicketDetails( severity=get("severity"), - resource_list=[ - map_create_resource_details(r) for r in (get("resource_list") or []) - ] if get("resource_list") else None, + resource_list=( + [map_create_resource_details(r) for r in (get("resource_list") or [])] + if get("resource_list") + else None + ), title=get("title"), description=get("description"), contextual_data=map_contextual_data(get("contextual_data")), ) + def map_create_incident(oci_incident) -> CreateIncident | None: if not oci_incident: return None + def get(field, default=None): if isinstance(oci_incident, dict): return oci_incident.get(field, default) return getattr(oci_incident, field, default) + return CreateIncident( compartment_id=get("compartment_id"), ticket=map_create_ticket_details(get("ticket")), csi=get("csi"), user_group_id=get("user_group_id"), problem_type=get("problem_type"), - contacts=[map_contact(c) for c in get("contacts") or []] if get("contacts") else None, + contacts=( + [map_contact(c) for c in get("contacts") or []] if get("contacts") else None + ), referrer=get("referrer"), ) + def map_incident(oci_incident) -> Incident | None: if not oci_incident: return None @@ -640,104 +759,149 @@ def get(field, default=None): referrer=get("referrer"), ) + def map_incident_resource_type(oci_resource_type) -> IncidentResourceType | None: if not oci_resource_type: return None + def get(field, default=None): if isinstance(oci_resource_type, dict): return oci_resource_type.get(field, default) return getattr(oci_resource_type, field, default) + return IncidentResourceType( resource_type_key=get("resource_type_key"), name=get("name"), label=get("label"), description=get("description"), is_subscriptions_supported=get("is_subscriptions_supported"), - service_category_list=[map_service_category(sc) for sc in (get("service_category_list") or [])] if get("service_category_list") else None, + service_category_list=( + [map_service_category(sc) for sc in (get("service_category_list") or [])] + if get("service_category_list") + else None + ), service=get("service"), - services=[map_services(s) for s in (get("services") or [])] if get("services") else None, + services=( + [map_services(s) for s in (get("services") or [])] + if get("services") + else None + ), ) + def map_service_category(oci_service_category) -> ServiceCategory | None: if not oci_service_category: return None + def get(field, default=None): if isinstance(oci_service_category, dict): return oci_service_category.get(field, default) return getattr(oci_service_category, field, default) + return ServiceCategory( key=get("key"), name=get("name"), label=get("label"), description=get("description"), - issue_type_list=[map_issue_type(it) for it in (get("issue_type_list") or [])] if get("issue_type_list") else None, + issue_type_list=( + [map_issue_type(it) for it in (get("issue_type_list") or [])] + if get("issue_type_list") + else None + ), supported_subscriptions=get("supported_subscriptions"), scope=get("scope"), unit=get("unit"), limit_id=get("limit_id"), ) + def map_services(oci_services) -> Services | None: if not oci_services: return None + def get(field, default=None): if isinstance(oci_services, dict): return oci_services.get(field, default) return getattr(oci_services, field, default) + return Services( service=get("service"), schema=get("schema"), - service_categories=[map_sub_categories(sc) for sc in (get("service_categories") or [])] if get("service_categories") else None + service_categories=( + [map_sub_categories(sc) for sc in (get("service_categories") or [])] + if get("service_categories") + else None + ), ) + def map_sub_categories(oci_sub_categories) -> SubCategories | None: if not oci_sub_categories: return None + def get(field, default=None): if isinstance(oci_sub_categories, dict): return oci_sub_categories.get(field, default) return getattr(oci_sub_categories, field, default) + return SubCategories( service_category=get("service_category"), schema=get("schema"), has_sub_category=get("has_sub_category"), - sub_categories=[map_sub_components(sc) for sc in (get("sub_categories") or [])] if get("sub_categories") else None + sub_categories=( + [map_sub_components(sc) for sc in (get("sub_categories") or [])] + if get("sub_categories") + else None + ), ) + def map_sub_components(oci_sub_components) -> SubComponents | None: if not oci_sub_components: return None + def get(field, default=None): if isinstance(oci_sub_components, dict): return oci_sub_components.get(field, default) return getattr(oci_sub_components, field, default) + return SubComponents( sub_category=get("sub_category"), schema=get("schema"), ) + def map_cmos_user_group_info(oci_user_group_info) -> CmosUserGroupInfo | None: if not oci_user_group_info: return None + def get(field, default=None): if isinstance(oci_user_group_info, dict): return oci_user_group_info.get(field, default) return getattr(oci_user_group_info, field, default) + return CmosUserGroupInfo( user_group_id=get("user_group_id"), user_group_name=get("user_group_name"), ) + def map_validation_response(oci_validation_response) -> ValidationResponse | None: if not oci_validation_response: return None + def get(field, default=None): if isinstance(oci_validation_response, dict): return oci_validation_response.get(field, default) return getattr(oci_validation_response, field, default) + user_groups = get("write_permitted_user_group_infos") - mapped_groups = [map_cmos_user_group_info(g) for g in (user_groups or [])] if user_groups else None + mapped_groups = ( + [map_cmos_user_group_info(g) for g in (user_groups or [])] + if user_groups + else None + ) return ValidationResponse( is_valid_user=get("is_valid_user"), - write_permitted_user_group_infos=mapped_groups + write_permitted_user_group_infos=mapped_groups, ) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py index 92743468..db209a64 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/server.py @@ -5,23 +5,24 @@ """ import os -import oci from logging import Logger from typing import List, Optional + +import oci from fastmcp import FastMCP -from pydantic import Field from oracle.oci_support_mcp_server.models import ( - IncidentSummary, - map_incident_summary, - Incident, - map_incident, CreateIncident, + Incident, IncidentResourceType, - map_incident_resource_type, + IncidentSummary, ValidationResponse, - map_validation_response, - + map_incident, + map_incident_resource_type, + map_incident_summary, + map_validation_response, + to_oci_create_incident, ) +from pydantic import Field __project__ = "oracle.oci_support_mcp_server" __version__ = "0.1.0" @@ -41,82 +42,92 @@ def get_cims_client(): return oci.cims.IncidentClient(config) - @mcp.tool( - description="List support incidents for the tenancy using the OCI Support API (CIMS). Returns mapped IncidentSummary models." + description=( + "List support incidents for the tenancy using the OCI Support API (CIMS). " + "Returns mapped IncidentSummary models." + ) ) def list_incidents( - compartment_id: str = Field( - ..., - description="The OCID of the tenancy." - ), + compartment_id: str = Field(..., description="The OCID of the tenancy."), csi: Optional[str] = Field( None, - description="The Customer Support Identifier (CSI) number associated with the support account. Optional for all request types." + description=( + "The Customer Support Identifier (CSI) number associated with the support account. " + ), ), ocid: Optional[str] = Field( None, - description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account. Required for OCI users, optional for Multicloud users." + description=( + "User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud " + "Infrastructure account. Required for OCI users, optional for Multicloud users." + ), ), limit: Optional[int] = Field( None, - description="For list pagination. The maximum number of results per page, or items to return in a paginated List call." + description=( + "For list pagination. The maximum number of results per page, or items to return " + "in a paginated List call." + ), ), sort_by: Optional[str] = Field( None, - description="The key to use to sort the returned items. Allowed values: 'dateUpdated', 'severity'." + description="The key to use to sort the returned items. Allowed values: 'dateUpdated', 'severity'.", ), sort_order: Optional[str] = Field( None, - description="The order to sort the results in. Allowed values: 'ASC', 'DESC'." + description="The order to sort the results in. Allowed values: 'ASC', 'DESC'.", ), lifecycle_state: Optional[str] = Field( None, - description="The current state of the ticket. Allowed values: 'ACTIVE', 'CLOSED'." + description="The current state of the ticket. Allowed values: 'ACTIVE', 'CLOSED'.", ), page: Optional[str] = Field( None, - description="For list pagination. The value of the 'opc-next-page' response header from the previous List call." + description="For list pagination. " + "The value of the 'opc-next-page' response header from the previous List call.", ), opc_request_id: Optional[str] = Field( None, - description="Unique Oracle-assigned identifier for the request. If you need to contact Oracle about a particular request, provide this ID." - ), - homeregion: Optional[str] = Field( - None, - description="The region of the tenancy." + description="Unique Oracle-assigned identifier for the request. " + "If you need to contact Oracle about a particular request, provide this ID.", ), + homeregion: Optional[str] = Field(None, description="The region of the tenancy."), problem_type: Optional[str] = Field( None, - description="A filter to return only resources that match the specified problem type. Accepts values such as 'LIMIT', 'TECH', or 'ACCOUNT'." + description="A filter to return only resources that match the specified problem type. " + "Accepts values such as 'LIMIT', 'TECH', or 'ACCOUNT'.", ), bearertokentype: Optional[str] = Field( None, - description="Token type that determines which cloud provider the request comes from." + description="Token type that determines which cloud provider the request comes from.", ), bearertoken: Optional[str] = Field( None, - description="Token provided by multi cloud provider, which helps to validate the email." + description="Token provided by multi cloud provider, which helps to validate the email.", ), idtoken: Optional[str] = Field( None, - description="IdToken provided by multi cloud provider, which helps to validate the email." + description="IdToken provided by multi cloud provider, which helps to validate the email.", ), domainid: Optional[str] = Field( None, - description="The OCID of identity domain. DomainID is mandatory if the user is part of Non Default Identity domain." + description="The OCID of identity domain. " + "DomainID is mandatory if the user is part of Non Default Identity domain.", ), allow_control_chars: Optional[bool] = Field( None, - description="Set to True to allow control characters in the response object." + description="Set to True to allow control characters in the response object.", ), retry_strategy: Optional[str] = Field( None, - description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default. (Advanced/experimental.)" - ) + description="Retry strategy for this operation. Allowed values: 'default', 'none'. " + "If not provided, uses SDK default. (Advanced/experimental.)", + ), ) -> List[IncidentSummary]: """ - Lists OCI support incidents using the OCI CIMS IncidentClient. Returns a list of mapped IncidentSummary models. + Lists OCI support incidents using the OCI CIMS IncidentClient. + Returns a list of mapped IncidentSummary models. All available filter, pagination, and context parameters are supported as per Oracle SDK documentation. """ logger.info("Calling OCI CIMS IncidentClient.list_incidents") @@ -162,7 +173,7 @@ def list_incidents( kwargs["allow_control_chars"] = allow_control_chars if retry_strategy is not None: - import oci.retry + if retry_strategy == "default": kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY elif retry_strategy == "none": @@ -172,7 +183,11 @@ def list_incidents( data = getattr(response, "data", []) or [] mapped = [map_incident_summary(x) for x in data if x is not None] # Convert to dicts for serialization, using mode="json" to recursively dump submodels - mapped_dicts = [m.model_dump(exclude_none=True, mode="json") for m in mapped if m is not None] + mapped_dicts = [ + m.model_dump(exclude_none=True, mode="json") + for m in mapped + if m is not None + ] results.extend(mapped_dicts) has_next_page = getattr(response, "has_next_page", False) @@ -202,57 +217,55 @@ def list_incidents( ) def get_incident( incident_key: str = Field( - ..., - description="Unique identifier for the support request." - ), - compartment_id: str = Field( - ..., - description="The OCID of the tenancy." + ..., description="Unique identifier for the support request." ), + compartment_id: str = Field(..., description="The OCID of the tenancy."), opc_request_id: Optional[str] = Field( None, - description="Unique Oracle-assigned identifier for the request. Provide this if needing to contact Oracle about a particular request." + description="Unique Oracle-assigned identifier for the request. " + "Provide this if needing to contact Oracle about a particular request.", ), csi: Optional[str] = Field( None, - description="The Customer Support Identifier (CSI) number associated with the support account." + description="The Customer Support Identifier (CSI) number associated with the support account.", ), ocid: Optional[str] = Field( None, - description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated OCI account. Required for OCI users, optional for Multicloud." - ), - homeregion: Optional[str] = Field( - None, - description="The region of the tenancy." + description="User OCID for Oracle Identity Cloud Service (IDCS) users who have " + "a federated OCI account. Required for OCI users, optional for Multicloud.", ), + homeregion: Optional[str] = Field(None, description="The region of the tenancy."), problemtype: Optional[str] = Field( None, - description="A filter to return only resources that match the specified problem type. Accepts values such as 'LIMIT', 'TECH', or 'ACCOUNT'." + description="A filter to return only resources that match the specified problem type. " + "Accepts values such as 'LIMIT', 'TECH', or 'ACCOUNT'.", ), bearertokentype: Optional[str] = Field( None, - description="Token type that determines which cloud provider the request comes from." + description="Token type that determines which cloud provider the request comes from.", ), bearertoken: Optional[str] = Field( None, - description="Token provided by multi cloud provider, which helps to validate the email." + description="Token provided by multi cloud provider, which helps to validate the email.", ), idtoken: Optional[str] = Field( None, - description="IdToken provided by multi cloud provider, which helps to validate the email." + description="IdToken provided by multi cloud provider, which helps to validate the email.", ), domainid: Optional[str] = Field( None, - description="The OCID of identity domain. DomainID is mandatory if the user is part of Non Default Identity domain." + description="The OCID of identity domain. " + "DomainID is mandatory if the user is part of Non Default Identity domain.", ), allow_control_chars: Optional[bool] = Field( None, - description="Set to True to allow control characters in the response object." + description="Set to True to allow control characters in the response object.", ), retry_strategy: Optional[str] = Field( None, - description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default. (Advanced/experimental.)" - ) + description="Retry strategy for this operation. Allowed values: 'default', 'none'. " + "If not provided, uses SDK default. (Advanced/experimental.)", + ), ) -> Incident: """ Gets a support request from OCI CIMS by incident_key. Returns mapped Incident Pydantic model. @@ -286,7 +299,7 @@ def get_incident( kwargs["allow_control_chars"] = allow_control_chars if retry_strategy is not None: - import oci.retry + if retry_strategy == "default": kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY elif retry_strategy == "none": @@ -304,58 +317,59 @@ def get_incident( @mcp.tool( - description="Create a support incident (OCI CIMS) with given incident details. Returns mapped Incident Pydantic model." + description="Create a support incident (OCI CIMS) with given incident details. " + "Returns mapped Incident Pydantic model." ) def create_incident( create_incident_details: CreateIncident = Field( ..., - description="Incident information to create request. Pydantic model matching oci.cims.models.CreateIncident." + description="Details gathered during the creation of the support request.", ), opc_request_id: Optional[str] = Field( - None, - description="Unique Oracle-assigned identifier for the request." + None, description="Unique Oracle-assigned identifier for the request." ), ocid: Optional[str] = Field( None, - description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account. Mandatory for OCI users, optional for Multicloud users." - ), - homeregion: Optional[str] = Field( - None, - description="The region of the tenancy." + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated " + "Oracle Cloud Infrastructure account. Mandatory for OCI users, optional for Multicloud users.", ), + homeregion: Optional[str] = Field(None, description="The region of the tenancy."), bearertokentype: Optional[str] = Field( None, - description="Token type that determines which cloud provider the request comes from." + description="Token type that determines which cloud provider the request comes from.", ), bearertoken: Optional[str] = Field( None, - description="Token provided by multi cloud provider, which helps to validate the email." + description="Token provided by multi cloud provider, which helps to validate the email.", ), idtoken: Optional[str] = Field( None, - description="IdToken provided by multi cloud provider, which helps to validate the email." + description="IdToken provided by multi cloud provider, which helps to validate the email.", ), domainid: Optional[str] = Field( None, - description="The OCID of identity domain. Mandatory if the user is part of Non Default Identity domain." + description="The OCID of identity domain. " + "Mandatory if the user is part of Non Default Identity domain.", ), allow_control_chars: Optional[bool] = Field( None, - description="Set to True to allow control characters in the response object." + description="Set to True to allow control characters in the response object.", ), retry_strategy: Optional[str] = Field( None, - description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default. (Advanced/experimental.)" - ) + description="Retry strategy for this operation. Allowed values: 'default', 'none'. " + "If not provided, uses SDK default. (Advanced/experimental.)", + ), ) -> Incident: """ - Create a support incident in OCI CIMS. Receives a CreateIncident Pydantic model, converts to OCI SDK model, and maps the Incident SDK response to API Pydantic. + Create a support incident in OCI CIMS. Receives a CreateIncident Pydantic model, + converts to OCI SDK model, and maps the Incident SDK response to API Pydantic. """ logger.info("Calling OCI CIMS IncidentClient.create_incident") try: client = get_cims_client() # Use recursive converter for SDK CreateIncident object - from oracle.oci_support_mcp_server.models import to_oci_create_incident + sdk_create_incident = to_oci_create_incident(create_incident_details) kwargs = {"create_incident_details": sdk_create_incident} if opc_request_id is not None: @@ -376,7 +390,7 @@ def create_incident( kwargs["allow_control_chars"] = allow_control_chars if retry_strategy is not None: - import oci.retry + if retry_strategy == "default": kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY elif retry_strategy == "none": @@ -392,66 +406,64 @@ def create_incident( logger.error(f"Error in create_incident tool: {str(e)}") raise e + @mcp.tool( - description="Lists available incident resource types (products/services/service-categories) for OCI support requests using the OCI CIMS API. Returns mapped IncidentResourceType models." + description="Lists available incident resource types (products/services/service-categories) for " + "OCI support requests using the OCI CIMS API. Returns mapped IncidentResourceType models." ) def list_incident_resource_types( - problem_type: str = Field( - ..., - description="The kind of support request." - ), - compartment_id: str = Field( - ..., - description="The OCID of the tenancy." - ), + problem_type: str = Field(..., description="The kind of support request."), + compartment_id: str = Field(..., description="The OCID of the tenancy."), opc_request_id: Optional[str] = Field( None, - description="Unique Oracle-assigned identifier for the request. If you need to contact Oracle about a particular request, provide this ID." + description="Unique Oracle-assigned identifier for the request. " + "If you need to contact Oracle about a particular request, provide this ID.", ), limit: Optional[int] = Field( None, - description="For list pagination. The maximum number of results per page, or items to return in a paginated List call." + description="For list pagination. " + "The maximum number of results per page, or items to return in a paginated List call.", ), page: Optional[str] = Field( None, - description="For list pagination. The value of the 'opc-next-page' response header from the previous List call." + description="For list pagination. " + "The value of the 'opc-next-page' response header from the previous List call.", ), sort_by: Optional[str] = Field( None, - description="The key to use to sort the returned items. Allowed values: 'dateUpdated', 'severity'." + description="The key to use to sort the returned items. Allowed values: 'dateUpdated', 'severity'.", ), sort_order: Optional[str] = Field( None, - description="The order to sort the results in. Allowed values: 'ASC', 'DESC'." + description="The order to sort the results in. Allowed values: 'ASC', 'DESC'.", ), name: Optional[str] = Field( - None, - description="The user-friendly name of the support request type." + None, description="The user-friendly name of the support request type." ), csi: Optional[str] = Field( None, - description="The Customer Support Identifier (CSI) number associated with the support account." + description="The Customer Support Identifier (CSI) number associated with the support account.", ), ocid: Optional[str] = Field( None, - description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account." - ), - homeregion: Optional[str] = Field( - None, - description="The region of the tenancy." + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have " + "a federated Oracle Cloud Infrastructure account.", ), + homeregion: Optional[str] = Field(None, description="The region of the tenancy."), domainid: Optional[str] = Field( None, - description="The OCID of identity domain. DomainID is mandatory if the user is part of Non Default Identity domain." + description="The OCID of identity domain. " + "DomainID is mandatory if the user is part of Non Default Identity domain.", ), allow_control_chars: Optional[bool] = Field( None, - description="Set to True to allow control characters in the response object." + description="Set to True to allow control characters in the response object.", ), retry_strategy: Optional[str] = Field( None, - description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default." - ) + description="Retry strategy for this operation. Allowed values: 'default', 'none'. " + "If not provided, uses SDK default.", + ), ) -> List[IncidentResourceType]: """ Lists available incident resource types (products/services/service-categories) for OCI support requests. @@ -487,7 +499,7 @@ def list_incident_resource_types( if allow_control_chars is not None: kwargs["allow_control_chars"] = allow_control_chars if retry_strategy is not None: - import oci.retry + if retry_strategy == "default": kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY elif retry_strategy == "none": @@ -504,42 +516,55 @@ def list_incident_resource_types( @mcp.tool( - description="Check whether the requested user is valid with OCI Support API (CIMS). Returns mapped ValidationResponse model." + description="Check whether the requested user is valid with OCI Support API (CIMS). " + "Returns mapped ValidationResponse model." ) def validate_user( csi: Optional[str] = Field( - None, description="The Customer Support Identifier (CSI) number associated with the support account." + None, + description="The Customer Support Identifier (CSI) number associated with the support account.", ), opc_request_id: Optional[str] = Field( - None, description="Unique Oracle-assigned identifier for the request. If you need to contact Oracle about a particular request, please provide the request ID." + None, + description="Unique Oracle-assigned identifier for the request. " + "If you need to contact Oracle about a particular request, please provide the request ID.", ), problem_type: Optional[str] = Field( - None, description="The kind of support request. Allowed: LIMIT, LEGACY_LIMIT, TECH, ACCOUNT, TAXONOMY" + None, + description="The kind of support request. Allowed: LIMIT, LEGACY_LIMIT, TECH, ACCOUNT, TAXONOMY", ), ocid: Optional[str] = Field( - None, description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have a federated Oracle Cloud Infrastructure account." - ), - homeregion: Optional[str] = Field( - None, description="The region of the tenancy." + None, + description="User OCID for Oracle Identity Cloud Service (IDCS) users who also have " + "a federated Oracle Cloud Infrastructure account.", ), + homeregion: Optional[str] = Field(None, description="The region of the tenancy."), bearertokentype: Optional[str] = Field( - None, description="Token type that determines which cloud provider the request comes from." + None, + description="Token type that determines which cloud provider the request comes from.", ), bearertoken: Optional[str] = Field( - None, description="Token provided by multi cloud provider, which helps to validate the email." + None, + description="Token provided by multi cloud provider, which helps to validate the email.", ), idtoken: Optional[str] = Field( - None, description="IdToken provided by multi cloud provider, which helps to validate the email." + None, + description="IdToken provided by multi cloud provider, which helps to validate the email.", ), domainid: Optional[str] = Field( - None, description="The OCID of identity domain. DomainID is mandatory if the user is part of Non Default Identity domain." + None, + description="The OCID of identity domain. " + "DomainID is mandatory if the user is part of Non Default Identity domain.", ), allow_control_chars: Optional[bool] = Field( - None, description="Set to True to allow control characters in the response object." + None, + description="Set to True to allow control characters in the response object.", ), retry_strategy: Optional[str] = Field( - None, description="Retry strategy for this operation. Allowed values: 'default', 'none'. If not provided, uses SDK default." - ) + None, + description="Retry strategy for this operation. Allowed values: 'default', 'none'. " + "If not provided, uses SDK default.", + ), ) -> ValidationResponse: """ Checks whether the requested user is valid via OCI CIMS. Returns mapped ValidationResponse Pydantic model. @@ -569,7 +594,7 @@ def validate_user( if allow_control_chars is not None: kwargs["allow_control_chars"] = allow_control_chars if retry_strategy is not None: - import oci.retry + if retry_strategy == "default": kwargs["retry_strategy"] = oci.retry.DEFAULT_RETRY_STRATEGY elif retry_strategy == "none": @@ -585,6 +610,7 @@ def validate_user( logger.error(f"Error in validate_user tool: {str(e)}") raise e + def main(): host = os.getenv("ORACLE_MCP_HOST") port = os.getenv("ORACLE_MCP_PORT") @@ -594,6 +620,5 @@ def main(): mcp.run() - if __name__ == "__main__": - main() \ No newline at end of file + main() From af96c5694c664621f729f69d20b064953cbf28be Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Fri, 9 Jan 2026 12:24:00 +0530 Subject: [PATCH 11/13] Test cases for oci-support-mcp-server --- .../tests/test_support_tools.py | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_support_tools.py diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_support_tools.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_support_tools.py new file mode 100644 index 00000000..f84d6c95 --- /dev/null +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_support_tools.py @@ -0,0 +1,128 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from unittest.mock import MagicMock, patch +import pytest +from fastmcp import Client +from oracle.oci_support_mcp_server.server import mcp + +from oracle.oci_support_mcp_server.models import ( + Incident, + IncidentSummary, + IncidentResourceType, + ValidationResponse, + CreateIncident, +) + + +class TestSupportTools: + @pytest.mark.asyncio + @patch("oracle.oci_support_mcp_server.server.get_cims_client") + async def test_list_incidents(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + mock_response = MagicMock() + # Simulate OCI response with sample IncidentSummary data + mock_summary = IncidentSummary(key="INC1", compartment_id="test_compartment") + mock_response.data = [mock_summary] + mock_response.has_next_page = False + mock_response.next_page = None + mock_client.list_incidents.return_value = mock_response + + async with Client(mcp) as client: + call_tool_result = await client.call_tool( + "list_incidents", {"compartment_id": "test_compartment"} + ) + result = call_tool_result.structured_content["result"] + assert isinstance(result, list) + assert result[0]["key"] == "INC1" + + @pytest.mark.asyncio + @patch("oracle.oci_support_mcp_server.server.get_cims_client") + async def test_get_incident(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + mock_response = MagicMock() + mock_incident = Incident(key="INC1", ticket={"severity": "SEV1"}) + mock_response.data = mock_incident + mock_client.get_incident.return_value = mock_response + + async with Client(mcp) as client: + call_tool_result = await client.call_tool( + "get_incident", + {"incident_key": "INC1", "compartment_id": "test_compartment"}, + ) + result = call_tool_result.structured_content + assert result["key"] == "INC1" + assert result["ticket"]["severity"] == "SEV1" + + @pytest.mark.asyncio + @patch("oracle.oci_support_mcp_server.server.get_cims_client") + async def test_create_incident(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + mock_response = MagicMock() + mock_incident = Incident(key="INC1", ticket={"severity": "SEV2"}) + mock_response.data = mock_incident + mock_client.create_incident.return_value = mock_response + + create_incident_details = CreateIncident(compartment_id="test_compartment") + async with Client(mcp) as client: + call_tool_result = await client.call_tool( + "create_incident", {"create_incident_details": create_incident_details} + ) + result = call_tool_result.structured_content + assert result["key"] == "INC1" + assert result["ticket"]["severity"] == "SEV2" + + @pytest.mark.asyncio + @patch("oracle.oci_support_mcp_server.server.get_cims_client") + async def test_list_incident_resource_types(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + mock_response = MagicMock() + mock_res_type = IncidentResourceType(resource_type_key="RT1", name="ResType1") + # Use model_dump to ensure dict serialization, matching FastMCP output + mock_response.data = [mock_res_type.model_dump(exclude_none=True)] + mock_client.list_incident_resource_types.return_value = mock_response + + async with Client(mcp) as client: + call_tool_result = await client.call_tool( + "list_incident_resource_types", + {"problem_type": "TECH", "compartment_id": "test_compartment"}, + ) + result = call_tool_result.structured_content + assert isinstance( + result, dict + ), f"Expected a dict, got {type(result)}: {result}" + entries = result.get("result") + assert isinstance(entries, list), f"'result' is not a list: {entries}" + assert ( + entries + ), f"list_incident_resource_types returned empty list: {entries}" + assert entries[0]["resource_type_key"] == "RT1" + assert entries[0]["name"] == "ResType1" + + @pytest.mark.asyncio + @patch("oracle.oci_support_mcp_server.server.get_cims_client") + async def test_validate_user(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + mock_response = MagicMock() + mock_validation = ValidationResponse(is_valid_user=True) + mock_response.data = mock_validation + mock_client.validate_user.return_value = mock_response + + async with Client(mcp) as client: + call_tool_result = await client.call_tool("validate_user", {}) + result = call_tool_result.structured_content + assert "is_valid_user" in result + assert result["is_valid_user"] From 6fa666cb2b8b066dde0292a9c745b0abc5da7342 Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Tue, 13 Jan 2026 10:43:22 +0530 Subject: [PATCH 12/13] Added test cases for models.py --- .../tests/test_models.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_models.py diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_models.py new file mode 100644 index 00000000..a6d9aeb2 --- /dev/null +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_models.py @@ -0,0 +1,99 @@ +""" +Unit tests for oracle.oci_support_mcp_server.models mapping utilities and Pydantic model validation. +Designed to increase coverage of models.py (mappings and edge cases). +""" + +import pytest + +from oracle.oci_support_mcp_server import models + + +def test_map_incident_summary_with_dict(): + input_dict = { + "key": "K1", + "compartment_id": "C1", + "problem_type": "TECH", + "ticket": { + "severity": "SEV1", + }, + } + result = models.map_incident_summary(input_dict) + assert isinstance(result, models.IncidentSummary) + assert result.key == "K1" + assert result.compartment_id == "C1" + assert result.problem_type == "TECH" + assert result.ticket.severity == "SEV1" + + +def test_map_incident_summary_with_none(): + assert models.map_incident_summary(None) is None + + +def test_map_incident_with_dict(): + input_dict = { + "key": "INC1", + "compartment_id": "COMP1", + "problem_type": "LIMIT", + "ticket": { + "severity": "SEV2", + }, + } + result = models.map_incident(input_dict) + assert isinstance(result, models.Incident) + assert result.key == "INC1" + assert result.compartment_id == "COMP1" + assert result.problem_type == "LIMIT" + assert result.ticket.severity == "SEV2" + + +def test_map_incident_with_none(): + assert models.map_incident(None) is None + + +def test_map_incident_resource_type_with_dict(): + input_dict = { + "resource_type_key": "RTKEY", + "name": "ResourceType1", + "description": "A type", + } + result = models.map_incident_resource_type(input_dict) + assert isinstance(result, models.IncidentResourceType) + assert result.resource_type_key == "RTKEY" + assert result.name == "ResourceType1" + assert result.description == "A type" + + +def test_map_validation_response_with_dict(): + input_dict = { + "is_valid_user": True, + "write_permitted_user_group_infos": [ + {"user_group_id": "UG1", "user_group_name": "Group One"} + ], + } + result = models.map_validation_response(input_dict) + assert result.is_valid_user is True + assert isinstance(result.write_permitted_user_group_infos, list) + assert result.write_permitted_user_group_infos[0].user_group_id == "UG1" + assert result.write_permitted_user_group_infos[0].user_group_name == "Group One" + + +def test_contact_and_map_contact(): + d = {"contact_name": "A", "email": "a@example.com"} + obj = models.Contact(**d) + mapped = models.map_contact(d) + assert mapped.contact_name == "A" + assert mapped.email == "a@example.com" + assert obj.model_dump() == mapped.model_dump() + + +def test_category_and_map_category(): + d = {"category_key": "cat-01", "name": "Hardware"} + obj = models.Category(**d) + mapped = models.map_category(d) + assert mapped.category_key == "cat-01" + assert mapped.name == "Hardware" + assert obj.model_dump() == mapped.model_dump() + + +def test_item_and_map_item_with_none(): + assert models.map_item(None) is None From 640d743fd1d3b8da6ce12d366e3ce04d2edb2709 Mon Sep 17 00:00:00 2001 From: Prashanth G K Date: Tue, 13 Jan 2026 10:57:33 +0530 Subject: [PATCH 13/13] Formatting test files --- .../oracle/oci_support_mcp_server/tests/test_models.py | 2 -- .../oci_support_mcp_server/tests/test_support_tools.py | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_models.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_models.py index a6d9aeb2..7efc59d7 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_models.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_models.py @@ -3,8 +3,6 @@ Designed to increase coverage of models.py (mappings and edge cases). """ -import pytest - from oracle.oci_support_mcp_server import models diff --git a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_support_tools.py b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_support_tools.py index f84d6c95..a8ad2d51 100644 --- a/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_support_tools.py +++ b/src/oci-support-mcp-server/oracle/oci_support_mcp_server/tests/test_support_tools.py @@ -5,17 +5,17 @@ """ from unittest.mock import MagicMock, patch + import pytest from fastmcp import Client -from oracle.oci_support_mcp_server.server import mcp - from oracle.oci_support_mcp_server.models import ( + CreateIncident, Incident, - IncidentSummary, IncidentResourceType, + IncidentSummary, ValidationResponse, - CreateIncident, ) +from oracle.oci_support_mcp_server.server import mcp class TestSupportTools: